Merge pull request #65 from Superalgos/develop
Development progress update for the demo node.
This commit is contained in:
56 files changed
+5015
-2463
No files matched your search
+12
@@ -0,0 +1,12 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
.scannerwork
|
||||
|
||||
\.env
|
||||
|
||||
\.nyc_output/
|
||||
|
||||
coverage/
|
||||
|
||||
node_modules/**
|
||||
|
||||
test/activeCloneList\.js
|
||||
Generated
+155
-155
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@superalgos/operations-client",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.29",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -9,7 +9,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz",
|
||||
"integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "0.12.1"
|
||||
"regenerator-runtime": "^0.12.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"regenerator-runtime": {
|
||||
@@ -25,31 +25,31 @@
|
||||
"integrity": "sha512-wDTJyR76+OBdlltiPs3lc1gD6zR+dSA6nITtgkBeLf1NvWfNotMWdvldIDnF3bu24ySAX9imfzDfkBu4Edtqww==",
|
||||
"requires": {
|
||||
"@babel/runtime": "7.1.2",
|
||||
"@types/jss": "9.5.6",
|
||||
"@types/react-transition-group": "2.0.14",
|
||||
"brcast": "3.0.1",
|
||||
"classnames": "2.2.6",
|
||||
"csstype": "2.5.7",
|
||||
"debounce": "1.2.0",
|
||||
"deepmerge": "2.2.1",
|
||||
"dom-helpers": "3.3.1",
|
||||
"hoist-non-react-statics": "2.5.5",
|
||||
"is-plain-object": "2.0.4",
|
||||
"jss": "9.8.7",
|
||||
"jss-camel-case": "6.1.0",
|
||||
"jss-default-unit": "8.0.2",
|
||||
"jss-global": "3.0.0",
|
||||
"jss-nested": "6.0.1",
|
||||
"jss-props-sort": "6.0.0",
|
||||
"jss-vendor-prefixer": "7.0.0",
|
||||
"keycode": "2.2.0",
|
||||
"normalize-scroll-left": "0.1.2",
|
||||
"popper.js": "1.14.4",
|
||||
"prop-types": "15.6.2",
|
||||
"react-event-listener": "0.6.4",
|
||||
"react-transition-group": "2.5.0",
|
||||
"recompose": "0.30.0",
|
||||
"warning": "4.0.2"
|
||||
"@types/jss": "^9.5.6",
|
||||
"@types/react-transition-group": "^2.0.8",
|
||||
"brcast": "^3.0.1",
|
||||
"classnames": "^2.2.5",
|
||||
"csstype": "^2.5.2",
|
||||
"debounce": "^1.1.0",
|
||||
"deepmerge": "^2.0.1",
|
||||
"dom-helpers": "^3.2.1",
|
||||
"hoist-non-react-statics": "^2.5.0",
|
||||
"is-plain-object": "^2.0.4",
|
||||
"jss": "^9.3.3",
|
||||
"jss-camel-case": "^6.0.0",
|
||||
"jss-default-unit": "^8.0.2",
|
||||
"jss-global": "^3.0.0",
|
||||
"jss-nested": "^6.0.1",
|
||||
"jss-props-sort": "^6.0.0",
|
||||
"jss-vendor-prefixer": "^7.0.0",
|
||||
"keycode": "^2.1.9",
|
||||
"normalize-scroll-left": "^0.1.2",
|
||||
"popper.js": "^1.14.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"react-event-listener": "^0.6.2",
|
||||
"react-transition-group": "^2.2.1",
|
||||
"recompose": "0.28.0 - 0.30.0",
|
||||
"warning": "^4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
@@ -57,7 +57,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.2.tgz",
|
||||
"integrity": "sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "0.12.1"
|
||||
"regenerator-runtime": "^0.12.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@
|
||||
"integrity": "sha512-1kNcxYiIT1x8iDPEAlgmKrfRTIV8UyK6fLVcZ9kMHIKGWft9I451V5mvSrbCjbf7MX1TbLWzZjph0aVCRf9MqQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "7.0.0",
|
||||
"recompose": "0.29.0"
|
||||
"recompose": "^0.29.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"recompose": {
|
||||
@@ -76,12 +76,12 @@
|
||||
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.29.0.tgz",
|
||||
"integrity": "sha512-J/qLXNU4W+AeHCDR70ajW8eMd1uroqZaECTj6qqDLPMILz3y0EzpYlvrnxKB9DnqcngWrtGwjXY9JeXaW9kS1A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "7.0.0",
|
||||
"change-emitter": "0.1.6",
|
||||
"fbjs": "0.8.17",
|
||||
"hoist-non-react-statics": "2.5.5",
|
||||
"react-lifecycles-compat": "3.0.4",
|
||||
"symbol-observable": "1.2.0"
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"change-emitter": "^0.1.2",
|
||||
"fbjs": "^0.8.1",
|
||||
"hoist-non-react-statics": "^2.3.1",
|
||||
"react-lifecycles-compat": "^3.0.2",
|
||||
"symbol-observable": "^1.0.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,8 +97,8 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.6.tgz",
|
||||
"integrity": "sha512-7TWmR5y1jYG4ka4wTZt65RR0kw4WgALFUWktQIWbLnDd6/z/0SQZ/4+UeH0rhdp+HEdIfmzPBH0VwE/4Z9Evzw==",
|
||||
"requires": {
|
||||
"csstype": "2.5.7",
|
||||
"indefinite-observable": "1.0.1"
|
||||
"csstype": "^2.0.0",
|
||||
"indefinite-observable": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@types/prop-types": {
|
||||
@@ -111,8 +111,8 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.4.16.tgz",
|
||||
"integrity": "sha512-lxyoipLWweAnLnSsV4Ho2NAZTKKmxeYgkTQ6PaDiPDU9JJBUY2zJVVGiK1smzYv8+ZgbqEmcm5xM74GCpunSEA==",
|
||||
"requires": {
|
||||
"@types/prop-types": "15.5.6",
|
||||
"csstype": "2.5.7"
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"@types/react-transition-group": {
|
||||
@@ -120,7 +120,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.0.14.tgz",
|
||||
"integrity": "sha512-pa7qB0/mkhwWMBFoXhX8BcntK8G4eQl4sIfSrJCxnivTYRQWjOWf2ClR9bWdm0EUFBDHzMbKYS+QYfDtBzkY4w==",
|
||||
"requires": {
|
||||
"@types/react": "16.4.16"
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/zen-observable": {
|
||||
@@ -133,7 +133,7 @@
|
||||
"resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.1.17.tgz",
|
||||
"integrity": "sha512-7sp24n2HZO4vXgTaKNomLyIfGxG4gDdDkBB0jkRzRi7HhnKmfwhiF/RCiKNbgLdrPX151INdls0KwIVliD0dHQ==",
|
||||
"requires": {
|
||||
"apollo-utilities": "1.0.21"
|
||||
"apollo-utilities": "^1.0.21"
|
||||
}
|
||||
},
|
||||
"apollo-client": {
|
||||
@@ -142,13 +142,13 @@
|
||||
"integrity": "sha512-g1z23umaVSoKLj9xNl0aAnk2KBF4JeBi7MeKFc9CGTixH7TkqeQUQtxcjrC7j2h4KmDbuhOAHOFUGf8YshN+ag==",
|
||||
"requires": {
|
||||
"@types/async": "2.0.49",
|
||||
"@types/zen-observable": "0.8.0",
|
||||
"@types/zen-observable": "^0.8.0",
|
||||
"apollo-cache": "1.1.17",
|
||||
"apollo-link": "1.2.3",
|
||||
"apollo-link-dedup": "1.0.10",
|
||||
"apollo-link": "^1.0.0",
|
||||
"apollo-link-dedup": "^1.0.0",
|
||||
"apollo-utilities": "1.0.21",
|
||||
"symbol-observable": "1.2.0",
|
||||
"zen-observable": "0.8.9"
|
||||
"symbol-observable": "^1.0.2",
|
||||
"zen-observable": "^0.8.0"
|
||||
}
|
||||
},
|
||||
"apollo-link": {
|
||||
@@ -156,8 +156,8 @@
|
||||
"resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.3.tgz",
|
||||
"integrity": "sha512-iL9yS2OfxYhigme5bpTbmRyC+Htt6tyo2fRMHT3K1XRL/C5IQDDz37OjpPy4ndx7WInSvfSZaaOTKFja9VWqSw==",
|
||||
"requires": {
|
||||
"apollo-utilities": "1.0.21",
|
||||
"zen-observable-ts": "0.8.10"
|
||||
"apollo-utilities": "^1.0.0",
|
||||
"zen-observable-ts": "^0.8.10"
|
||||
}
|
||||
},
|
||||
"apollo-link-dedup": {
|
||||
@@ -165,7 +165,7 @@
|
||||
"resolved": "https://registry.npmjs.org/apollo-link-dedup/-/apollo-link-dedup-1.0.10.tgz",
|
||||
"integrity": "sha512-tpUI9lMZsidxdNygSY1FxflXEkUZnvKRkMUsXXuQUNoSLeNtEvUX7QtKRAl4k9ubLl8JKKc9X3L3onAFeGTK8w==",
|
||||
"requires": {
|
||||
"apollo-link": "1.2.3"
|
||||
"apollo-link": "^1.2.3"
|
||||
}
|
||||
},
|
||||
"apollo-utilities": {
|
||||
@@ -173,8 +173,8 @@
|
||||
"resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.0.21.tgz",
|
||||
"integrity": "sha512-ZcxELlEl+sDCYBgEMdNXJAsZtRVm8wk4HIA58bMsqYfd1DSAJQEtZ93F0GZgYNAGy3QyaoBeZtbb0/01++G8JQ==",
|
||||
"requires": {
|
||||
"fast-json-stable-stringify": "2.0.0",
|
||||
"fclone": "1.0.11"
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"fclone": "^1.0.11"
|
||||
}
|
||||
},
|
||||
"asap": {
|
||||
@@ -207,7 +207,7 @@
|
||||
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-0.3.8.tgz",
|
||||
"integrity": "sha1-ZCHP0wNM5mT+dnOXL9ARn8KJQfo=",
|
||||
"requires": {
|
||||
"is-in-browser": "1.1.3"
|
||||
"is-in-browser": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"csstype": {
|
||||
@@ -241,7 +241,7 @@
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
|
||||
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
|
||||
"requires": {
|
||||
"iconv-lite": "0.4.24"
|
||||
"iconv-lite": "~0.4.13"
|
||||
}
|
||||
},
|
||||
"fast-json-stable-stringify": {
|
||||
@@ -260,13 +260,13 @@
|
||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz",
|
||||
"integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=",
|
||||
"requires": {
|
||||
"core-js": "1.2.7",
|
||||
"isomorphic-fetch": "2.2.1",
|
||||
"loose-envify": "1.4.0",
|
||||
"object-assign": "4.1.1",
|
||||
"promise": "7.3.1",
|
||||
"setimmediate": "1.0.5",
|
||||
"ua-parser-js": "0.7.18"
|
||||
"core-js": "^1.0.0",
|
||||
"isomorphic-fetch": "^2.1.1",
|
||||
"loose-envify": "^1.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"promise": "^7.1.1",
|
||||
"setimmediate": "^1.0.5",
|
||||
"ua-parser-js": "^0.7.18"
|
||||
}
|
||||
},
|
||||
"fbjs-css-vars": {
|
||||
@@ -285,8 +285,8 @@
|
||||
"integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"min-document": "2.19.0",
|
||||
"process": "0.5.2"
|
||||
"min-document": "^2.19.0",
|
||||
"process": "~0.5.1"
|
||||
}
|
||||
},
|
||||
"graphql": {
|
||||
@@ -294,7 +294,7 @@
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.2.tgz",
|
||||
"integrity": "sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==",
|
||||
"requires": {
|
||||
"iterall": "1.2.2"
|
||||
"iterall": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"graphql-tag": {
|
||||
@@ -307,11 +307,11 @@
|
||||
"resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz",
|
||||
"integrity": "sha512-1zkBRWW6XweO0NBcjiphtVJVsIQ+SXF29z9DVkceeaSLVMFXHool+fdCZD4spDCfZJCILPILc3bm7Bc+HRi0nA==",
|
||||
"requires": {
|
||||
"invariant": "2.2.4",
|
||||
"loose-envify": "1.4.0",
|
||||
"resolve-pathname": "2.2.0",
|
||||
"value-equal": "0.4.0",
|
||||
"warning": "3.0.0"
|
||||
"invariant": "^2.2.1",
|
||||
"loose-envify": "^1.2.0",
|
||||
"resolve-pathname": "^2.2.0",
|
||||
"value-equal": "^0.4.0",
|
||||
"warning": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"warning": {
|
||||
@@ -319,7 +319,7 @@
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
|
||||
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
|
||||
"requires": {
|
||||
"loose-envify": "1.4.0"
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,7 +339,7 @@
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"requires": {
|
||||
"safer-buffer": "2.1.2"
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"indefinite-observable": {
|
||||
@@ -362,7 +362,7 @@
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||
"requires": {
|
||||
"loose-envify": "1.4.0"
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-in-browser": {
|
||||
@@ -375,7 +375,7 @@
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
|
||||
"requires": {
|
||||
"isobject": "3.0.1"
|
||||
"isobject": "^3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"isobject": {
|
||||
@@ -395,8 +395,8 @@
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
|
||||
"integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
|
||||
"requires": {
|
||||
"node-fetch": "1.7.3",
|
||||
"whatwg-fetch": "2.0.4"
|
||||
"node-fetch": "^1.0.1",
|
||||
"whatwg-fetch": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"iterall": {
|
||||
@@ -414,9 +414,9 @@
|
||||
"resolved": "https://registry.npmjs.org/jss/-/jss-9.8.7.tgz",
|
||||
"integrity": "sha512-awj3XRZYxbrmmrx9LUSj5pXSUfm12m8xzi/VKeqI1ZwWBtQ0kVPTs3vYs32t4rFw83CgFDukA8wKzOE9sMQnoQ==",
|
||||
"requires": {
|
||||
"is-in-browser": "1.1.3",
|
||||
"symbol-observable": "1.2.0",
|
||||
"warning": "3.0.0"
|
||||
"is-in-browser": "^1.1.3",
|
||||
"symbol-observable": "^1.1.0",
|
||||
"warning": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"warning": {
|
||||
@@ -424,7 +424,7 @@
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
|
||||
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
|
||||
"requires": {
|
||||
"loose-envify": "1.4.0"
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -434,7 +434,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jss-camel-case/-/jss-camel-case-6.1.0.tgz",
|
||||
"integrity": "sha512-HPF2Q7wmNW1t79mCqSeU2vdd/vFFGpkazwvfHMOhPlMgXrJDzdj9viA2SaHk9ZbD5pfL63a8ylp4++irYbbzMQ==",
|
||||
"requires": {
|
||||
"hyphenate-style-name": "1.0.2"
|
||||
"hyphenate-style-name": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"jss-default-unit": {
|
||||
@@ -452,7 +452,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jss-nested/-/jss-nested-6.0.1.tgz",
|
||||
"integrity": "sha512-rn964TralHOZxoyEgeq3hXY8hyuCElnvQoVrQwKHVmu55VRDd6IqExAx9be5HgK0yN/+hQdgAXQl/GUrBbbSTA==",
|
||||
"requires": {
|
||||
"warning": "3.0.0"
|
||||
"warning": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"warning": {
|
||||
@@ -460,7 +460,7 @@
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
|
||||
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
|
||||
"requires": {
|
||||
"loose-envify": "1.4.0"
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -475,7 +475,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jss-vendor-prefixer/-/jss-vendor-prefixer-7.0.0.tgz",
|
||||
"integrity": "sha512-Agd+FKmvsI0HLcYXkvy8GYOw3AAASBUpsmIRvVQheps+JWaN892uFOInTr0DRydwaD91vSSUCU4NssschvF7MA==",
|
||||
"requires": {
|
||||
"css-vendor": "0.3.8"
|
||||
"css-vendor": "^0.3.8"
|
||||
}
|
||||
},
|
||||
"keycode": {
|
||||
@@ -498,7 +498,7 @@
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"requires": {
|
||||
"js-tokens": "4.0.0"
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"luxon": {
|
||||
@@ -511,12 +511,12 @@
|
||||
"resolved": "https://registry.npmjs.org/material-ui-pickers/-/material-ui-pickers-1.0.1.tgz",
|
||||
"integrity": "sha512-FRJwgeFUQdQxzNAybZvf11204KFBxqKS6sg4JvAO0cWmHULXJhrc9kuEDT8F7Pomwnv6+AyfxygsoMgIRcBMNg==",
|
||||
"requires": {
|
||||
"classnames": "2.2.6",
|
||||
"keycode": "2.2.0",
|
||||
"react-event-listener": "0.6.4",
|
||||
"react-text-mask": "5.4.1",
|
||||
"react-transition-group": "2.5.0",
|
||||
"tslib": "1.9.3"
|
||||
"classnames": "^2.2.6",
|
||||
"keycode": "^2.2.0",
|
||||
"react-event-listener": "^0.6.4",
|
||||
"react-text-mask": "=5.4.1",
|
||||
"react-transition-group": "^2.5.0",
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"min-document": {
|
||||
@@ -525,7 +525,7 @@
|
||||
"integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dom-walk": "0.1.1"
|
||||
"dom-walk": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
@@ -533,8 +533,8 @@
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
|
||||
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
|
||||
"requires": {
|
||||
"encoding": "0.1.12",
|
||||
"is-stream": "1.1.0"
|
||||
"encoding": "^0.1.11",
|
||||
"is-stream": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"normalize-scroll-left": {
|
||||
@@ -578,7 +578,7 @@
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||
"requires": {
|
||||
"asap": "2.0.6"
|
||||
"asap": "~2.0.3"
|
||||
}
|
||||
},
|
||||
"prop-types": {
|
||||
@@ -586,8 +586,8 @@
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz",
|
||||
"integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==",
|
||||
"requires": {
|
||||
"loose-envify": "1.4.0",
|
||||
"object-assign": "4.1.1"
|
||||
"loose-envify": "^1.3.1",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"react": {
|
||||
@@ -595,10 +595,10 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.6.1.tgz",
|
||||
"integrity": "sha512-OtawJThYlvRgm9BXK+xTL7BIlDx8vv21j+fbQDjRRUyok6y7NyjlweGorielTahLZHYIdKUoK2Dp9ByVWuMqxw==",
|
||||
"requires": {
|
||||
"loose-envify": "1.4.0",
|
||||
"object-assign": "4.1.1",
|
||||
"prop-types": "15.6.2",
|
||||
"scheduler": "0.11.0"
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"react-apollo": {
|
||||
@@ -606,12 +606,12 @@
|
||||
"resolved": "https://registry.npmjs.org/react-apollo/-/react-apollo-2.2.4.tgz",
|
||||
"integrity": "sha512-haS5R30Qvteb65ZLfWomUZQh47VU4ld4Kof3zlqdbLOrYPt3/DdVZC8ZFPZSxd5zPeIJtZqpUfAxD1WHVoMPIA==",
|
||||
"requires": {
|
||||
"fbjs": "1.0.0",
|
||||
"hoist-non-react-statics": "3.0.1",
|
||||
"invariant": "2.2.4",
|
||||
"lodash.flowright": "3.5.0",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"prop-types": "15.6.2"
|
||||
"fbjs": "^1.0.0",
|
||||
"hoist-non-react-statics": "^3.0.0",
|
||||
"invariant": "^2.2.2",
|
||||
"lodash.flowright": "^3.5.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"prop-types": "^15.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
@@ -624,14 +624,14 @@
|
||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-1.0.0.tgz",
|
||||
"integrity": "sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA==",
|
||||
"requires": {
|
||||
"core-js": "2.5.7",
|
||||
"fbjs-css-vars": "1.0.1",
|
||||
"isomorphic-fetch": "2.2.1",
|
||||
"loose-envify": "1.4.0",
|
||||
"object-assign": "4.1.1",
|
||||
"promise": "7.3.1",
|
||||
"setimmediate": "1.0.5",
|
||||
"ua-parser-js": "0.7.18"
|
||||
"core-js": "^2.4.1",
|
||||
"fbjs-css-vars": "^1.0.0",
|
||||
"isomorphic-fetch": "^2.1.1",
|
||||
"loose-envify": "^1.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"promise": "^7.1.1",
|
||||
"setimmediate": "^1.0.5",
|
||||
"ua-parser-js": "^0.7.18"
|
||||
}
|
||||
},
|
||||
"hoist-non-react-statics": {
|
||||
@@ -639,7 +639,7 @@
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.0.1.tgz",
|
||||
"integrity": "sha512-1kXwPsOi0OGQIZNVMPvgWJ9tSnGMiMfJdihqEzrPEXlHOBh9AAHXX/QYmAJTXztnz/K+PQ8ryCb4eGaN6HlGbQ==",
|
||||
"requires": {
|
||||
"react-is": "16.5.2"
|
||||
"react-is": "^16.3.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -649,10 +649,10 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.1.tgz",
|
||||
"integrity": "sha512-zm+wBuEMGm009Wt1uE4Zw5KcXOW7qC4E/xW/fpJsCsbOco4U/R84u+DzzO/S4SYSdNBcqcaulcp4w3FXl8pImw==",
|
||||
"requires": {
|
||||
"loose-envify": "1.4.0",
|
||||
"object-assign": "4.1.1",
|
||||
"prop-types": "15.6.2",
|
||||
"scheduler": "0.11.0"
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"react-event-listener": {
|
||||
@@ -661,8 +661,8 @@
|
||||
"integrity": "sha512-t7VSjIuUFmN+GeyKb+wm025YLeojVB85kJL6sSs0wEBJddfmKBEQz+CNBZ2zBLKVWkPy/fZXM6U5yvojjYBVYQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "7.0.0",
|
||||
"prop-types": "15.6.2",
|
||||
"warning": "4.0.2"
|
||||
"prop-types": "^15.6.0",
|
||||
"warning": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"react-hot-loader": {
|
||||
@@ -671,12 +671,12 @@
|
||||
"integrity": "sha512-T0G5jURyTsFLoiW6MTr5Q35UHC/B2pmYJ7+VBjk8yMDCEABRmCGy4g6QwxoB4pWg4/xYvVTa/Pbqnsgx/+NLuA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-levenshtein": "2.0.6",
|
||||
"global": "4.3.2",
|
||||
"hoist-non-react-statics": "2.5.5",
|
||||
"prop-types": "15.6.2",
|
||||
"react-lifecycles-compat": "3.0.4",
|
||||
"shallowequal": "1.1.0"
|
||||
"fast-levenshtein": "^2.0.6",
|
||||
"global": "^4.3.0",
|
||||
"hoist-non-react-statics": "^2.5.0",
|
||||
"prop-types": "^15.6.1",
|
||||
"react-lifecycles-compat": "^3.0.4",
|
||||
"shallowequal": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
@@ -694,7 +694,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-4.0.6.tgz",
|
||||
"integrity": "sha512-WfBlqYRD0fGZpQxRy1apxsPeaqtxhzo7CnUVFKBoGDsQbc89+DQNhjG/D3IxgGYM47oKB+W/Su9ol/vVS6Wc2Q==",
|
||||
"requires": {
|
||||
"prop-types": "15.6.2"
|
||||
"prop-types": "^15.6.0"
|
||||
}
|
||||
},
|
||||
"react-router": {
|
||||
@@ -702,13 +702,13 @@
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-4.3.1.tgz",
|
||||
"integrity": "sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg==",
|
||||
"requires": {
|
||||
"history": "4.7.2",
|
||||
"hoist-non-react-statics": "2.5.5",
|
||||
"invariant": "2.2.4",
|
||||
"loose-envify": "1.4.0",
|
||||
"path-to-regexp": "1.7.0",
|
||||
"prop-types": "15.6.2",
|
||||
"warning": "4.0.2"
|
||||
"history": "^4.7.2",
|
||||
"hoist-non-react-statics": "^2.5.0",
|
||||
"invariant": "^2.2.4",
|
||||
"loose-envify": "^1.3.1",
|
||||
"path-to-regexp": "^1.7.0",
|
||||
"prop-types": "^15.6.1",
|
||||
"warning": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"react-router-dom": {
|
||||
@@ -716,12 +716,12 @@
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-4.3.1.tgz",
|
||||
"integrity": "sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA==",
|
||||
"requires": {
|
||||
"history": "4.7.2",
|
||||
"invariant": "2.2.4",
|
||||
"loose-envify": "1.4.0",
|
||||
"prop-types": "15.6.2",
|
||||
"react-router": "4.3.1",
|
||||
"warning": "4.0.2"
|
||||
"history": "^4.7.2",
|
||||
"invariant": "^2.2.4",
|
||||
"loose-envify": "^1.3.1",
|
||||
"prop-types": "^15.6.1",
|
||||
"react-router": "^4.3.1",
|
||||
"warning": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"react-text-mask": {
|
||||
@@ -729,7 +729,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-text-mask/-/react-text-mask-5.4.1.tgz",
|
||||
"integrity": "sha512-mGx1n0x0skktwsKcpgAGnrgExFlXDflbWEHh8PC1UaQP8+EdHkW7JYczTghsBo2rnVwU9/4A9x/2bVt3HY46/w==",
|
||||
"requires": {
|
||||
"prop-types": "15.6.2"
|
||||
"prop-types": "^15.5.6"
|
||||
}
|
||||
},
|
||||
"react-transition-group": {
|
||||
@@ -737,10 +737,10 @@
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.0.tgz",
|
||||
"integrity": "sha512-qYB3JBF+9Y4sE4/Mg/9O6WFpdoYjeeYqx0AFb64PTazVy8RPMiE3A47CG9QmM4WJ/mzDiZYslV+Uly6O1Erlgw==",
|
||||
"requires": {
|
||||
"dom-helpers": "3.3.1",
|
||||
"loose-envify": "1.4.0",
|
||||
"prop-types": "15.6.2",
|
||||
"react-lifecycles-compat": "3.0.4"
|
||||
"dom-helpers": "^3.3.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"recompose": {
|
||||
@@ -748,12 +748,12 @@
|
||||
"resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz",
|
||||
"integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "7.0.0",
|
||||
"change-emitter": "0.1.6",
|
||||
"fbjs": "0.8.17",
|
||||
"hoist-non-react-statics": "2.5.5",
|
||||
"react-lifecycles-compat": "3.0.4",
|
||||
"symbol-observable": "1.2.0"
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"change-emitter": "^0.1.2",
|
||||
"fbjs": "^0.8.1",
|
||||
"hoist-non-react-statics": "^2.3.1",
|
||||
"react-lifecycles-compat": "^3.0.2",
|
||||
"symbol-observable": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
@@ -776,8 +776,8 @@
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.11.0.tgz",
|
||||
"integrity": "sha512-MAYbBfmiEHxF0W+c4CxMpEqMYK+rYF584VP/qMKSiHM6lTkBKKYOJaDiSILpJHla6hBOsVd6GucPL46o2Uq3sg==",
|
||||
"requires": {
|
||||
"loose-envify": "1.4.0",
|
||||
"object-assign": "4.1.1"
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"setimmediate": {
|
||||
@@ -816,7 +816,7 @@
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz",
|
||||
"integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==",
|
||||
"requires": {
|
||||
"loose-envify": "1.4.0"
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"whatwg-fetch": {
|
||||
@@ -834,7 +834,7 @@
|
||||
"resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.10.tgz",
|
||||
"integrity": "sha512-5vqMtRggU/2GhePC9OU4sYEWOdvmayp2k3gjPf4F0mXwB3CSbbNznfDUvDJx9O2ZTa1EIXdJhPchQveFKwNXPQ==",
|
||||
"requires": {
|
||||
"zen-observable": "0.8.9"
|
||||
"zen-observable": "^0.8.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@superalgos/operations-client",
|
||||
"version": "0.0.13",
|
||||
"version": "0.0.31",
|
||||
"description": "Superalgos Operations client-side web app",
|
||||
"main": "src/index.js",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { clone } from '../Fragments';
|
||||
import gql from 'graphql-tag'
|
||||
import { clone } from '../Fragments'
|
||||
|
||||
const OPERATIONS_ADD_CLONE = gql`
|
||||
mutation Operations_AddClone($clone: operations_CloneInput!) {
|
||||
@@ -8,35 +8,35 @@ const OPERATIONS_ADD_CLONE = gql`
|
||||
}
|
||||
}
|
||||
${clone}
|
||||
`;
|
||||
`
|
||||
|
||||
const OPERATIONS_LIST_CLONES = gql`
|
||||
query Operations_Clones{
|
||||
operations_Clones {
|
||||
operations_Clones(queryLogs: true) {
|
||||
...clone
|
||||
}
|
||||
}
|
||||
${clone}
|
||||
`;
|
||||
`
|
||||
|
||||
const OPERATIONS_HISTORY_CLONES = gql`
|
||||
query Operations_HistoryClones{
|
||||
operations_HistoryClones {
|
||||
operations_HistoryClones(botType: "Trading") {
|
||||
...clone
|
||||
}
|
||||
}
|
||||
${clone}
|
||||
`;
|
||||
`
|
||||
|
||||
const OPERATIONS_REMOVE_CLONE = gql`
|
||||
mutation Operations_RemoveClone($id: ID!){
|
||||
operations_RemoveClone( id: $id)
|
||||
}
|
||||
`;
|
||||
`
|
||||
|
||||
export default {
|
||||
OPERATIONS_LIST_CLONES,
|
||||
OPERATIONS_HISTORY_CLONES,
|
||||
OPERATIONS_ADD_CLONE,
|
||||
OPERATIONS_REMOVE_CLONE
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import clones from './clones';
|
||||
import teams from './teams';
|
||||
import clones from './clones'
|
||||
import teams from './teams'
|
||||
import keys from './keys'
|
||||
|
||||
export {
|
||||
clones,
|
||||
teams
|
||||
};
|
||||
teams,
|
||||
keys
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const GET_ALL_KEYS = gql`
|
||||
query {
|
||||
keyVault_AvailableKeys {
|
||||
id
|
||||
key
|
||||
description
|
||||
exchange
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default {
|
||||
GET_ALL_KEYS
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import gql from 'graphql-tag';
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const GET_ALL_TEAMS_QUERY = gql`
|
||||
query {
|
||||
@@ -18,4 +18,4 @@ export const GET_ALL_TEAMS_QUERY = gql`
|
||||
|
||||
export default {
|
||||
GET_ALL_TEAMS_QUERY
|
||||
};
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const clone = gql`
|
||||
fragment clone on operations_Clone {
|
||||
id
|
||||
authId
|
||||
teamId
|
||||
botId
|
||||
mode
|
||||
resumeExecution
|
||||
beginDatetime
|
||||
endDatetime
|
||||
waitTime
|
||||
state
|
||||
stateDatetime
|
||||
createDatetime
|
||||
lastLogs
|
||||
runAsTeam
|
||||
summaryDate
|
||||
buyAverage
|
||||
sellAverage
|
||||
marketRate
|
||||
combinedProfitsA
|
||||
combinedProfitsB
|
||||
assetA
|
||||
assetB
|
||||
botType
|
||||
teamName
|
||||
botName
|
||||
botAvatar
|
||||
teamAvatar
|
||||
processName
|
||||
}
|
||||
`;
|
||||
@@ -1,6 +1,36 @@
|
||||
import { clone } from './clone'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
|
||||
export {
|
||||
clone
|
||||
};
|
||||
export const clone = gql`
|
||||
fragment clone on operations_Clone {
|
||||
id
|
||||
authId
|
||||
teamId
|
||||
botId
|
||||
mode
|
||||
resumeExecution
|
||||
beginDatetime
|
||||
endDatetime
|
||||
waitTime
|
||||
state
|
||||
stateDatetime
|
||||
createDatetime
|
||||
lastLogs
|
||||
runAsTeam
|
||||
summaryDate
|
||||
buyAverage
|
||||
sellAverage
|
||||
marketRate
|
||||
combinedProfitsA
|
||||
combinedProfitsB
|
||||
assetA
|
||||
assetB
|
||||
botType
|
||||
teamName
|
||||
botName
|
||||
botAvatar
|
||||
teamAvatar
|
||||
processName
|
||||
keyId
|
||||
botSlug
|
||||
}
|
||||
`
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,25 +1,63 @@
|
||||
const availableMonths = ["January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"]
|
||||
const availableMonths = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
]
|
||||
|
||||
const tradingStartModes ={
|
||||
live: "Live",
|
||||
competition: "Competition",
|
||||
backtest: "Backtest"
|
||||
const availableTimePeriods = ["24-hs", "12-hs", "08-hs", "06-hs", "04-hs", "03-hs", "02-hs", "01-hs", "45-min", "40-min", "30-min", "20-min", "15-min", "10-min", "05-min", "04-min", "03-min", "02-min", "01-min"]
|
||||
|
||||
const tradingStartModes = {
|
||||
live: 'Live',
|
||||
backtest: 'Backtest'
|
||||
}
|
||||
|
||||
const tradingStartModesList = {
|
||||
live: 'Live',
|
||||
backtest: 'Backtest',
|
||||
competition: 'Competition'
|
||||
}
|
||||
|
||||
const indicatorStartModes = {
|
||||
allMonths: "All Months",
|
||||
oneMonth: "One Month",
|
||||
noTime: "No Time"
|
||||
allMonths: 'All Months',
|
||||
oneMonth: 'One Month',
|
||||
noTime: 'Head of the Market'
|
||||
}
|
||||
|
||||
const exchanges = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Poloniex'
|
||||
}
|
||||
]
|
||||
const sensorProcessNames = {
|
||||
Live: "Live-Trades",
|
||||
HoleFixing: "Hole-Fixing"
|
||||
}
|
||||
|
||||
const indicatorProcessNames = {
|
||||
Daily: 'Multi-Period-Daily',
|
||||
Market: 'Multi-Period-Market'
|
||||
}
|
||||
|
||||
const tradingProcessNames = {
|
||||
TradingProcess: "Trading-Process"
|
||||
}
|
||||
|
||||
export { tradingStartModes, exchanges, indicatorStartModes, availableMonths }
|
||||
const botTypes = {
|
||||
Trading: "Trading",
|
||||
Indicator: "Indicator",
|
||||
Sensor: "Sensor"
|
||||
}
|
||||
|
||||
const exchanges = {
|
||||
Coss: "Coss",
|
||||
Poloniex: "Poloniex"
|
||||
}
|
||||
|
||||
export {
|
||||
tradingStartModes, tradingStartModesList, exchanges, indicatorStartModes, availableMonths,
|
||||
availableTimePeriods, sensorProcessNames, indicatorProcessNames, tradingProcessNames, botTypes
|
||||
}
|
||||
+5
-7
@@ -1,9 +1,7 @@
|
||||
import React, { Component } from 'react'
|
||||
import React from 'react'
|
||||
import { Route, Switch } from 'react-router-dom'
|
||||
|
||||
import { MuiPickersUtilsProvider } from 'material-ui-pickers'
|
||||
import LuxonUtils from 'material-ui-pickers/utils/luxon-utils'
|
||||
|
||||
import BrowseClones from './views/Clone/List'
|
||||
import HistoryClones from './views/Clone/History'
|
||||
import AddClone from './views/Clone/Add'
|
||||
@@ -12,10 +10,10 @@ import Home from './views/Home'
|
||||
const App = () => (
|
||||
<MuiPickersUtilsProvider utils={LuxonUtils}>
|
||||
<Switch>
|
||||
<Route exact path='/operations/' component={Home} />
|
||||
<Route path='/operations/browse' component={BrowseClones} />
|
||||
<Route path='/operations/history' component={HistoryClones} />
|
||||
<Route path='/operations/add' component={AddClone} />
|
||||
<Route exact path='/clones/' component={Home} />
|
||||
<Route path='/clones/browse' component={BrowseClones} />
|
||||
<Route path='/clones/history' component={HistoryClones} />
|
||||
<Route path='/clones/add' component={AddClone} />
|
||||
</Switch>
|
||||
</MuiPickersUtilsProvider>
|
||||
)
|
||||
|
||||
@@ -10,13 +10,13 @@ export function toLocalTime (epoch) {
|
||||
}
|
||||
|
||||
export function toUTCTimestamp (localDate) {
|
||||
if(isDefined(localDate)){
|
||||
if (isDefined(localDate)) {
|
||||
const date = new Date(localDate.valueOf())
|
||||
const utcDate = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(),
|
||||
date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(),
|
||||
date.getUTCSeconds())
|
||||
|
||||
return utcDate.valueOf() / 1000|0
|
||||
return utcDate.valueOf() / 1000 | 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ export function getIndicatorYears () {
|
||||
const localDate = new Date()
|
||||
let availableYears = []
|
||||
for (var i = 2015; i <= localDate.getUTCFullYear(); i++) {
|
||||
availableYears[i-2015] = i
|
||||
availableYears[i - 2015] = i
|
||||
}
|
||||
return availableYears
|
||||
}
|
||||
+334
-301
@@ -1,40 +1,34 @@
|
||||
import React, { Component } from 'react'
|
||||
import { graphql, compose, Query } from 'react-apollo'
|
||||
import { clones, teams } from '../../../GraphQL/Calls'
|
||||
import { clones, teams, keys } from '../../../GraphQL/Calls'
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import TopBar from '../../BannerTopBar'
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import classNames from 'classnames'
|
||||
import styles from './styles'
|
||||
|
||||
import { tradingStartModes, indicatorStartModes, availableMonths, processNames } from '../../../GraphQL/models'
|
||||
import { isDefined, getIndicatorYears } from '../../../utils'
|
||||
import {
|
||||
tradingStartModes, indicatorStartModes, availableMonths, availableTimePeriods,
|
||||
sensorProcessNames, indicatorProcessNames, tradingProcessNames, botTypes, exchanges
|
||||
} from '../../../GraphQL/models'
|
||||
|
||||
import {
|
||||
MenuItem, Button, TextField, FormControl, InputLabel, Input, Typography,
|
||||
Paper, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions,
|
||||
Select, FormControlLabel, Checkbox, FormHelperText
|
||||
MenuItem, Button, TextField, FormControl, InputLabel, Input, Typography,
|
||||
Paper, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions,
|
||||
Select, FormControlLabel, Checkbox, FormHelperText
|
||||
} from '@material-ui/core'
|
||||
|
||||
import NumberFormat from 'react-number-format'
|
||||
|
||||
import { DateTimePicker } from 'material-ui-pickers'
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
} from '@material-ui/icons'
|
||||
import { ChevronLeft, ChevronRight } from '@material-ui/icons'
|
||||
|
||||
class AddClone extends Component {
|
||||
|
||||
constructor(props){
|
||||
constructor(props) {
|
||||
super(props)
|
||||
let user = localStorage.getItem('user')
|
||||
this.state = {
|
||||
user: JSON.parse(user),
|
||||
selectedBot: {'id':''},
|
||||
mode: '',
|
||||
selectedBot: { 'id': '' },
|
||||
mode: "noTime",
|
||||
resumeExecution: false,
|
||||
beginDatetime: DateTime.local().minus({ days: 8 }).startOf('day'),
|
||||
endDatetime: DateTime.local(),
|
||||
@@ -45,12 +39,17 @@ class AddClone extends Component {
|
||||
runAsTeam: false,
|
||||
teams: [],
|
||||
teamId: '',
|
||||
keyId: '',
|
||||
exchangeName: exchanges.Coss,
|
||||
processName: indicatorProcessNames.Daily,
|
||||
|
||||
// Indicator Bot
|
||||
startYear: 2019,
|
||||
endYear: 2019,
|
||||
month: 1,
|
||||
processName: '',
|
||||
|
||||
//TradingBot
|
||||
timePeriod: '01-hs',
|
||||
|
||||
//Error handlers
|
||||
nameError: false,
|
||||
@@ -60,34 +59,35 @@ class AddClone extends Component {
|
||||
isNewCloneConfirmationOpen: false,
|
||||
serverResponse: '',
|
||||
serverError: false,
|
||||
processNameError: false
|
||||
processNameError: false,
|
||||
keyIdError: false,
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes } = this.props
|
||||
if( !isDefined(this.state.user) ) {
|
||||
if (!isDefined(this.state.user)) {
|
||||
return (
|
||||
<TopBar
|
||||
size='big'
|
||||
title='Create a bot clone'
|
||||
text="Please login to create a bot clone."
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else return (
|
||||
<React.Fragment>
|
||||
<TopBar
|
||||
size='medium'
|
||||
title='Create a bot clone'
|
||||
text='Create a bot clone here.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
/>
|
||||
<React.Fragment>
|
||||
<TopBar
|
||||
size='medium'
|
||||
title='Create a bot clone'
|
||||
text='Create a bot clone here.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
|
||||
<div className='container'>
|
||||
<div className='container'>
|
||||
<Paper className={classNames('container', classes.root)}>
|
||||
|
||||
<form noValidate autoComplete="off" onSubmit={this.submitForm.bind(this)}>
|
||||
<form noValidate autoComplete="off" onSubmit={this.submitForm.bind(this)}>
|
||||
<Typography className={classes.typography} variant='h5' gutterBottom>
|
||||
Create a Bot Clone
|
||||
</Typography>
|
||||
@@ -119,31 +119,31 @@ class AddClone extends Component {
|
||||
)
|
||||
}
|
||||
|
||||
if(!isDefined(data.teams_TeamsByOwner)){
|
||||
return (
|
||||
<Select
|
||||
value='noteams'
|
||||
input={<Input />}
|
||||
displayEmpty
|
||||
className={classes.selectEmpty}
|
||||
disabled
|
||||
>
|
||||
<MenuItem key='1' value='noteams'>You don't have any teams yet.</MenuItem>
|
||||
</Select>
|
||||
)
|
||||
} else{
|
||||
if (!isDefined(data.teams_TeamsByOwner)) {
|
||||
return (
|
||||
<Select
|
||||
value='noteams'
|
||||
input={<Input />}
|
||||
displayEmpty
|
||||
className={classes.selectEmpty}
|
||||
disabled
|
||||
>
|
||||
<MenuItem key='1' value='noteams'>You don't have any teams yet.</MenuItem>
|
||||
</Select>
|
||||
)
|
||||
} else {
|
||||
this.teams = data.teams_TeamsByOwner
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={this.state.selectedBot.id}
|
||||
displayEmpty
|
||||
className={classes.selectEmpty}
|
||||
onBlur={(e)=>this.setState({botError:false})}
|
||||
error={this.state.botError}
|
||||
onChange={e => this.setSelectedBot(e.target.value)}
|
||||
>
|
||||
<Select
|
||||
value={this.state.selectedBot.id}
|
||||
displayEmpty
|
||||
className={classes.selectEmpty}
|
||||
onBlur={(e) => this.setState({ botError: false })}
|
||||
error={this.state.botError}
|
||||
onChange={e => this.setSelectedBot(e.target.value)}
|
||||
>
|
||||
{
|
||||
data.teams_TeamsByOwner.map(team => (
|
||||
team.fb.map(financialBeing => (
|
||||
@@ -153,236 +153,262 @@ class AddClone extends Component {
|
||||
))
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
</Select>
|
||||
)
|
||||
}}
|
||||
</Query>
|
||||
<FormHelperText>{this.state.selectedBot.kind}</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
{ this.state.selectedBot.kind === "TRADER" &&
|
||||
<React.Fragment>
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
At the moment Live Trading Clones trade by default 0.001 BTC on Poloniex.
|
||||
</Typography>
|
||||
<TextField label="Exchange"
|
||||
select
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.exchangeName}
|
||||
onChange={(e) => this.setState({ exchangeName: e.target.value })}
|
||||
fullWidth
|
||||
>
|
||||
{Object.keys(exchanges).map((option, index) => (
|
||||
<MenuItem key={index} value={option}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
select
|
||||
label="Running Mode"
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.mode}
|
||||
onChange={(e)=> this.setState({mode:e.target.value})}
|
||||
onBlur={(e)=>this.setState({modeError:false})}
|
||||
error={this.state.modeError}
|
||||
fullWidth
|
||||
>
|
||||
{Object.keys(tradingStartModes).map(option => (
|
||||
<MenuItem key={option} value={option}>
|
||||
{tradingStartModes[option]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
{this.state.selectedBot.kind === botTypes.Trading &&
|
||||
<React.Fragment>
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
Available Running Modes are Backtest and Live.
|
||||
<ul>
|
||||
<li> Backtest mode does NOT put real orders at the exchange;
|
||||
it simulates the order execution at the specified price.
|
||||
</li>
|
||||
<li>
|
||||
Live mode DOES put real orders at the exchange.
|
||||
</li>
|
||||
</ul>
|
||||
</Typography>
|
||||
|
||||
{ this.state.mode == "backtest" &&
|
||||
<React.Fragment>
|
||||
<DateTimePicker
|
||||
autoOk
|
||||
disableFuture
|
||||
format="DD' at 'HH:mm"
|
||||
ampm={false}
|
||||
showTabs={false}
|
||||
leftArrowIcon={<ChevronLeft />}
|
||||
rightArrowIcon={<ChevronRight />}
|
||||
value={this.state.beginDatetime}
|
||||
label='Backtest Start Date'
|
||||
onChange={newVal => this.setState({ beginDatetime: newVal })}
|
||||
className={classNames(classes.form, classes.textField)}
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
<DateTimePicker
|
||||
autoOk
|
||||
disableFuture
|
||||
format="DD' at 'HH:mm"
|
||||
ampm={false}
|
||||
showTabs={false}
|
||||
leftArrowIcon={<ChevronLeft />}
|
||||
rightArrowIcon={<ChevronRight />}
|
||||
value={this.state.endDatetime}
|
||||
label='Backtest End Date'
|
||||
onChange={newVal => this.setState({ endDatetime: newVal })}
|
||||
className={classNames(classes.form, classes.textField)}
|
||||
fullWidth
|
||||
/>
|
||||
<TextField label="Running Mode"
|
||||
select
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.mode}
|
||||
onChange={(e) => this.setState({ mode: e.target.value })}
|
||||
onBlur={(e) => this.setState({ modeError: false })}
|
||||
error={this.state.modeError}
|
||||
fullWidth
|
||||
>
|
||||
{Object.keys(tradingStartModes).map(option => (
|
||||
<MenuItem key={option} value={option}>
|
||||
{tradingStartModes[option]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
The Wait Time represent the number of miliseconds the bot will wait between executions.
|
||||
{this.state.mode == "backtest" &&
|
||||
<React.Fragment>
|
||||
<DateTimePicker
|
||||
autoOk
|
||||
disableFuture
|
||||
format="DD' at 'HH:mm"
|
||||
ampm={false}
|
||||
showTabs={false}
|
||||
leftArrowIcon={<ChevronLeft />}
|
||||
rightArrowIcon={<ChevronRight />}
|
||||
value={this.state.beginDatetime}
|
||||
label='Backtest Start Date'
|
||||
onChange={newVal => this.setState({ beginDatetime: newVal })}
|
||||
className={classNames(classes.form, classes.textField)}
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
<DateTimePicker
|
||||
autoOk
|
||||
disableFuture
|
||||
format="DD' at 'HH:mm"
|
||||
ampm={false}
|
||||
showTabs={false}
|
||||
leftArrowIcon={<ChevronLeft />}
|
||||
rightArrowIcon={<ChevronRight />}
|
||||
value={this.state.endDatetime}
|
||||
label='Backtest End Date'
|
||||
onChange={newVal => this.setState({ endDatetime: newVal })}
|
||||
className={classNames(classes.form, classes.textField)}
|
||||
fullWidth
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{this.state.mode == "live" &&
|
||||
<React.Fragment>
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
Select one of your available Exchange API Keys to be used by this clone.
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
id="waitTime"
|
||||
label="Wait Time"
|
||||
className={classNames(classes.form, classes.textField)}
|
||||
value={this.state.waitTime}
|
||||
onChange={(e)=>this.setState({waitTime:e.target.value})}
|
||||
fullWidth
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
If Run as Team is selected, the clone will be taken from the
|
||||
team respository instead of your own team member folder.
|
||||
Competitions runs from the team respository.
|
||||
</Typography>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={this.state.runAsTeam}
|
||||
onChange={(e)=>this.setState({runAsTeam:e.target.checked })}
|
||||
value="runAsTeam"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Run as Team"
|
||||
className={classNames(classes.form, classes.textField)}
|
||||
/>
|
||||
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
The Resume Execution option let's you pick up the context of
|
||||
the last execution and continue from there.
|
||||
</Typography>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={this.state.resumeExecution}
|
||||
onChange={(e)=>this.setState({resumeExecution:e.target.checked })}
|
||||
value="resumeExecution"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Resume Execution"
|
||||
className={classNames(classes.form, classes.textField)}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{ (this.state.selectedBot.kind === "INDICATOR"
|
||||
|| this.state.selectedBot.kind === "EXTRACTOR") &&
|
||||
<React.Fragment>
|
||||
<TextField
|
||||
label="Process Name"
|
||||
className={classNames(classes.textField, classes.form)}
|
||||
value={this.state.processName}
|
||||
onChange={(e)=> this.setState({processName:e.target.value})}
|
||||
onBlur={(e)=>this.setState({processNameError:false})}
|
||||
error={this.state.processNameError}
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
<TextField
|
||||
select
|
||||
label="Running Mode"
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.mode}
|
||||
onChange={(e)=> this.setState({mode:e.target.value})}
|
||||
onBlur={(e)=>this.setState({modeError:false})}
|
||||
error={this.state.modeError}
|
||||
fullWidth
|
||||
>
|
||||
{Object.keys(indicatorStartModes).map(option => (
|
||||
<MenuItem key={option} value={option}>
|
||||
{indicatorStartModes[option]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
{ this.state.mode === "allMonths" &&
|
||||
<React.Fragment>
|
||||
<TextField
|
||||
id="beginYearInput"
|
||||
select
|
||||
label="Start Year"
|
||||
className={classNames(classes.textField, classes.form)}
|
||||
value={this.state.startYear}
|
||||
onChange={(e)=> this.setState({startYear:e.target.value})}
|
||||
fullWidth
|
||||
>
|
||||
{getIndicatorYears().map((option, index) => (
|
||||
<MenuItem key={option} value={index}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<Query
|
||||
query={keys.GET_ALL_KEYS}
|
||||
>
|
||||
{({ loading, error, data }) => {
|
||||
if (loading) return (
|
||||
<TextField
|
||||
id="endYearInput"
|
||||
select
|
||||
label="End Year"
|
||||
className={classNames(classes.textField, classes.form)}
|
||||
value={this.state.endYear}
|
||||
onChange={(e)=> this.setState({endYear:e.target.value})}
|
||||
fullWidth
|
||||
>
|
||||
{getIndicatorYears().map((option, index) => (
|
||||
<MenuItem key={option} value={index}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{ this.state.mode === "oneMonth" &&
|
||||
<React.Fragment>
|
||||
<TextField
|
||||
id="beginYearInput"
|
||||
select
|
||||
label="Year"
|
||||
className={classNames(classes.textField, classes.form)}
|
||||
value={this.state.startYear}
|
||||
onChange={(e)=> this.setState({startYear:e.target.value})}
|
||||
label="Exchange API Key"
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.keyId}
|
||||
onChange={(e) => this.setState({ keyId: e.target.value })}
|
||||
onBlur={(e) => this.setState({ keyIdError: false })}
|
||||
error={this.state.keyIdError}
|
||||
fullWidth
|
||||
>
|
||||
{getIndicatorYears().map((option, index) => (
|
||||
<MenuItem key={option} value={index}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
>
|
||||
<MenuItem key='1' value='Loading keys...'>
|
||||
'Loading keys...'
|
||||
</MenuItem>
|
||||
</TextField>
|
||||
|
||||
);
|
||||
if (error) return `Error! ${error.message}`;
|
||||
const list = data.keyVault_AvailableKeys.map((key) => (
|
||||
<MenuItem key={key.id} value={key.id}>
|
||||
{key.key.substr(0, 32) + "..." + " - " + key.exchange}
|
||||
</MenuItem>
|
||||
));
|
||||
return (
|
||||
<TextField
|
||||
id="endYearInput"
|
||||
select
|
||||
label="Month"
|
||||
className={classNames(classes.textField, classes.form)}
|
||||
value={this.state.month}
|
||||
onChange={(e)=> this.setState({month:e.target.value})}
|
||||
fullWidth
|
||||
>
|
||||
{availableMonths.map(option => (
|
||||
<MenuItem key={option} value={option}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</React.Fragment>
|
||||
}
|
||||
</React.Fragment>
|
||||
select
|
||||
label="Exchange API Key"
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.keyId}
|
||||
onChange={(e) => this.setState({ keyId: e.target.value })}
|
||||
onBlur={(e) => this.setState({ keyIdError: false })}
|
||||
error={this.state.keyIdError}
|
||||
fullWidth
|
||||
>
|
||||
{list}
|
||||
</TextField>
|
||||
);
|
||||
}}
|
||||
</Query>
|
||||
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
Because we are still in early alpha-testing phase,
|
||||
Live Trading Clones are limited to an initial investment
|
||||
of 0.001 BTC. If you choose to run this bot or any modified
|
||||
version of the code, you are doing it at your own risk.
|
||||
</Typography>
|
||||
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
The Time Period in which the bot will operate.
|
||||
</Typography>
|
||||
|
||||
<TextField label="Time Period"
|
||||
select
|
||||
className={classNames(classes.margin, classes.textField, classes.form)}
|
||||
value={this.state.timePeriod}
|
||||
onChange={(e) => this.setState({ timePeriod: e.target.value })}
|
||||
fullWidth
|
||||
>
|
||||
{availableTimePeriods.map((option, index) => (
|
||||
<MenuItem key={index} value={option}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{this.state.selectedBot.kind === botTypes.Indicator &&
|
||||
<React.Fragment>
|
||||
<TextField label="Process Name"
|
||||
select
|
||||
className={classNames(classes.textField, classes.form)}
|
||||
value={this.state.processName}
|
||||
onChange={(e) => this.setState({ processName: e.target.value })}
|
||||
onBlur={(e) => this.setState({ processNameError: false })}
|
||||
error={this.state.processNameError}
|
||||
fullWidth
|
||||
>
|
||||
{Object.keys(indicatorProcessNames).map((option, index) => (
|
||||
<MenuItem key={index} value={indicatorProcessNames[option]}>
|
||||
{indicatorProcessNames[option]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
You will be able to query your clone information under Active Clones until it finishes the execution, then it will moved to the History.
|
||||
{this.state.selectedBot.kind === botTypes.Sensor &&
|
||||
<React.Fragment>
|
||||
<TextField label="Process Name"
|
||||
select
|
||||
className={classNames(classes.textField, classes.form)}
|
||||
value={this.state.processName}
|
||||
onChange={(e) => this.setState({ processName: e.target.value })}
|
||||
onBlur={(e) => this.setState({ processNameError: false })}
|
||||
error={this.state.processNameError}
|
||||
fullWidth
|
||||
>
|
||||
{Object.keys(sensorProcessNames).map((option, index) => (
|
||||
<MenuItem key={index} value={sensorProcessNames[option]}>
|
||||
{sensorProcessNames[option]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
{(this.state.mode === "noTime" && this.state.selectedBot.kind !== botTypes.Sensor) &&
|
||||
<React.Fragment>
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
The Resume Execution option let's you pick up the context of
|
||||
the last execution and continue from there.
|
||||
</Typography>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={this.state.resumeExecution}
|
||||
onChange={(e) => this.setState({ resumeExecution: e.target.checked })}
|
||||
value="resumeExecution"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Resume Execution"
|
||||
className={classNames(classes.form, classes.textField)}
|
||||
/>
|
||||
{!this.state.resumeExecution &&
|
||||
<DateTimePicker
|
||||
autoOk
|
||||
disableFuture
|
||||
format="DD' at 'HH:mm"
|
||||
ampm={false}
|
||||
showTabs={false}
|
||||
leftArrowIcon={<ChevronLeft />}
|
||||
rightArrowIcon={<ChevronRight />}
|
||||
value={this.state.beginDatetime}
|
||||
label='Start Date'
|
||||
onChange={newVal => this.setState({ beginDatetime: newVal })}
|
||||
className={classNames(classes.form, classes.textField)}
|
||||
fullWidth
|
||||
/>
|
||||
}
|
||||
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
<Typography className={classes.typography} variant='subtitle1' align='justify'>
|
||||
You will be able to see the clone information under Active Clones.
|
||||
Whenever you decide to stop the clone you can delete it. On History Clones will find all deleted clones.
|
||||
</Typography>
|
||||
|
||||
<div className={classes.actionButton} >
|
||||
<Button
|
||||
type="submit"
|
||||
variant='contained' color='secondary'>
|
||||
Clone Bot!
|
||||
<Button
|
||||
type="submit"
|
||||
variant='contained' color='secondary'>
|
||||
Clone Bot!
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
@@ -399,24 +425,25 @@ class AddClone extends Component {
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.handleNewCloneConfirmationClose} color="primary" autoFocus>
|
||||
<Button onClick={this.handleNewCloneConfirmationClose}
|
||||
variant='contained' color='secondary' autoFocus>
|
||||
Ok
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Paper>
|
||||
</Paper>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
setSelectedBot(botId){
|
||||
if(isDefined(this.teams)){
|
||||
setSelectedBot(botId) {
|
||||
if (isDefined(this.teams)) {
|
||||
for (var j = 0; j < this.teams.length; j++) {
|
||||
for (var i = 0; i < this.teams[j].fb.length; i++) {
|
||||
if(this.teams[j].fb[i].id === botId){
|
||||
this.setState({selectedBot:this.teams[j].fb[i]})
|
||||
this.setState({teamId:this.teams[j].id})
|
||||
if (this.teams[j].fb[i].id === botId) {
|
||||
this.setState({ selectedBot: this.teams[j].fb[i] })
|
||||
this.setState({ teamId: this.teams[j].id })
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -424,16 +451,16 @@ class AddClone extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
async submitForm(e){
|
||||
async submitForm(e) {
|
||||
e.preventDefault()
|
||||
let error = this.validate()
|
||||
if (!error){
|
||||
if (!error) {
|
||||
let serverResponse = await this.createCloneOnServer()
|
||||
error = serverResponse.errors || !isDefined(serverResponse.data.operations_AddClone)
|
||||
if(error){
|
||||
this.state.serverResponse = serverResponse.errors[0].message
|
||||
if (error) {
|
||||
this.state.serverResponse = serverResponse.errors[0].message || serverResponse.errors[0]
|
||||
this.state.serverError = true
|
||||
}else{
|
||||
} else {
|
||||
this.state.serverResponse = "The new clone was sucessfully created."
|
||||
this.state.serverError = false
|
||||
}
|
||||
@@ -442,32 +469,36 @@ class AddClone extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
async createCloneOnServer(){
|
||||
async createCloneOnServer() {
|
||||
let variables = {
|
||||
clone:{
|
||||
clone: {
|
||||
teamId: this.state.teamId,
|
||||
botId: this.state.selectedBot.id,
|
||||
mode: this.state.mode,
|
||||
resumeExecution: this.state.resumeExecution,
|
||||
botType: this.getBotTypeFromKind(this.state.selectedBot.kind)
|
||||
botType: this.state.selectedBot.kind,
|
||||
exchangeName: this.state.exchangeName
|
||||
}
|
||||
}
|
||||
|
||||
if(this.state.mode === "backtest"){
|
||||
variables.clone.beginDatetime = this.state.beginDatetime.valueOf() / 1000|0
|
||||
variables.clone.endDatetime = this.state.endDatetime.valueOf() / 1000|0
|
||||
variables.clone.waitTime = this.state.waitTime
|
||||
}
|
||||
|
||||
if(this.state.selectedBot.kind === "TRADER"){
|
||||
if (this.state.selectedBot.kind === "Trading") {
|
||||
variables.clone.runAsTeam = this.state.runAsTeam
|
||||
variables.clone.processName = 'Trading-Process'
|
||||
variables.clone.timePeriod = this.state.timePeriod
|
||||
if (this.state.mode === "backtest") {
|
||||
variables.clone.beginDatetime = this.state.beginDatetime.valueOf() / 1000 | 0
|
||||
variables.clone.endDatetime = this.state.endDatetime.valueOf() / 1000 | 0
|
||||
variables.clone.waitTime = this.state.waitTime
|
||||
} else {
|
||||
variables.clone.keyId = this.state.keyId
|
||||
}
|
||||
} else {
|
||||
variables.clone.processName = this.state.processName
|
||||
variables.clone.startYear = this.state.startYear
|
||||
variables.clone.endYear = this.state.endYear
|
||||
variables.clone.month = this.state.month
|
||||
variables.clone.runAsTeam = true
|
||||
variables.clone.beginDatetime = this.state.beginDatetime.valueOf() / 1000 | 0
|
||||
}
|
||||
|
||||
return this.props.addCloneMutation({
|
||||
@@ -475,15 +506,6 @@ class AddClone extends Component {
|
||||
})
|
||||
}
|
||||
|
||||
getBotTypeFromKind(kind){
|
||||
if(kind === 'TRADER')
|
||||
return 'Trading'
|
||||
else if(kind === 'INDICATOR')
|
||||
return 'Indicator'
|
||||
else if(kind === 'EXTRACTOR')
|
||||
return 'Extraction'
|
||||
}
|
||||
|
||||
handleNewCloneConfirmationOpen = () => {
|
||||
this.setState({ isNewCloneConfirmationOpen: true })
|
||||
};
|
||||
@@ -493,21 +515,31 @@ class AddClone extends Component {
|
||||
isNewCloneConfirmationOpen: false
|
||||
})
|
||||
|
||||
if(!this.state.serverError)
|
||||
if (!this.state.serverError)
|
||||
this.setState({
|
||||
selectedBot: {'id':''},
|
||||
mode: '',
|
||||
selectedBot: { 'id': '' },
|
||||
mode: 'noTime',
|
||||
resumeExecution: false,
|
||||
beginDatetime: DateTime.local().minus({ days: 8 }).startOf('day'),
|
||||
endDatetime: DateTime.local(),
|
||||
waitTime: 1,
|
||||
state : '',
|
||||
state: '',
|
||||
stateDatetime: 0,
|
||||
createDatetime: 0,
|
||||
runAsTeam: false,
|
||||
processName: '',
|
||||
exchangeName: exchanges.Coss,
|
||||
processName: indicatorProcessNames.Daily,
|
||||
teams: [],
|
||||
teamId: '',
|
||||
keyId: '',
|
||||
|
||||
// Indicator Bot
|
||||
startYear: 2019,
|
||||
endYear: 2019,
|
||||
month: 1,
|
||||
|
||||
//TradingBot
|
||||
timePeriod: '01-hs',
|
||||
|
||||
//Error handlers
|
||||
nameError: false,
|
||||
@@ -517,25 +549,26 @@ class AddClone extends Component {
|
||||
isNewCloneConfirmationOpen: false,
|
||||
serverResponse: '',
|
||||
serverError: false,
|
||||
processNameError: false
|
||||
processNameError: false,
|
||||
keyIdError: false
|
||||
})
|
||||
};
|
||||
|
||||
validate(){
|
||||
validate() {
|
||||
let isError = false
|
||||
|
||||
if(this.state.selectedBot.length < 1) {
|
||||
if (this.state.selectedBot.length < 1) {
|
||||
isError = true
|
||||
this.setState(state => ({ selectedBotError: true }));
|
||||
}
|
||||
|
||||
if(this.state.mode.length < 1) {
|
||||
if (this.state.mode.length < 1) {
|
||||
isError = true
|
||||
this.setState(state => ({ modeError: true }));
|
||||
}
|
||||
|
||||
if(this.state.selectedBot.kind !== "TRADER"
|
||||
&& this.state.processName.length < 1 ) {
|
||||
if (this.state.selectedBot.kind !== "Trading"
|
||||
&& this.state.processName.length < 1) {
|
||||
isError = true
|
||||
this.setState(state => ({ processNameError: true }));
|
||||
}
|
||||
@@ -546,6 +579,6 @@ class AddClone extends Component {
|
||||
}
|
||||
|
||||
export default compose(
|
||||
graphql(clones.OPERATIONS_ADD_CLONE, { name:'addCloneMutation' }),
|
||||
graphql(clones.OPERATIONS_ADD_CLONE, { name: 'addCloneMutation' }),
|
||||
withStyles(styles)
|
||||
)(AddClone)
|
||||
@@ -14,7 +14,7 @@ const styles = theme => ({
|
||||
},
|
||||
textField: {
|
||||
width: '80%',
|
||||
marginLeft:'10%',
|
||||
marginLeft: '10%',
|
||||
marginBottom: 10
|
||||
},
|
||||
actionButton: {
|
||||
@@ -24,14 +24,14 @@ const styles = theme => ({
|
||||
typography: {
|
||||
width: '80%',
|
||||
marginLeft: '10%',
|
||||
marginTop: 40
|
||||
marginTop: 20
|
||||
},
|
||||
form: {
|
||||
marginTop: 20
|
||||
},
|
||||
selectEmpty: {
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
},
|
||||
});
|
||||
marginTop: theme.spacing.unit * 2
|
||||
}
|
||||
})
|
||||
|
||||
export default styles;
|
||||
export default styles
|
||||
@@ -2,12 +2,8 @@ import React, { Component } from 'react'
|
||||
import { graphql } from 'react-apollo'
|
||||
import { clones } from '../../../GraphQL/Calls'
|
||||
import TopBar from '../../BannerTopBar'
|
||||
//import CloneDialog from './CloneDialog'
|
||||
import ListClones from '../List/ListClones'
|
||||
|
||||
// Material-ui
|
||||
import { Typography, Paper } from '@material-ui/core'
|
||||
|
||||
class HistoryClones extends Component {
|
||||
|
||||
constructor (props) {
|
||||
@@ -27,7 +23,7 @@ class HistoryClones extends Component {
|
||||
size='big'
|
||||
title='Bot Clone History'
|
||||
text='Loading your Bot Clones...'
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else if (data.operations_HistoryClones && data.operations_HistoryClones.length > 0) {
|
||||
@@ -37,14 +33,14 @@ class HistoryClones extends Component {
|
||||
size='medium'
|
||||
title='Bot Clone History'
|
||||
text='All your Bot Clones are here.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
|
||||
<div className='container'>
|
||||
{
|
||||
data.operations_HistoryClones.map((clone, i) => {
|
||||
return (
|
||||
<ListClones key={clone.id} currentClone={clone} isHistory={true}/>
|
||||
<ListClones key={clone.id} currentClone={clone} isHistory />
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -57,7 +53,7 @@ class HistoryClones extends Component {
|
||||
size='big'
|
||||
title='Bot Clone History'
|
||||
text='Please login to gain access to your Bot Clones.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
@@ -66,7 +62,7 @@ class HistoryClones extends Component {
|
||||
size='big'
|
||||
title='Bot Clone History'
|
||||
text="You don't have any Clone yet. Once you create one you will find it here."
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -79,7 +75,7 @@ class HistoryClones extends Component {
|
||||
size='big'
|
||||
title='Manage your Bot Clones'
|
||||
text='Loading Bot Clones...'
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else if (data.operations_HistoryClones && data.operations_HistoryClones.length > 0) {
|
||||
@@ -89,7 +85,7 @@ class HistoryClones extends Component {
|
||||
size='big'
|
||||
title='Manage your Bot Clones'
|
||||
text='Loading Bot Clones...'
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
|
||||
<div className='container'>
|
||||
@@ -109,7 +105,7 @@ class HistoryClones extends Component {
|
||||
size='big'
|
||||
title='No Bot Clones to display'
|
||||
text='Please Login to access your Bot Clones.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
@@ -119,7 +115,7 @@ class HistoryClones extends Component {
|
||||
size='big'
|
||||
title='No Bot Clones to display'
|
||||
text="You don't have any Bot Clone yet. After you create a new Clone, it will be listed here."
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import React, { Component } from 'react'
|
||||
import AddClone from './AddClone'
|
||||
|
||||
import AddIcon from '@material-ui/icons/Add'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import styles from './styles'
|
||||
|
||||
import {
|
||||
Button, Dialog, DialogContent, DialogContentText, DialogTitle, Paper, Typography
|
||||
Button, Dialog, DialogContent, DialogContentText, DialogTitle
|
||||
} from '@material-ui/core'
|
||||
|
||||
class CloneDialog extends Component {
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import React, { Component } from 'react'
|
||||
import { graphql, compose } from 'react-apollo'
|
||||
import {withRouter} from "react-router-dom";
|
||||
import { clones } from '../../../GraphQL/Calls'
|
||||
import Poloniex from '../../../img/poloniex.png'
|
||||
import Binance from '../../../img/binance.png'
|
||||
|
||||
import {
|
||||
Grid, Paper, Typography, Button, TextField, Dialog, DialogContent,
|
||||
Typography, Button, TextField, Dialog, DialogContent,
|
||||
DialogContentText, DialogTitle, DialogActions,
|
||||
ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary,
|
||||
ExpansionPanelActions, Chip, Divider
|
||||
ExpansionPanelActions, Divider
|
||||
|
||||
} from '@material-ui/core'
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
|
||||
import { tradingStartModes, indicatorStartModes } from '../../../GraphQL/models'
|
||||
import { tradingStartModesList, indicatorStartModes } from '../../../GraphQL/models'
|
||||
import { isDefined, toLocalTime } from '../../../utils'
|
||||
import classNames from 'classnames'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
@@ -47,7 +44,7 @@ class ListClones extends Component {
|
||||
</div>
|
||||
<div className={classes.heading}>
|
||||
<Typography className={classes.heading}>
|
||||
{ clone.processName } / { clone.botType === 'TRADER' ? tradingStartModes[clone.mode] : indicatorStartModes[clone.mode] }
|
||||
{ clone.processName } / { clone.botType === 'Trading' ? tradingStartModesList[clone.mode] : indicatorStartModes[clone.mode] }
|
||||
</Typography>
|
||||
</div>
|
||||
</ExpansionPanelSummary>
|
||||
@@ -93,7 +90,7 @@ class ListClones extends Component {
|
||||
<div className={classes.details}>
|
||||
<div className={classes.column4}>
|
||||
<Typography className={classes.cloneInfoBold}>Last Execution:</Typography>
|
||||
{ (clone.botType === 'TRADER' && isDefined(clone.assetA) ) &&
|
||||
{ (clone.botType === 'Trading' && isDefined(clone.assetA) ) &&
|
||||
<React.Fragment>
|
||||
<Typography className={classes.cloneInfoBold}>Buy Average:</Typography>
|
||||
<Typography className={classes.cloneInfoBold}>Sell Average:</Typography>
|
||||
@@ -105,7 +102,7 @@ class ListClones extends Component {
|
||||
</div>
|
||||
<div className={classes.column4}>
|
||||
<Typography className={classes.cloneInfoNormal}>{ toLocalTime(clone.summaryDate) }</Typography>
|
||||
{ (clone.botType === 'TRADER' && isDefined(clone.assetA) ) &&
|
||||
{ (clone.botType === 'Trading' && isDefined(clone.assetA) ) &&
|
||||
<React.Fragment>
|
||||
<Typography className={classes.cloneInfoNormal}>{clone.buyAverage}</Typography>
|
||||
<Typography className={classes.cloneInfoNormal}>{clone.sellAverage}</Typography>
|
||||
@@ -148,6 +145,12 @@ class ListClones extends Component {
|
||||
className={classes.buttonList}
|
||||
variant='contained' color='secondary' size='small'
|
||||
onClick={() => this.setState({ isLogsDialogOpen: true })}>
|
||||
View Process Output
|
||||
</Button>
|
||||
<Button
|
||||
className={classes.buttonList}
|
||||
variant='contained' color='secondary' size='small'
|
||||
onClick={() => this.showCloneLogs(clone)}>
|
||||
View Logs
|
||||
</Button>
|
||||
<Button
|
||||
@@ -230,6 +233,10 @@ class ListClones extends Component {
|
||||
)
|
||||
}
|
||||
|
||||
showCloneLogs(clone) {
|
||||
this.props.history.push("/logs/show/"+clone.botSlug+"-"+clone.id+".1.0");
|
||||
}
|
||||
|
||||
canStopClone(){
|
||||
const state = JSON.parse(this.props.currentClone.state)
|
||||
if(isDefined(state) && state.hasOwnProperty("running")){
|
||||
@@ -281,5 +288,6 @@ class ListClones extends Component {
|
||||
|
||||
export default compose(
|
||||
graphql(clones.OPERATIONS_REMOVE_CLONE),
|
||||
withStyles(styles)
|
||||
withStyles(styles),
|
||||
withRouter
|
||||
)(ListClones)
|
||||
@@ -2,12 +2,8 @@ import React, { Component } from 'react'
|
||||
import { graphql } from 'react-apollo'
|
||||
import { clones } from '../../../GraphQL/Calls'
|
||||
import TopBar from '../../BannerTopBar'
|
||||
//import CloneDialog from './CloneDialog'
|
||||
import ListClones from './ListClones'
|
||||
|
||||
// Material-ui
|
||||
import { Typography, Paper } from '@material-ui/core'
|
||||
|
||||
class BrowseClones extends Component {
|
||||
|
||||
constructor (props) {
|
||||
@@ -27,7 +23,7 @@ class BrowseClones extends Component {
|
||||
size='big'
|
||||
title='Your Bot Clones'
|
||||
text='Loading your Bot Clones...'
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else if (data.operations_Clones && data.operations_Clones.length > 0) {
|
||||
@@ -37,7 +33,7 @@ class BrowseClones extends Component {
|
||||
size='medium'
|
||||
title='Your Bot Clones'
|
||||
text='All your Bot Clones are here.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
|
||||
<div className='container'>
|
||||
@@ -57,7 +53,7 @@ class BrowseClones extends Component {
|
||||
size='big'
|
||||
title='Your Bot Clones'
|
||||
text='Please login to gain access to your Bot Clones.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
@@ -66,7 +62,7 @@ class BrowseClones extends Component {
|
||||
size='big'
|
||||
title='Your Bot Clones'
|
||||
text="You don't have any Bot Clone yet. Once you create one you will find it here."
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -79,7 +75,7 @@ class BrowseClones extends Component {
|
||||
size='big'
|
||||
title='Manage your Bot Clones'
|
||||
text='Loading Bot Clones...'
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else if (data.operations_Clones && data.operations_Clones.length > 0) {
|
||||
@@ -89,7 +85,7 @@ class BrowseClones extends Component {
|
||||
size='big'
|
||||
title='Manage your Bot Clones'
|
||||
text='Loading Bot Clones...'
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
|
||||
<div className='container'>
|
||||
@@ -109,7 +105,7 @@ class BrowseClones extends Component {
|
||||
size='big'
|
||||
title='No Bot Clones to display'
|
||||
text='Please Login to access your Bot Clones.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
@@ -119,7 +115,7 @@ class BrowseClones extends Component {
|
||||
size='big'
|
||||
title='No Bot Clones to display'
|
||||
text="You don't have any Bot Clone yet. After you create a new Clone, it will be listed here."
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,27 +17,26 @@ const styles = theme => ({
|
||||
},
|
||||
buttonList: {
|
||||
margin: theme.spacing.unit,
|
||||
float: 'right',
|
||||
float: 'right'
|
||||
},
|
||||
buttonGrid: {
|
||||
marginTop: -20,
|
||||
marginTop: -20
|
||||
},
|
||||
button: {
|
||||
display: 'block',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
marginRight: 'auto'
|
||||
},
|
||||
textField: {
|
||||
marginLeft: theme.spacing.unit,
|
||||
marginRight: theme.spacing.unit,
|
||||
marginRight: theme.spacing.unit
|
||||
},
|
||||
textArea: {
|
||||
width:'1750px',
|
||||
margin: theme.spacing.unit,
|
||||
whiteSpace: 'pre',
|
||||
width:'98%',
|
||||
overflowY:'hidden',
|
||||
overflowX:'hidden'
|
||||
width: '98%',
|
||||
overflowY: 'hidden',
|
||||
overflowX: 'hidden'
|
||||
},
|
||||
heading: {
|
||||
fontSize: theme.typography.pxToRem(18),
|
||||
@@ -50,15 +49,15 @@ const styles = theme => ({
|
||||
},
|
||||
helper: {
|
||||
borderLeft: `2px solid ${theme.palette.divider}`,
|
||||
padding: `0px ${theme.spacing.unit * 2}px`,
|
||||
padding: `0px ${theme.spacing.unit * 2}px`
|
||||
},
|
||||
logsWrapper:{
|
||||
width:'1750px',
|
||||
overflowY:'auto',
|
||||
overflowX:'hidden'
|
||||
logsWrapper: {
|
||||
width: '1750px',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden'
|
||||
},
|
||||
details:{
|
||||
display:'flex'
|
||||
details: {
|
||||
display: 'flex'
|
||||
},
|
||||
column2: {
|
||||
flexBasis: '50%',
|
||||
@@ -68,23 +67,23 @@ const styles = theme => ({
|
||||
flexBasis: '33.33%',
|
||||
maxWidth: '33.33%'
|
||||
},
|
||||
cloneInfoTitle:{
|
||||
cloneInfoTitle: {
|
||||
fontWeight: '600',
|
||||
flexBasis: '100%',
|
||||
maxWidth: '100%'
|
||||
},
|
||||
cloneInfoBold:{
|
||||
cloneInfoBold: {
|
||||
fontWeight: '600',
|
||||
paddingLeft: '10px',
|
||||
paddingTop: '10px'
|
||||
},
|
||||
cloneInfoNormal:{
|
||||
cloneInfoNormal: {
|
||||
paddingLeft: '10px',
|
||||
paddingTop: '10px'
|
||||
},
|
||||
avatar: {
|
||||
margin: 10,
|
||||
},
|
||||
margin: 10
|
||||
}
|
||||
})
|
||||
|
||||
export default styles
|
||||
+18
-10
@@ -1,36 +1,44 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Typography } from '@material-ui/core'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import TopBar from './BannerTopBar'
|
||||
|
||||
class Home extends Component {
|
||||
|
||||
render () {
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TopBar
|
||||
size='big'
|
||||
title='Operations Module'
|
||||
text='Responsible for manage bot clones.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
|
||||
title='Clones Module'
|
||||
text='Creating and managing bot clones'
|
||||
backgroundUrl='https://superalgos.org/img/photos/clones-original.jpg'
|
||||
/>
|
||||
|
||||
<div className='homePage container'>
|
||||
<Typography variant='h1' align='center' className='title'>
|
||||
Welcome to the Operations Module!
|
||||
Clones Module
|
||||
</Typography>
|
||||
<Typography variant='h2' align='center' className='subtitle'>
|
||||
Responsible for manage bot clones.
|
||||
Creating and managing bot clones
|
||||
</Typography>
|
||||
<div className='column'>
|
||||
<Typography align='justify'>
|
||||
The Operations module allows you to create instances of your bots
|
||||
that will be executed on a virtual machine.
|
||||
The clones module allows creating and managing the execution of bot’s code.
|
||||
It also stores the metadata information of the clones and handles the interaction
|
||||
with the orchestration software.
|
||||
</Typography>
|
||||
<Typography align='justify'>
|
||||
It integrates with other system modules to enable secure and encapsulated execution of the
|
||||
bots. On the clone creation process the encapsulation is achieved by deploying a Clone Executor
|
||||
Image on a Kubernetes container.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className='column'>
|
||||
<Typography align='justify'>
|
||||
|
||||
Bots are executed using the common framework called Clone Executor which provides support for commonly used resources.
|
||||
</Typography>
|
||||
<Typography align='justify'>
|
||||
When creating a new clone, specific execution parameters are selected, for example the exchange and the time period that the bot will operate in.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Generated
+1903
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "operations-test",
|
||||
"version": "1.0.0",
|
||||
"description": "Superalgos Operations backend API",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"test": "./node_modules/.bin/mocha --reporter spec"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "0.18.0",
|
||||
"chai": "^4.2.0",
|
||||
"dotenv": "^7.0.0",
|
||||
"mocha": "^6.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
NODE_ENV=production
|
||||
|
||||
VIRTUAL_HOST=VIRTUAL_HOST
|
||||
LETSENCRYPT_HOST=LETSENCRYPT_HOST
|
||||
LETSENCRYPT_EMAIL=LETSENCRYPT_EMAIL
|
||||
VIRTUAL_PORT=4003
|
||||
|
||||
PORT=4003
|
||||
|
||||
DB_USERNAME=DB_USERNAME
|
||||
DB_PASSWORD=DB_PASSWORD
|
||||
DB_DOMAIN=DB_DOMAIN
|
||||
|
||||
GATEWAY_ENDPOINT=GATEWAY_ENDPOINT
|
||||
OPERATIONS_API_PRESHARED=OPERATIONS_API_PRESHARED
|
||||
|
||||
# Clone Executor Integration
|
||||
PLATFORM_ENVIRONMENT=PLATFORM_ENVIRONMENT
|
||||
STORAGE_BASE_URL=STORAGE_BASE_URL
|
||||
STORAGE_CONNECTION_STRING=STORAGE_CONNECTION_STRING
|
||||
|
||||
# Auth Config
|
||||
AUTH_URL=AUTH_URL
|
||||
AUTH_CLIENT_ID=AUTH_CLIENT_ID
|
||||
AUTH_CLIENT_SECRET=AUTH_CLIENT_SECRET
|
||||
AUTH_AUDIENCE=AUTH_AUDIENCE
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
/src/config/clone-deployment.json
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
|
||||
+49
-2
@@ -1,4 +1,51 @@
|
||||
# Superalgos Operations Server Module
|
||||
|
||||
## AA Operations Server
|
||||
The Superalgos Operations Server manages the clone life cycle inside the **Superalgos Ecosystem**.
|
||||
The main function includes storing the metadata information of the clones and handling the interaction with the orchestration software which is [Kubernetes](https://Kubernetes.io).
|
||||
|
||||
Server side functions support the business rules for the module.
|
||||
# Execution sequence
|
||||
The operations module (or Clones) integrates with other system modules to enable secure and encapsulated execution. On the clone creation process the encapsulation is achieved by deploying a **Clone Executor** Image on a **Kubernates** container.
|
||||
- [Keys](https://github.com/Superalgos/KeyVaultModule): Manages the signature of the messages sent to the external exchanges.
|
||||
- [Teams](https://github.com/Superalgos/TeamsModule): Provide team and bot authorization.
|
||||
- [Clone Executor](https://github.com/Superalgos/CloneExecutor): Receive the execution parameters and runs an independent instance of the platform.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
User ->> Keys: createKey
|
||||
User ->> Keys: getAvailableKeys
|
||||
User ->> Clones: createClone
|
||||
Clones ->> Keys: getAvailableKeys
|
||||
Clones ->> Keys: authorizeCloneToUseKey
|
||||
Keys ->> Clones: geCloneTeamAndBot
|
||||
Keys ->> Teams: getTeamMembers
|
||||
Keys -->> Clones: authorizeCloneToUseKey
|
||||
Clones ->> Clone Executor: createClone
|
||||
Clone Executor ->> Keys: signTransaction
|
||||
Clone Executor ->> Clones: updateExection
|
||||
|
||||
Note left of Clone Executor: signTransaction and <br/> updateExection are <br/> called n number <br/> of times
|
||||
User ->> Clones: getCloneExecutionInformation
|
||||
Clone Executor ->> Clones: cloneExecutionComplete
|
||||
User ->> Clones: deleteClone
|
||||
Clones ->> Keys: releaseKey
|
||||
```
|
||||
|
||||
> Details about all available methods can be found at the GraphQL endpoint.
|
||||
|
||||
## List active clones
|
||||
- List the clones created by the user that are currently running on the virtual machine.
|
||||
- List the running clones of the teams who the user is authorized to manage (future release)
|
||||
|
||||
## List history clones
|
||||
- List the clones owned by the user that were completed and deleted
|
||||
- List the finalised clones of the teams who the user is authorized to manage (future release)
|
||||
|
||||
## Add new clones
|
||||
A clone can be created:
|
||||
- As a personal clone (the source code will be the one on the personal user folder)
|
||||
- As a team clone (the source code will be the one on the main team folder)
|
||||
Who can create each type:
|
||||
- Personal clones are managed by the user who creates it, not visible for any other user.
|
||||
- Team clones are managed by any ADMIN type team member (on the team member role attribute), and all team admins will see it listed.
|
||||
|
||||
> Written with [StackEdit](https://stackedit.io/).
|
||||
Generated
+1494
-1368
File diff suppressed because it is too large.
Load diff
+13
-13
@@ -10,20 +10,20 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-stage-2": "^6.24.1",
|
||||
"nodemon": "^1.18.10"
|
||||
"babel-cli": "6.26.0",
|
||||
"babel-preset-env": "1.7.0",
|
||||
"babel-preset-stage-2": "6.24.1",
|
||||
"nodemon": "1.18.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"cors": "^2.8.4",
|
||||
"dotenv": "^6.0.0",
|
||||
"express": "^4.16.3",
|
||||
"express-graphql": "^0.6.12",
|
||||
"graphql": "^14.0.0",
|
||||
"kubernetes-client": "^6.5.1",
|
||||
"mongoose": "^5.2.12",
|
||||
"winston": "^3.1.0"
|
||||
"axios": "0.18.0",
|
||||
"cors": "2.8.4",
|
||||
"dotenv": "6.0.0",
|
||||
"express": "4.16.3",
|
||||
"express-graphql": "0.6.12",
|
||||
"graphql": "14.0.0",
|
||||
"kubernetes-client": "6.5.1",
|
||||
"mongoose": "5.2.12",
|
||||
"winston": "3.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"apiVersion": "batch/v1",
|
||||
"kind": "Job",
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "clone-executor"
|
||||
},
|
||||
"name": "clone-executor"
|
||||
},
|
||||
"spec": {
|
||||
"template": {
|
||||
"spec": {
|
||||
"restartPolicy": "OnFailure",
|
||||
"containers": [
|
||||
{
|
||||
"name": "clone-executor",
|
||||
"image": "superalgos/clone-executor:v1.0",
|
||||
"command": ["node", "Run.js"],
|
||||
"env": [
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "clone-executor"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
export const TRADER = 'Trading'
|
||||
export const INDICATOR = 'Indicator'
|
||||
export const EXTRACTION = 'Extraction'
|
||||
export const Trading = 'Trading'
|
||||
export const Indicator = 'Indicator'
|
||||
export const Sensor = 'Sensor'
|
||||
|
||||
export const BotTypesEnum = [
|
||||
TRADER,
|
||||
INDICATOR,
|
||||
EXTRACTION
|
||||
Trading,
|
||||
Indicator,
|
||||
Sensor
|
||||
]
|
||||
|
||||
export default BotTypesEnum
|
||||
@@ -6,7 +6,6 @@ export const ONE_MONTH = 'oneMonth'
|
||||
export const NO_TIME = 'noTime'
|
||||
export const FIXED_INTERVAL = 'fixedInterval'
|
||||
|
||||
|
||||
export const CloneModeEnum = [
|
||||
LIVE,
|
||||
BACKTEST,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import logger from '../config/logger'
|
||||
|
||||
export class AuthentificationError extends Error {
|
||||
code = 401
|
||||
message = 'Autentification not found, you have to be authentificated to perform this action.'
|
||||
@@ -53,6 +51,5 @@ export class KubernateError extends Error {
|
||||
message = 'OperationsModule Kubernate Error. ' + this.message
|
||||
constructor(message){
|
||||
super(message)
|
||||
logger.error(this.stack)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import logger from '../config/logger'
|
||||
import axios from 'axios'
|
||||
|
||||
const authorizeClon = (authorization, keyId, cloneId, releaseClon) => {
|
||||
logger.debug('authorizeClon -> Entering function.')
|
||||
|
||||
return axios({
|
||||
url: process.env.GATEWAY_ENDPOINT,
|
||||
method: 'post',
|
||||
data: {
|
||||
query: `
|
||||
mutation keyVault_AuthorizeClone($keyId: String!, $cloneId: String!, $releaseClon: Boolean!){
|
||||
keyVault_AuthorizeClone(keyId: $keyId, cloneId: $cloneId, releaseClon:$releaseClon)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
keyId: keyId,
|
||||
cloneId: cloneId,
|
||||
releaseClon: releaseClon
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
authorization: authorization
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default authorizeClon
|
||||
@@ -1,46 +1,49 @@
|
||||
import logger from '../config/logger'
|
||||
import axios from 'axios'
|
||||
|
||||
const teamQuery = async(authorization, teamId) => {
|
||||
logger.debug('GraphQL retrieving team information. %s', teamId)
|
||||
const teamQuery = (authorization, botId) => {
|
||||
logger.debug('teamQuery -> Entering function')
|
||||
|
||||
return axios({
|
||||
url: process.env.GATEWAY_ENDPOINT,
|
||||
method: 'post',
|
||||
data: {
|
||||
query: `
|
||||
query Teams_Team($teamId: String!){
|
||||
teams_TeamById(teamId:$teamId) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
fb {
|
||||
id
|
||||
name
|
||||
slug
|
||||
kind
|
||||
avatar
|
||||
}
|
||||
members {
|
||||
member {
|
||||
alias
|
||||
authId
|
||||
query teams_FbByOwner($fbId: String){
|
||||
teams_FbByOwner(fbId: $fbId) {
|
||||
edges{
|
||||
node{
|
||||
id
|
||||
kind
|
||||
name
|
||||
slug
|
||||
avatar
|
||||
team{
|
||||
id
|
||||
name
|
||||
slug
|
||||
profile{
|
||||
avatar
|
||||
}
|
||||
members{
|
||||
member{
|
||||
alias
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
profile{
|
||||
avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
teamId: teamId
|
||||
}
|
||||
fbId: botId
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
authorization: authorization
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
export default teamQuery
|
||||
+4
-4
@@ -5,17 +5,17 @@ import schema from './schema'
|
||||
import mongoose from 'mongoose'
|
||||
import cors from 'cors'
|
||||
import logger from './config/logger'
|
||||
import wrongPreshared from './errors/notAllowed.json';
|
||||
import wrongPreshared from './errors/notAllowed.json'
|
||||
|
||||
const app = express()
|
||||
|
||||
app.post('/graphql', (req, res, next) => {
|
||||
if (req.headers.preshared === process.env.OPERATIONS_API_PRESHARED) {
|
||||
next();
|
||||
next()
|
||||
} else {
|
||||
res.send(wrongPreshared);
|
||||
res.send(wrongPreshared)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// Allow cross origing request
|
||||
app.use(cors())
|
||||
|
||||
@@ -2,138 +2,226 @@ import logger from '../config/logger'
|
||||
import { toPlatformDatetime, isDefined } from '../config/utils'
|
||||
import { KubernateError } from '../errors'
|
||||
import { Client, config } from 'kubernetes-client'
|
||||
// Get base Deployment config
|
||||
import deploymentManifest from '../config/clone-deployment.json'
|
||||
import { BACKTEST, NO_TIME } from '../enums/CloneMode'
|
||||
import { TRADER, INDICATOR, EXTRACTION } from '../enums/BotTypes'
|
||||
import { BACKTEST, COMPETITION, LIVE, NO_TIME } from '../enums/CloneMode'
|
||||
import { Trading, Indicator, Sensor } from '../enums/BotTypes'
|
||||
|
||||
const createClone = async (clone) => {
|
||||
try {
|
||||
logger.debug("createClone %s", clone.id)
|
||||
const client = new Client({config: config.fromKubeconfig(), version: '1.9'})
|
||||
|
||||
// Make changes to base deployment config
|
||||
logger.debug('createClone %s', clone.id)
|
||||
const client = new Client({ config: config.fromKubeconfig(), version: '1.9' })
|
||||
deploymentManifest.metadata.name = clone.id
|
||||
|
||||
logger.debug("createClone Environment and Auth Configuration.")
|
||||
logger.debug('createClone Environment and Auth Configuration.')
|
||||
let env = []
|
||||
env.push({
|
||||
"name": "PLATFORM_ENVIRONMENT",
|
||||
"value": process.env.PLATFORM_ENVIRONMENT
|
||||
'name': 'PLATFORM_ENVIRONMENT',
|
||||
'value': process.env.PLATFORM_ENVIRONMENT
|
||||
})
|
||||
env.push({
|
||||
"name": "GATEWAY_ENDPOINT",
|
||||
"value": process.env.GATEWAY_ENDPOINT
|
||||
'name': 'GATEWAY_ENDPOINT',
|
||||
'value': process.env.GATEWAY_ENDPOINT
|
||||
})
|
||||
env.push({
|
||||
"name": "STORAGE_BASE_URL",
|
||||
"value": process.env.STORAGE_BASE_URL
|
||||
'name': 'STORAGE_BASE_URL',
|
||||
'value': process.env.STORAGE_BASE_URL
|
||||
})
|
||||
env.push({
|
||||
"name": "STORAGE_CONNECTION_STRING",
|
||||
"value": process.env.STORAGE_CONNECTION_STRING
|
||||
'name': 'STORAGE_CONNECTION_STRING',
|
||||
'value': process.env.STORAGE_CONNECTION_STRING
|
||||
})
|
||||
env.push({
|
||||
"name": "AUTH_URL",
|
||||
"value": process.env.AUTH_URL
|
||||
'name': 'AUTH_URL',
|
||||
'value': process.env.AUTH_URL
|
||||
})
|
||||
env.push({
|
||||
"name": "AUTH_CLIENT_ID",
|
||||
"value": process.env.AUTH_CLIENT_ID
|
||||
'name': 'AUTH_CLIENT_ID',
|
||||
'value': process.env.AUTH_CLIENT_ID
|
||||
})
|
||||
env.push({
|
||||
"name": "AUTH_CLIENT_SECRET",
|
||||
"value": process.env.AUTH_CLIENT_SECRET
|
||||
'name': 'AUTH_CLIENT_SECRET',
|
||||
'value': process.env.AUTH_CLIENT_SECRET
|
||||
})
|
||||
env.push({
|
||||
"name": "AUTH_AUDIENCE",
|
||||
"value": process.env.AUTH_AUDIENCE
|
||||
'name': 'AUTH_AUDIENCE',
|
||||
'value': process.env.AUTH_AUDIENCE
|
||||
})
|
||||
|
||||
logger.debug("createClone General Financial Being Configuration.")
|
||||
// MINIO
|
||||
env.push({
|
||||
"name": "DEV_TEAM",
|
||||
"value": clone.teamSlug
|
||||
'name': 'STORAGE_PROVIDER',
|
||||
'value': process.env.STORAGE_PROVIDER
|
||||
})
|
||||
env.push({
|
||||
"name": "BOT",
|
||||
"value": clone.botSlug
|
||||
'name': 'MINIO_END_POINT',
|
||||
'value': process.env.MINIO_END_POINT
|
||||
})
|
||||
env.push({
|
||||
"name": "TYPE",
|
||||
"value": clone.botType
|
||||
'name': 'MINIO_PORT',
|
||||
'value': process.env.MINIO_PORT
|
||||
})
|
||||
env.push({
|
||||
"name": "CLONE_ID",
|
||||
"value": clone.id.toString()
|
||||
'name': 'MINIO_USE_SSL',
|
||||
'value': process.env.MINIO_USE_SSL
|
||||
})
|
||||
env.push({
|
||||
"name": "START_MODE",
|
||||
"value": clone.mode
|
||||
'name': 'MINIO_ACCESS_KEY',
|
||||
'value': process.env.MINIO_ACCESS_KEY
|
||||
})
|
||||
env.push({
|
||||
"name": "RUN_AS_TEAM",
|
||||
"value": clone.runAsTeam.toString()
|
||||
})
|
||||
env.push({
|
||||
"name": "USER_LOGGED_IN",
|
||||
"value": clone.userLoggedIn
|
||||
})
|
||||
env.push({
|
||||
"name": "PROCESS",
|
||||
"value": clone.processName
|
||||
'name': 'MINIO_SECRET_KEY',
|
||||
'value': process.env.MINIO_SECRET_KEY
|
||||
})
|
||||
|
||||
logger.debug("createClone Trading Configuration.")
|
||||
if(clone.botType === TRADER ){
|
||||
|
||||
logger.debug('createClone General Financial Being Configuration.')
|
||||
env.push({
|
||||
'name': 'DEV_TEAM',
|
||||
'value': clone.teamSlug
|
||||
})
|
||||
env.push({
|
||||
'name': 'BOT',
|
||||
'value': clone.botSlug
|
||||
})
|
||||
env.push({
|
||||
'name': 'TYPE',
|
||||
'value': clone.botType
|
||||
})
|
||||
env.push({
|
||||
'name': 'CLONE_ID',
|
||||
'value': clone.id.toString()
|
||||
})
|
||||
env.push({
|
||||
'name': 'START_MODE',
|
||||
'value': clone.mode
|
||||
})
|
||||
env.push({
|
||||
'name': 'RUN_AS_TEAM',
|
||||
'value': clone.runAsTeam.toString()
|
||||
})
|
||||
env.push({
|
||||
'name': 'USER_LOGGED_IN',
|
||||
'value': clone.userLoggedIn
|
||||
})
|
||||
env.push({
|
||||
'name': 'PROCESS',
|
||||
'value': clone.processName
|
||||
})
|
||||
env.push({
|
||||
'name': 'EXCHANGE_NAME',
|
||||
'value': clone.exchangeName
|
||||
})
|
||||
|
||||
logger.debug('createClone Trading Configuration.')
|
||||
if (clone.botType === Trading) {
|
||||
env.push({
|
||||
"name": "RESUME_EXECUTION",
|
||||
"value": clone.resumeExecution.toString()
|
||||
'name': 'RESUME_EXECUTION',
|
||||
'value': clone.resumeExecution.toString()
|
||||
})
|
||||
|
||||
if(clone.mode === BACKTEST){
|
||||
env.push({
|
||||
'name': 'TIME_PERIOD',
|
||||
'value': clone.timePeriod
|
||||
})
|
||||
|
||||
env.push({
|
||||
'name': 'DATA_SET',
|
||||
'value': datasetNames().get(clone.timePeriod)
|
||||
})
|
||||
|
||||
if (clone.mode === BACKTEST) {
|
||||
env.push({
|
||||
"name": "BEGIN_DATE_TIME",
|
||||
"value": toPlatformDatetime(clone.beginDatetime)
|
||||
'name': 'BEGIN_DATE_TIME',
|
||||
'value': toPlatformDatetime(clone.beginDatetime)
|
||||
})
|
||||
env.push({
|
||||
"name": "END_DATE_TIME",
|
||||
"value": toPlatformDatetime(clone.endDatetime)
|
||||
'name': 'END_DATE_TIME',
|
||||
'value': toPlatformDatetime(clone.endDatetime)
|
||||
})
|
||||
env.push({
|
||||
"name": "WAIT_TIME",
|
||||
"value": clone.waitTime.toString()
|
||||
'name': 'WAIT_TIME',
|
||||
'value': clone.waitTime.toString()
|
||||
})
|
||||
} else if (clone.mode === COMPETITION) {
|
||||
env.push({
|
||||
'name': 'BEGIN_DATE_TIME',
|
||||
'value': toPlatformDatetime(clone.beginDatetime)
|
||||
})
|
||||
env.push({
|
||||
'name': 'END_DATE_TIME',
|
||||
'value': toPlatformDatetime(clone.endDatetime)
|
||||
})
|
||||
env.push({
|
||||
'name': 'KEY_ID',
|
||||
'value': clone.keyId
|
||||
})
|
||||
} else if (clone.mode === LIVE) {
|
||||
env.push({
|
||||
'name': 'KEY_ID',
|
||||
'value': clone.keyId
|
||||
})
|
||||
}
|
||||
} else if(clone.botType === INDICATOR || clone.botType === EXTRACTION ){
|
||||
if(clone.mode !== NO_TIME){
|
||||
} else if (clone.botType === Indicator || clone.botType === Sensor) {
|
||||
if (clone.mode === NO_TIME) {
|
||||
if (!clone.resumeExecution) {
|
||||
env.push({
|
||||
"name": "MIN_YEAR",
|
||||
"value": isDefined(clone.startYear) ? clone.startYear.toString() : ''
|
||||
})
|
||||
env.push({
|
||||
"name": "MAX_YEAR",
|
||||
"value": isDefined(clone.endYear) ? clone.endYear.toString() : ''
|
||||
})
|
||||
env.push({
|
||||
"name": "MONTH",
|
||||
"value": isDefined(clone.month) ? clone.month.toString() : ''
|
||||
})
|
||||
env.push({
|
||||
"name": "INTERVAL",
|
||||
"value": isDefined(clone.interval) ? clone.interval.toString() : ''
|
||||
'name': 'BEGIN_DATE_TIME',
|
||||
'value': toPlatformDatetime(clone.beginDatetime)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
env.push({
|
||||
'name': 'MIN_YEAR',
|
||||
'value': isDefined(clone.startYear) ? clone.startYear.toString() : ''
|
||||
})
|
||||
env.push({
|
||||
'name': 'MAX_YEAR',
|
||||
'value': isDefined(clone.endYear) ? clone.endYear.toString() : ''
|
||||
})
|
||||
env.push({
|
||||
'name': 'MONTH',
|
||||
'value': isDefined(clone.month) ? clone.month.toString() : ''
|
||||
})
|
||||
env.push({
|
||||
'name': 'INTERVAL',
|
||||
'value': isDefined(clone.interval) ? clone.interval.toString() : ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deploymentManifest.spec.template.spec.containers[0].env = env
|
||||
|
||||
const create = await client.apis.batch.v1.namespaces('default').jobs.post(
|
||||
await client.apis.batch.v1.namespaces('default').jobs.post(
|
||||
{ body: deploymentManifest })
|
||||
|
||||
logger.debug("createClone %s on Kubernates success.", clone.id)
|
||||
logger.debug('createClone %s on Kubernates success.', clone.id)
|
||||
} catch (err) {
|
||||
throw new KubernateError(err)
|
||||
throw new KubernateError(err)
|
||||
}
|
||||
}
|
||||
|
||||
function datasetNames() {
|
||||
let dataSetMap = new Map()
|
||||
dataSetMap.set("24-hs", "Multi-Period-Market")
|
||||
dataSetMap.set("12-hs", "Multi-Period-Market")
|
||||
dataSetMap.set("08-hs", "Multi-Period-Market")
|
||||
dataSetMap.set("06-hs", "Multi-Period-Market")
|
||||
dataSetMap.set("04-hs", "Multi-Period-Market")
|
||||
dataSetMap.set("03-hs", "Multi-Period-Market")
|
||||
dataSetMap.set("02-hs", "Multi-Period-Market")
|
||||
dataSetMap.set("01-hs", "Multi-Period-Market")
|
||||
dataSetMap.set("45-min", "Multi-Period-Daily")
|
||||
dataSetMap.set("40-min", "Multi-Period-Daily")
|
||||
dataSetMap.set("30-min", "Multi-Period-Daily")
|
||||
dataSetMap.set("20-min", "Multi-Period-Daily")
|
||||
dataSetMap.set("15-min", "Multi-Period-Daily")
|
||||
dataSetMap.set("10-min", "Multi-Period-Daily")
|
||||
dataSetMap.set("05-min", "Multi-Period-Daily")
|
||||
dataSetMap.set("04-min", "Multi-Period-Daily")
|
||||
dataSetMap.set("03-min", "Multi-Period-Daily")
|
||||
dataSetMap.set("02-min", "Multi-Period-Daily")
|
||||
dataSetMap.set("01-min", "Multi-Period-Daily")
|
||||
return dataSetMap
|
||||
}
|
||||
|
||||
export default createClone
|
||||
@@ -4,31 +4,37 @@ import { Client, config } from 'kubernetes-client'
|
||||
|
||||
const getClonePodLogs = async (cloneName) => {
|
||||
try {
|
||||
logger.debug("getClonePodLogs on kubernates started.")
|
||||
logger.debug('getClonePodLogs on kubernates started.')
|
||||
const client = new Client({config: config.fromKubeconfig(), version: '1.9'})
|
||||
|
||||
let query = {
|
||||
"qs":{
|
||||
"labelSelector": "job-name="+cloneName
|
||||
'qs': {
|
||||
'labelSelector': 'job-name=' + cloneName
|
||||
}
|
||||
}
|
||||
|
||||
const pod = await client.api.v1.namespaces('default').pods.get(query)
|
||||
|
||||
let queryLogs = {
|
||||
"qs":{
|
||||
"tailLines": "20"
|
||||
'qs': {
|
||||
'tailLines': '20'
|
||||
}
|
||||
}
|
||||
const logs = await client.api.v1.namespaces('default')
|
||||
.pods(pod.body.items[0].metadata.name).log.get(queryLogs)
|
||||
let logsArray = logs.body.split('\n')
|
||||
if(pod.body.items[0] !== undefined){
|
||||
const logs = await client.api.v1.namespaces('default')
|
||||
.pods(pod.body.items[0].metadata.name).log.get(queryLogs)
|
||||
let logsArray = logs.body.split('\n')
|
||||
|
||||
logger.debug("getClonePodLogs on kubernates successful.")
|
||||
logger.debug('getClonePodLogs on kubernates successful.')
|
||||
|
||||
return JSON.stringify(logsArray, null, 2)
|
||||
return JSON.stringify(logsArray, null, 2)
|
||||
} else {
|
||||
logger.warn('getClonePodStatus pod not found %s: ', cloneName)
|
||||
return "Logs not available."
|
||||
}
|
||||
} catch (err) {
|
||||
throw new KubernateError(err)
|
||||
logger.error('There was an error getting the pod logs %s: ', err)
|
||||
return "Logs not available."
|
||||
}
|
||||
}
|
||||
export default getClonePodLogs
|
||||
@@ -4,27 +4,34 @@ import { Client, config } from 'kubernetes-client'
|
||||
|
||||
const getClonePodStatus = async (cloneName) => {
|
||||
try {
|
||||
logger.debug("getClonePodStatus on kubernates started. ")
|
||||
const client = new Client({config: config.fromKubeconfig(), version: '1.9'})
|
||||
logger.debug('getClonePodStatus on kubernates started.')
|
||||
const client = new Client({ config: config.fromKubeconfig(), version: '1.9' })
|
||||
|
||||
let query = {
|
||||
"qs":{
|
||||
"labelSelector": "job-name="+cloneName
|
||||
'qs': {
|
||||
'labelSelector': 'job-name=' + cloneName
|
||||
}
|
||||
}
|
||||
|
||||
const pod = await client.api.v1.namespaces('default').pods.get(query)
|
||||
|
||||
let clonePodStatus = pod.body.items[0].status.containerStatuses[0].state
|
||||
if (pod.body.items[0] !== undefined) {
|
||||
let clonePodStatus = pod.body.items[0].status.containerStatuses[0].state
|
||||
|
||||
if (clonePodStatus.hasOwnProperty('terminated')
|
||||
&& clonePodStatus.terminated.hasOwnProperty('containerID')) {
|
||||
delete clonePodStatus.terminated.containerID
|
||||
if (clonePodStatus.hasOwnProperty('terminated')
|
||||
&& clonePodStatus.terminated.hasOwnProperty('containerID')) {
|
||||
delete clonePodStatus.terminated.containerID
|
||||
}
|
||||
logger.debug('getClonePodStatus %s on kubernates successful', cloneName)
|
||||
|
||||
return JSON.stringify(clonePodStatus, null, 2)
|
||||
} else {
|
||||
logger.warn('getClonePodStatus pod not found: %s', cloneName)
|
||||
return " Clone not found on the server. "
|
||||
}
|
||||
logger.debug("getClonePodStatus %s on kubernates successful", cloneName)
|
||||
return JSON.stringify(clonePodStatus, null, 2)
|
||||
} catch (err) {
|
||||
throw new KubernateError(err)
|
||||
logger.error('There was an error getting the pod status %s: ', err)
|
||||
return "Status not available."
|
||||
}
|
||||
}
|
||||
export default getClonePodStatus
|
||||
@@ -4,25 +4,23 @@ import { Client, config } from 'kubernetes-client'
|
||||
|
||||
const removeClone = async (clone) => {
|
||||
try {
|
||||
logger.debug("removeClone %s", clone.id)
|
||||
logger.debug('removeClone %s', clone.id)
|
||||
const client = new Client({config: config.fromKubeconfig(), version: '1.9'})
|
||||
|
||||
let query = {
|
||||
"qs":{
|
||||
"labelSelector": "job-name=" + clone.id
|
||||
'qs': {
|
||||
'labelSelector': 'job-name=' + clone.id
|
||||
}
|
||||
}
|
||||
|
||||
const removed = await client.apis.batch.v1.namespaces('default').jobs(
|
||||
clone.id).delete()
|
||||
await client.apis.batch.v1.namespaces('default').jobs(clone.id).delete()
|
||||
|
||||
const pod = await client.api.v1.namespaces('default').pods.get(query)
|
||||
const podRemoved = await client.api.v1.namespaces('default')
|
||||
.pods(pod.body.items[0].metadata.name).delete()
|
||||
await client.api.v1.namespaces('default').pods(pod.body.items[0].metadata.name).delete()
|
||||
|
||||
logger.debug("removeClone %s on kubernates succesful", clone.id)
|
||||
logger.debug('removeClone %s on kubernates succesful', clone.id)
|
||||
} catch (err) {
|
||||
throw new KubernateError(err)
|
||||
logger.warn('removeClone %s on kubernates failed, the job was not found on the server.', clone.id)
|
||||
}
|
||||
}
|
||||
export default removeClone
|
||||
+13
-12
@@ -2,31 +2,32 @@ import mongoose, { Schema } from 'mongoose'
|
||||
|
||||
import {
|
||||
UNPUBLISHED,
|
||||
CloneStateEnum,
|
||||
} from '../enums/CloneState';
|
||||
CloneStateEnum
|
||||
} from '../enums/CloneState'
|
||||
|
||||
import {
|
||||
BACKTEST,
|
||||
CloneModeEnum,
|
||||
} from '../enums/CloneMode';
|
||||
CloneModeEnum
|
||||
} from '../enums/CloneMode'
|
||||
|
||||
const cloneSchema = new Schema({
|
||||
authId: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
teamId: {
|
||||
type: String,
|
||||
required: true,
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
botId: {
|
||||
type: String,
|
||||
required: true,
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
keyId: String,
|
||||
mode: {
|
||||
type: String,
|
||||
enum: CloneModeEnum,
|
||||
default: BACKTEST,
|
||||
default: BACKTEST
|
||||
},
|
||||
resumeExecution: Boolean,
|
||||
beginDatetime: Number,
|
||||
@@ -35,7 +36,7 @@ const cloneSchema = new Schema({
|
||||
state: {
|
||||
type: String,
|
||||
enum: CloneStateEnum,
|
||||
default: UNPUBLISHED,
|
||||
default: UNPUBLISHED
|
||||
},
|
||||
stateDatetime: Number,
|
||||
createDatetime: Number,
|
||||
@@ -51,7 +52,7 @@ const cloneSchema = new Schema({
|
||||
assetB: String,
|
||||
processName: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,54 +1,18 @@
|
||||
import logger from '../config/logger'
|
||||
import { isDefined } from '../config/utils'
|
||||
import { AutorizationError } from '../errors'
|
||||
|
||||
const cloneDetails = async(authId, team, clone) => {
|
||||
logger.debug('Retrieving clone details.')
|
||||
|
||||
if(!(authId === (process.env.AUTH_CLIENT_ID + '@clients') || isAuthorized(team, authId))){
|
||||
logger.debug('User %s is not authorized to manage team %s.', authId, team.slug )
|
||||
throw new AutorizationError()
|
||||
const cloneDetails = (selectedBot, clone) => {
|
||||
if (selectedBot !== undefined) {
|
||||
clone.botId = selectedBot.id
|
||||
clone.kind = selectedBot.kind
|
||||
clone.botName = selectedBot.name
|
||||
clone.botSlug = selectedBot.slug
|
||||
clone.botAvatar = selectedBot.avatar
|
||||
clone.teamId = selectedBot.team.id
|
||||
clone.teamName = selectedBot.team.name
|
||||
clone.teamSlug = selectedBot.team.slug
|
||||
clone.teamAvatar = selectedBot.team.profile.avatar
|
||||
clone.userLoggedIn = selectedBot.team.members[0].member.alias
|
||||
clone.botType = selectedBot.kind
|
||||
}
|
||||
|
||||
let selectedBot = getSelectedBot(team, clone.botId)
|
||||
clone.teamId = team.id
|
||||
clone.botId = selectedBot.id
|
||||
clone.kind = selectedBot.kind
|
||||
clone.teamSlug = team.slug
|
||||
clone.botSlug = selectedBot.slug
|
||||
clone.teamName = team.name
|
||||
clone.botName = selectedBot.name
|
||||
clone.botAvatar = selectedBot.avatar
|
||||
clone.teamAvatar = team.profile.avatar
|
||||
clone.userLoggedIn = team.members[0].member.alias //TODO change by context info if possible
|
||||
|
||||
logger.debug('Retrieving clone details complete.')
|
||||
return clone
|
||||
};
|
||||
|
||||
function getSelectedBot(team, selectedBotId){
|
||||
logger.debug('Getting selected bot.')
|
||||
if(isDefined(team)){
|
||||
for (var i = 0; i < team.fb.length; i++) {
|
||||
if(team.fb[i].id === selectedBotId){
|
||||
return team.fb[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isAuthorized(team, authId){
|
||||
logger.debug('Checking if the user is authorized by the teams module.')
|
||||
let authorized = false
|
||||
if(isDefined(team)){
|
||||
for (var i = 0; i < team.members.length; i++) {
|
||||
if(team.members[i].member.authId === authId){
|
||||
authorized = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return authorized
|
||||
}
|
||||
|
||||
export default cloneDetails
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
GraphQLSchema
|
||||
} from 'graphql'
|
||||
|
||||
import { GraphQLSchema } from 'graphql'
|
||||
import Mutation from './mutations'
|
||||
import RootQuery from './queries'
|
||||
|
||||
|
||||
@@ -1,72 +1,93 @@
|
||||
import { CloneInputType } from '../types/input';
|
||||
import { CloneType } from '../types';
|
||||
import { Clone } from '../../models';
|
||||
import { CloneModeEnum } from '../../enums/CloneMode';
|
||||
import { BotTypesEnum } from '../../enums/BotTypes';
|
||||
import { CloneInputType } from '../types/input'
|
||||
import { CloneType } from '../types'
|
||||
import { Clone } from '../../models'
|
||||
import { CloneModeEnum } from '../../enums/CloneMode'
|
||||
import { BotTypesEnum } from '../../enums/BotTypes'
|
||||
import teamQuery from '../../graphQLCalls/teamQuery'
|
||||
import authorizeClone from '../../graphQLCalls/authorizeClone'
|
||||
import cloneDetails from '../cloneDetails'
|
||||
|
||||
import {
|
||||
AuthentificationError,
|
||||
WrongArgumentsError,
|
||||
OperationsError
|
||||
OperationsError,
|
||||
AutorizationError
|
||||
} from '../../errors'
|
||||
|
||||
import logger from '../../config/logger'
|
||||
import createKubernetesClone from '../../kubernetes/createClone'
|
||||
import { isDefined } from '../../config/utils'
|
||||
import { LIVE, COMPETITION } from '../../enums/CloneMode'
|
||||
|
||||
const args = {
|
||||
clone: {type: CloneInputType }
|
||||
clone: { type: CloneInputType }
|
||||
}
|
||||
|
||||
const resolve = async(parent, { clone }, context) => {
|
||||
const resolve = async (parent, { clone }, context) => {
|
||||
logger.debug('addClone -> Entering Fuction.')
|
||||
|
||||
if (!context.userId) {
|
||||
throw new AuthentificationError()
|
||||
}
|
||||
|
||||
if(!Object.values(CloneModeEnum).includes(clone.mode)){
|
||||
if (!Object.values(CloneModeEnum).includes(clone.mode)) {
|
||||
throw new WrongArgumentsError('The mode selected is not valid.')
|
||||
}
|
||||
|
||||
if(!Object.values(BotTypesEnum).includes(clone.botType)){
|
||||
if (!Object.values(BotTypesEnum).includes(clone.botType)) {
|
||||
throw new WrongArgumentsError('The bot type selected is not valid.')
|
||||
}
|
||||
|
||||
try{
|
||||
let team = await teamQuery(context.authorization, clone.teamId)
|
||||
clone = await cloneDetails(context.userId, team.data.data.teams_TeamById, clone)
|
||||
clone.createDatetime = new Date().valueOf() / 1000|0
|
||||
clone.active = true
|
||||
if ((clone.mode === LIVE || clone.mode === COMPETITION) && !isDefined(clone.keyId)) {
|
||||
throw new WrongArgumentsError('The key was not provided to run the clone.')
|
||||
}
|
||||
|
||||
logger.debug('addClone -> Creating a new clone.')
|
||||
let allUserBotsResponse = await teamQuery(context.authorization, clone.botId)
|
||||
let bot = allUserBotsResponse.data.data.teams_FbByOwner.edges[0].node
|
||||
clone = cloneDetails(bot, clone)
|
||||
clone.createDatetime = new Date().valueOf() / 1000 | 0
|
||||
clone.active = true
|
||||
|
||||
// TODO Temporary Limitation on bot creation
|
||||
if (!(clone.teamSlug === "AAMasters" || clone.teamSlug === "AAVikings")) {
|
||||
let clones = await Clone.find({
|
||||
authId: context.userId,
|
||||
active: true
|
||||
})
|
||||
|
||||
if (clones !== undefined && clones.length >= 5) {
|
||||
throw new Error('Currently maximum number of clones per user is 5. You can delete an existing clone and try again.')
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let newClone = new Clone(clone)
|
||||
newClone.authId = context.userId
|
||||
clone.id = newClone._id
|
||||
await createKubernetesClone(clone)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
newClone.save((err) => {
|
||||
if (err){
|
||||
logger.error('addClone -> Error: %s', err.stack)
|
||||
reject('There has been an error storing the clone.')
|
||||
}
|
||||
else {
|
||||
//TODO transaction on database
|
||||
logger.debug('addClone -> Save clone sucessful.')
|
||||
resolve(newClone)
|
||||
}
|
||||
})
|
||||
})
|
||||
} catch(err) {
|
||||
logger.debug('addClone -> Creating the clone on the Database.')
|
||||
let savedClone = await newClone.save()
|
||||
|
||||
if (clone.mode === LIVE || clone.mode === COMPETITION) {
|
||||
logger.debug('addClone -> Authorizing clone to use the key.')
|
||||
let response = await authorizeClone(context.authorization, clone.keyId, clone.id, false)
|
||||
logger.debug('addClone -> Authorizing clone to use the key.')
|
||||
if (response.data.data.keyVault_AuthorizeClone === clone.keyId) {
|
||||
await createKubernetesClone(clone)
|
||||
} else {
|
||||
throw new AutorizationError()
|
||||
}
|
||||
} else {
|
||||
await createKubernetesClone(clone)
|
||||
}
|
||||
|
||||
logger.debug('addClone -> Clone created sucessfully.')
|
||||
return savedClone
|
||||
} catch (err) {
|
||||
logger.error('addClone -> Error: %s', err.stack)
|
||||
throw new OperationsError('There has been an error creating the clone.')
|
||||
}
|
||||
}
|
||||
|
||||
const mutation = {
|
||||
const AddCloneMutation = {
|
||||
addClone: {
|
||||
type: CloneType,
|
||||
args,
|
||||
@@ -74,4 +95,4 @@ const mutation = {
|
||||
}
|
||||
}
|
||||
|
||||
export default mutation
|
||||
export default AddCloneMutation
|
||||
@@ -1,20 +1,13 @@
|
||||
import {
|
||||
GraphQLNonNull,
|
||||
GraphQLID,
|
||||
GraphQLString
|
||||
} from 'graphql'
|
||||
|
||||
import {
|
||||
AuthentificationError,
|
||||
OperationsError
|
||||
} from '../../errors'
|
||||
|
||||
import { GraphQLNonNull, GraphQLID, GraphQLString } from 'graphql'
|
||||
import { AuthentificationError, OperationsError } from '../../errors'
|
||||
import { Clone } from '../../models'
|
||||
import logger from '../../config/logger'
|
||||
import removeKuberneteClone from '../../kubernetes/removeClone'
|
||||
import teamQuery from '../../graphQLCalls/teamQuery'
|
||||
import { isDefined } from '../../config/utils'
|
||||
import cloneDetails from '../cloneDetails'
|
||||
import authorizeClone from '../../graphQLCalls/authorizeClone'
|
||||
import { LIVE, COMPETITION } from '../../enums/CloneMode'
|
||||
|
||||
const args = {
|
||||
id: { type: new GraphQLNonNull(GraphQLID) }
|
||||
@@ -26,42 +19,54 @@ const resolve = async (parent, { id }, context) => {
|
||||
if (!context.userId) {
|
||||
throw new AuthentificationError()
|
||||
}
|
||||
try {
|
||||
let clone = await Clone.findOne({
|
||||
_id: id,
|
||||
authId: context.userId,
|
||||
active: true
|
||||
})
|
||||
|
||||
let clone = await Clone.findOne({
|
||||
_id: id,
|
||||
authId: context.userId,
|
||||
active: true
|
||||
})
|
||||
|
||||
if(!isDefined(clone)){
|
||||
throw new OperationsError('You are not authorized to remove this clone.')
|
||||
}
|
||||
|
||||
let team = await teamQuery(context.authorization, clone.teamId)
|
||||
clone = await cloneDetails(context.userId, team.data.data.teams_TeamById, clone)
|
||||
|
||||
logger.debug('removeClone -> Removing Clone from Kubernates.')
|
||||
await removeKuberneteClone(clone)
|
||||
|
||||
const query = {
|
||||
_id: id,
|
||||
authId: context.userId
|
||||
}
|
||||
const options = { new: true }
|
||||
const update = { active: false }
|
||||
|
||||
Clone.findOneAndUpdate(query, update, options, (err, doc) => {
|
||||
if (err){
|
||||
logger.error('removeClone -> Error removing clone from the DB. %s', err.stack)
|
||||
throw new OperationsError(err)
|
||||
}else{
|
||||
logger.debug('removeClone -> Clone Removed from the DB.')
|
||||
return('Clone Removed.')
|
||||
if (!isDefined(clone)) {
|
||||
throw new OperationsError('You are not authorized to remove this clone.')
|
||||
}
|
||||
})
|
||||
|
||||
let allUserBotsResponse = await teamQuery(context.authorization, clone.botId)
|
||||
let bot = allUserBotsResponse.data.data.teams_FbByOwner.edges[0].node
|
||||
clone = cloneDetails(bot, clone)
|
||||
|
||||
if ((clone.mode === LIVE || clone.mode === COMPETITION) && isDefined(clone.keyId)) {
|
||||
logger.debug('removeClone -> Release the clone key.')
|
||||
await authorizeClone(context.authorization, clone.keyId, clone.id, true)
|
||||
}
|
||||
|
||||
logger.debug('removeClone -> Removing Clone from Kubernates.')
|
||||
await removeKuberneteClone(clone)
|
||||
|
||||
const query = {
|
||||
_id: id,
|
||||
authId: context.userId
|
||||
}
|
||||
const options = { new: true }
|
||||
const update = { active: false }
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
Clone.findOneAndUpdate(query, update, options, (err, doc) => {
|
||||
if (err) {
|
||||
logger.error('removeClone -> Error removing clone from the DB. %s', err.stack)
|
||||
rej(err)
|
||||
return
|
||||
}
|
||||
logger.debug('removeClone -> Clone Removed from the DB.')
|
||||
res('Clone Removed.')
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error removing the clone. %s', error.stack)
|
||||
throw new OperationsError(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
const mutation = {
|
||||
const RemoveCloneMutation = {
|
||||
removeClone: {
|
||||
type: GraphQLString,
|
||||
args,
|
||||
@@ -69,4 +74,4 @@ const mutation = {
|
||||
}
|
||||
}
|
||||
|
||||
export default mutation
|
||||
export default RemoveCloneMutation
|
||||
@@ -5,16 +5,9 @@ import {
|
||||
GraphQLInt,
|
||||
GraphQLFloat
|
||||
} from 'graphql'
|
||||
|
||||
import {
|
||||
OperationsError
|
||||
} from '../../errors'
|
||||
|
||||
import { OperationsError } from '../../errors'
|
||||
import { Clone } from '../../models'
|
||||
import logger from '../../config/logger'
|
||||
import teamQuery from '../../graphQLCalls/teamQuery'
|
||||
import cloneDetails from '../cloneDetails'
|
||||
import removeKuberneteClone from '../../kubernetes/removeClone'
|
||||
import { isDefined } from '../../config/utils'
|
||||
|
||||
const args = {
|
||||
@@ -30,34 +23,30 @@ const args = {
|
||||
}
|
||||
|
||||
const resolve = async (parent,
|
||||
{ id, summaryDate, buyAverage, sellAverage, marketRate, combinedProfitsA,
|
||||
combinedProfitsB, assetA, assetB
|
||||
}, context) => {
|
||||
|
||||
{ id, summaryDate, buyAverage, sellAverage, marketRate, combinedProfitsA,
|
||||
combinedProfitsB, assetA, assetB
|
||||
}, context) => {
|
||||
logger.debug('UpdateExecutionSummary -> Entering Function.')
|
||||
|
||||
if (!context.userId) {
|
||||
throw new AuthentificationError()
|
||||
}
|
||||
|
||||
try{
|
||||
let sucessful
|
||||
if (!(context.userId === process.env.AACLOUD_ID)) {
|
||||
logger.debug('User %s is not authorized to manage team %s.', authId, team.slug)
|
||||
throw new AutorizationError()
|
||||
}
|
||||
|
||||
let clone = await Clone.findOne( {
|
||||
try {
|
||||
let clone = await Clone.findOne({
|
||||
_id: id,
|
||||
active: true
|
||||
})
|
||||
|
||||
if(!isDefined(clone)){
|
||||
return "Clone not found."
|
||||
if (!isDefined(clone)) {
|
||||
return 'Clone not found.'
|
||||
}
|
||||
|
||||
let team = await teamQuery(context.authorization, clone.teamId)
|
||||
clone = await cloneDetails(context.userId, team.data.data.teams_TeamById, clone)
|
||||
|
||||
logger.debug('UpdateExecutionSummary -> Removing Clone from Kubernates.')
|
||||
await removeKuberneteClone(clone)
|
||||
|
||||
const query = { _id: id }
|
||||
const options = { new: true }
|
||||
const update = {
|
||||
@@ -68,23 +57,22 @@ const resolve = async (parent,
|
||||
combinedProfitsA: combinedProfitsA,
|
||||
combinedProfitsB: combinedProfitsB,
|
||||
assetA: assetA,
|
||||
assetB: assetB,
|
||||
active: false
|
||||
assetB: assetB
|
||||
}
|
||||
|
||||
logger.debug('UpdateExecutionSummary -> Updating clone on database.')
|
||||
await Clone.update(query, update, options, (err, clone) => {
|
||||
if (err){
|
||||
logger.error('Error updating execution summary on the database. %s', err.stack)
|
||||
throw err
|
||||
} else {
|
||||
logger.debug('Execution summary updated.')
|
||||
sucessful = true
|
||||
}
|
||||
})
|
||||
|
||||
if(sucessful)
|
||||
return 'Execution summary updated.'
|
||||
return new Promise((res, rej) => {
|
||||
Clone.update(query, update, options, (err, clone) => {
|
||||
if (err) {
|
||||
logger.error('Error updating execution summary on the database. %s', err.stack)
|
||||
rej(err)
|
||||
return
|
||||
}
|
||||
logger.debug('Execution summary updated.')
|
||||
res('Execution summary updated.')
|
||||
})
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Error updating the execution summary. %s', error.stack)
|
||||
@@ -92,7 +80,7 @@ const resolve = async (parent,
|
||||
}
|
||||
}
|
||||
|
||||
const mutation = {
|
||||
const UpdateExecutionSummary = {
|
||||
updateExecutionSummary: {
|
||||
type: GraphQLString,
|
||||
args,
|
||||
@@ -100,4 +88,4 @@ const mutation = {
|
||||
}
|
||||
}
|
||||
|
||||
export default mutation
|
||||
export default UpdateExecutionSummary
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
GraphQLObjectType
|
||||
} from 'graphql'
|
||||
|
||||
import { GraphQLObjectType } from 'graphql'
|
||||
import AddCloneMutation from './AddCloneMutation'
|
||||
import RemoveCloneMutation from './RemoveCloneMutation'
|
||||
import UpdateExecutionSummary from './UpdateExecutionSummary'
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { GraphQLNonNull, GraphQLID } from 'graphql'
|
||||
import { OperationsError } from '../../errors'
|
||||
import { GetClonesType } from '../types'
|
||||
import { Clone } from '../../models'
|
||||
import logger from '../../config/logger'
|
||||
import mongoose from 'mongoose'
|
||||
import { isDefined } from '../../config/utils'
|
||||
|
||||
const args = {
|
||||
cloneId: { type: new GraphQLNonNull(GraphQLID) }
|
||||
}
|
||||
|
||||
const resolve = async(parent, { cloneId }, context) => {
|
||||
logger.debug('Get Clone -> Entering Fuction.')
|
||||
|
||||
try {
|
||||
if (mongoose.Types.ObjectId.isValid(cloneId)) {
|
||||
let existingClone = await Clone.findOne({
|
||||
_id: cloneId
|
||||
})
|
||||
|
||||
if (isDefined(existingClone)) {
|
||||
return {
|
||||
id: cloneId,
|
||||
teamId: existingClone.teamId,
|
||||
botId: existingClone.botId
|
||||
}
|
||||
} else {
|
||||
throw new Error('Clone not found.')
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invalid Clone ID.')
|
||||
}
|
||||
return clones
|
||||
} catch (err) {
|
||||
logger.error('Get Clone error: %s', err.stack)
|
||||
throw new OperationsError('There has been an error getting the clone. ' + err.message)
|
||||
}
|
||||
}
|
||||
|
||||
const GetClones = {
|
||||
getClone: {
|
||||
type: GetClonesType,
|
||||
args,
|
||||
resolve
|
||||
}
|
||||
}
|
||||
|
||||
export default GetClones
|
||||
@@ -1,12 +1,5 @@
|
||||
import {
|
||||
GraphQLList,
|
||||
GraphQLID
|
||||
} from 'graphql'
|
||||
|
||||
import {
|
||||
OperationsError
|
||||
} from '../../errors'
|
||||
|
||||
import { GraphQLList, GraphQLID } from 'graphql'
|
||||
import { OperationsError } from '../../errors'
|
||||
import { GetClonesType } from '../types'
|
||||
import { Clone } from '../../models'
|
||||
import logger from '../../config/logger'
|
||||
@@ -18,43 +11,41 @@ const args = {
|
||||
}
|
||||
|
||||
const resolve = async(parent, { cloneIdList }, context) => {
|
||||
logger.debug('Get Clones -> Entering Fuction. %j', cloneIdList)
|
||||
try{
|
||||
let clones = []
|
||||
for (var i = 0; i < cloneIdList.length; i++) {
|
||||
logger.debug('Get Clones -> Entering Fuction.')
|
||||
try {
|
||||
let clones = []
|
||||
for (var i = 0; i < cloneIdList.length; i++) {
|
||||
if (mongoose.Types.ObjectId.isValid(cloneIdList[i])) {
|
||||
let existingClone = await Clone.findOne({
|
||||
_id: cloneIdList[i]
|
||||
})
|
||||
|
||||
if(mongoose.Types.ObjectId.isValid(cloneIdList[i])){
|
||||
let existingClone = await Clone.findOne({
|
||||
_id: cloneIdList[i]
|
||||
})
|
||||
if (isDefined(existingClone)) {
|
||||
clones.push({
|
||||
id: cloneIdList[i],
|
||||
teamId: existingClone.teamId,
|
||||
botId: existingClone.botId
|
||||
})
|
||||
} else {
|
||||
clones.push({
|
||||
id: cloneIdList[i]
|
||||
})
|
||||
}
|
||||
} else {
|
||||
clones.push({
|
||||
id: cloneIdList[i]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if(isDefined(existingClone)){
|
||||
clones.push({
|
||||
id: cloneIdList[i],
|
||||
teamId: existingClone.teamId,
|
||||
botId: existingClone.botId
|
||||
})
|
||||
}else{
|
||||
clones.push({
|
||||
id: cloneIdList[i]
|
||||
})
|
||||
}
|
||||
} else {
|
||||
clones.push({
|
||||
id: cloneIdList[i]
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return clones
|
||||
} catch (err){
|
||||
return clones
|
||||
} catch (err) {
|
||||
logger.error('Get Clones error: %s', err.stack)
|
||||
throw new OperationsError('There has been an error getting the clones.')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const query = {
|
||||
const GetClones = {
|
||||
getClones: {
|
||||
type: new GraphQLList(GetClonesType),
|
||||
args,
|
||||
@@ -62,4 +53,4 @@ const query = {
|
||||
}
|
||||
}
|
||||
|
||||
export default query
|
||||
export default GetClones
|
||||
@@ -1,48 +1,55 @@
|
||||
import {
|
||||
GraphQLList
|
||||
} from 'graphql'
|
||||
|
||||
import {
|
||||
AuthentificationError,
|
||||
OperationsError
|
||||
} from '../../errors'
|
||||
|
||||
import { GraphQLList, GraphQLString } from 'graphql'
|
||||
import { AuthentificationError, OperationsError } from '../../errors'
|
||||
import { CloneType } from '../types'
|
||||
import { Clone } from '../../models'
|
||||
import logger from '../../config/logger'
|
||||
import getKuberneteClonePodStatus from '../../kubernetes/getClonePodStatus'
|
||||
import getKuberneteClonePodLogs from '../../kubernetes/getClonePodLogs'
|
||||
import cloneDetails from '../cloneDetails'
|
||||
import teamQuery from '../../graphQLCalls/teamQuery'
|
||||
|
||||
const args = {}
|
||||
|
||||
const resolve = async(parent, args, context) => {
|
||||
logger.debug('Retrieving History Clones.')
|
||||
try{
|
||||
if (!context.userId) {
|
||||
throw new AuthentificationError()
|
||||
}
|
||||
|
||||
let clones = await Clone.find({
|
||||
authId: context.userId,
|
||||
active: false
|
||||
}).sort({createDatetime: -1})
|
||||
|
||||
for (var i = 0; i < clones.length; i++) {
|
||||
let team = await teamQuery(context.authorization, clones[i].teamId)
|
||||
clones[i] = await cloneDetails(context.userId, team.data.data.teams_TeamById, clones[i])
|
||||
clones[i].botType = clones[i].kind // Only for listing we show the teams value
|
||||
}
|
||||
|
||||
return clones
|
||||
} catch (err){
|
||||
logger.error('Retrieving History Clones error: %s', err.stack)
|
||||
throw new OperationsError('There has been an error retrieving history clones.')
|
||||
}
|
||||
const args = {
|
||||
botType: { type: GraphQLString }
|
||||
}
|
||||
|
||||
const query = {
|
||||
const resolve = async(parent, { botType }, context) => {
|
||||
logger.debug('Retrieving History Clones.')
|
||||
try {
|
||||
if (!context.userId) {
|
||||
throw new AuthentificationError()
|
||||
}
|
||||
|
||||
let clones = await Clone.find({
|
||||
authId: context.userId,
|
||||
active: false
|
||||
}).sort({createDatetime: -1})
|
||||
|
||||
let cloneResponse = []
|
||||
let allUserBotsResponse = await teamQuery(context.authorization, null)
|
||||
let allUserBots = allUserBotsResponse.data.data.teams_FbByOwner.edges
|
||||
|
||||
for (var i = 0; i < clones.length; i++) {
|
||||
for (var j = 0; j < allUserBots.length; j++) {
|
||||
let bot = allUserBots[j].node
|
||||
if(clones[i].botId === bot.id){
|
||||
let clone = cloneDetails(bot, clones[i])
|
||||
cloneResponse.push(clone)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (botType) {
|
||||
var filtered = cloneResponse.filter((value) => value.botType === botType)
|
||||
return filtered
|
||||
} else {
|
||||
return cloneResponse
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Retrieving History Clones error: %s', err.stack)
|
||||
throw new OperationsError('There has been an error retrieving history clones.')
|
||||
}
|
||||
}
|
||||
|
||||
const HistoryClones = {
|
||||
historyClones: {
|
||||
type: new GraphQLList(CloneType),
|
||||
args,
|
||||
@@ -50,4 +57,4 @@ const query = {
|
||||
}
|
||||
}
|
||||
|
||||
export default query
|
||||
export default HistoryClones
|
||||
@@ -1,12 +1,5 @@
|
||||
import {
|
||||
GraphQLList
|
||||
} from 'graphql'
|
||||
|
||||
import {
|
||||
AuthentificationError,
|
||||
OperationsError
|
||||
} from '../../errors'
|
||||
|
||||
import { GraphQLList, GraphQLString, GraphQLBoolean } from 'graphql'
|
||||
import { AuthentificationError, OperationsError } from '../../errors'
|
||||
import { CloneType } from '../types'
|
||||
import { Clone } from '../../models'
|
||||
import logger from '../../config/logger'
|
||||
@@ -15,37 +8,60 @@ import getCloneLogs from '../../kubernetes/getClonePodLogs'
|
||||
import cloneDetails from '../cloneDetails'
|
||||
import teamQuery from '../../graphQLCalls/teamQuery'
|
||||
|
||||
const args = {}
|
||||
|
||||
const resolve = async(parent, args, context) => {
|
||||
logger.debug('List Clones -> Entering Fuction.')
|
||||
try{
|
||||
if (!context.userId) {
|
||||
throw new AuthentificationError()
|
||||
}
|
||||
|
||||
let clones = await Clone.find({
|
||||
authId: context.userId,
|
||||
active: true
|
||||
}).sort({createDatetime: -1})
|
||||
|
||||
for (var i = 0; i < clones.length; i++) {
|
||||
let team = await teamQuery(context.authorization, clones[i].teamId)
|
||||
clones[i] = await cloneDetails(context.userId, team.data.data.teams_TeamById, clones[i])
|
||||
let state = await getCloneStatus(clones[i].id)
|
||||
clones[i].state = state.substring(1, state.length-1)
|
||||
let lastLogs = await getCloneLogs(clones[i].id)
|
||||
clones[i].lastLogs = lastLogs.substring(1, lastLogs.length-1)
|
||||
clones[i].botType = clones[i].kind // Only for listing we show the teams value
|
||||
}
|
||||
return clones
|
||||
} catch (err){
|
||||
logger.error('List Clones error: %s', err.stack)
|
||||
throw new OperationsError('There has been an error listing the clones.')
|
||||
}
|
||||
const args = {
|
||||
botType: { type: GraphQLString },
|
||||
queryLogs: { type: GraphQLBoolean }
|
||||
}
|
||||
|
||||
const query = {
|
||||
const resolve = async (parent, { botType, queryLogs }, context) => {
|
||||
logger.debug('List Clones -> Entering Fuction.')
|
||||
try {
|
||||
if (!context.userId) {
|
||||
throw new AuthentificationError()
|
||||
}
|
||||
|
||||
let clones = await Clone.find({
|
||||
authId: context.userId,
|
||||
active: true
|
||||
}).sort({ createDatetime: -1 })
|
||||
|
||||
let cloneResponse = []
|
||||
let allUserBotsResponse = await teamQuery(context.authorization, null)
|
||||
let allUserBots = allUserBotsResponse.data.data.teams_FbByOwner.edges
|
||||
|
||||
for (var i = 0; i < clones.length; i++) {
|
||||
for (var j = 0; j < allUserBots.length; j++) {
|
||||
let bot = allUserBots[j].node
|
||||
if(clones[i].botId === bot.id){
|
||||
let clone = cloneDetails(bot, clones[i])
|
||||
cloneResponse.push(clone)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (queryLogs) {
|
||||
for (var i = 0; i < cloneResponse.length; i++) {
|
||||
let state = await getCloneStatus(cloneResponse[i].id)
|
||||
cloneResponse[i].state = state.substring(1, state.length - 1)
|
||||
|
||||
let lastLogs = await getCloneLogs(cloneResponse[i].id)
|
||||
cloneResponse[i].lastLogs = lastLogs.substring(1, lastLogs.length - 1)
|
||||
}
|
||||
}
|
||||
if (botType) {
|
||||
var filtered = cloneResponse.filter((value) => value.botType === botType)
|
||||
return filtered
|
||||
} else {
|
||||
return cloneResponse
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('List Clones error: %s', err.stack)
|
||||
throw new OperationsError('There has been an error listing the clones.')
|
||||
}
|
||||
}
|
||||
|
||||
const ListClones = {
|
||||
clones: {
|
||||
type: new GraphQLList(CloneType),
|
||||
args,
|
||||
@@ -53,4 +69,4 @@ const query = {
|
||||
}
|
||||
}
|
||||
|
||||
export default query
|
||||
export default ListClones
|
||||
@@ -1,17 +1,16 @@
|
||||
import {
|
||||
GraphQLObjectType
|
||||
} from 'graphql'
|
||||
|
||||
import { GraphQLObjectType } from 'graphql'
|
||||
import ListClones from './ListClones'
|
||||
import HistoryClones from './HistoryClones'
|
||||
import GetClones from './GetClones'
|
||||
import GetClone from './GetClone'
|
||||
|
||||
const RootQuery = new GraphQLObjectType({
|
||||
name: 'RootQueryType',
|
||||
fields: Object.assign(
|
||||
ListClones,
|
||||
HistoryClones,
|
||||
GetClones
|
||||
GetClones,
|
||||
GetClone
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -37,7 +37,9 @@ const CloneType = new GraphQLObjectType({
|
||||
teamName: {type: GraphQLString},
|
||||
botName: {type: GraphQLString},
|
||||
botAvatar: {type: GraphQLString},
|
||||
teamAvatar: {type: GraphQLString}
|
||||
teamAvatar: {type: GraphQLString},
|
||||
keyId: {type: GraphQLString},
|
||||
botSlug: {type: GraphQLString}
|
||||
})
|
||||
})
|
||||
export default CloneType
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
GraphQLObjectType,
|
||||
GraphQLID
|
||||
} from 'graphql'
|
||||
import { GraphQLObjectType, GraphQLID } from 'graphql'
|
||||
|
||||
const GetClonesType = new GraphQLObjectType({
|
||||
name: 'GetClones',
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
GraphQLInputObjectType,
|
||||
GraphQLBoolean,
|
||||
GraphQLNonNull
|
||||
} from 'graphql';
|
||||
} from 'graphql'
|
||||
|
||||
const Type = new GraphQLInputObjectType({
|
||||
name: 'CloneInput',
|
||||
@@ -23,8 +23,11 @@ const Type = new GraphQLInputObjectType({
|
||||
month: { type: GraphQLInt },
|
||||
interval: { type: GraphQLInt },
|
||||
processName: { type: new GraphQLNonNull(GraphQLString) },
|
||||
botType: { type: new GraphQLNonNull(GraphQLString) }
|
||||
})
|
||||
});
|
||||
botType: { type: new GraphQLNonNull(GraphQLString) },
|
||||
keyId: { type: GraphQLString },
|
||||
timePeriod: { type: GraphQLString },
|
||||
exchangeName: { type: GraphQLString }
|
||||
})
|
||||
})
|
||||
|
||||
export default Type;
|
||||
export default Type
|
||||
@@ -2,4 +2,4 @@ import CloneInputType from './Clone'
|
||||
|
||||
export {
|
||||
CloneInputType
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
var assert = require('chai').assert
|
||||
const axios = require('axios')
|
||||
const { activeCloneList } = require('./activeCloneList')
|
||||
require('dotenv').config()
|
||||
|
||||
describe('Admin clones Clones', function () {
|
||||
it('Remove clones', async function () {
|
||||
this.timeout(300000)
|
||||
var error
|
||||
try {
|
||||
var clones = await getClones()
|
||||
for (let clone of clones) {
|
||||
await removeClone(clone.id, clone.botName)
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Error: ' + err)
|
||||
error = err
|
||||
}
|
||||
assert.equal(error, undefined)
|
||||
})
|
||||
it('Add COSS Live Indicator Clones', async function () {
|
||||
this.timeout(300000)
|
||||
var error
|
||||
var clones = activeCloneList("Coss")
|
||||
try {
|
||||
for (let clone of clones) {
|
||||
await createClone(clone)
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Error: ' + err)
|
||||
error = err
|
||||
}
|
||||
assert.equal(error, undefined)
|
||||
})
|
||||
it('Add POLONIEX Live Indicator Clones', async function () {
|
||||
this.timeout(300000)
|
||||
var error
|
||||
var clones = activeCloneList("Poloniex")
|
||||
try {
|
||||
for (let clone of clones) {
|
||||
await createClone(clone)
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Error: ' + err)
|
||||
error = err
|
||||
}
|
||||
assert.equal(error, undefined)
|
||||
})
|
||||
})
|
||||
|
||||
async function getClones() {
|
||||
try {
|
||||
const clones = await axios({
|
||||
url: process.env.GATEWAY_ENDPOINT,
|
||||
method: 'post',
|
||||
data: {
|
||||
query: `
|
||||
query {
|
||||
operations_Clones(queryLogs: false) {
|
||||
id
|
||||
botName
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
headers: {
|
||||
authorization: 'Bearer ' + process.env.ACCESS_TOKEN
|
||||
}
|
||||
})
|
||||
|
||||
console.log('getClones ok ')
|
||||
return clones.data.data.operations_Clones
|
||||
} catch (error) {
|
||||
console.log('getClones error: ' + error.response.data.errors[0])
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function removeClone(cloneId, botName) {
|
||||
try {
|
||||
await axios({
|
||||
url: process.env.GATEWAY_ENDPOINT,
|
||||
method: 'post',
|
||||
data: {
|
||||
query: `
|
||||
mutation ($id: ID!){
|
||||
operations_RemoveClone(
|
||||
id: $id
|
||||
)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: cloneId
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
authorization: 'Bearer ' + process.env.ACCESS_TOKEN
|
||||
}
|
||||
})
|
||||
|
||||
console.log('removeClone ok: ' + botName)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.log('removeClone error: ' + error.response.data.errors[0])
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function createClone(clone) {
|
||||
try {
|
||||
const clones = await axios({
|
||||
url: process.env.GATEWAY_ENDPOINT,
|
||||
method: 'post',
|
||||
data: {
|
||||
query: `
|
||||
mutation ($clone: operations_CloneInput){
|
||||
operations_AddClone(
|
||||
clone: $clone
|
||||
) {
|
||||
id
|
||||
teamName
|
||||
botName
|
||||
processName
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
clone: clone
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
authorization: 'Bearer ' + process.env.ACCESS_TOKEN
|
||||
}
|
||||
})
|
||||
|
||||
console.log('createClone id: ' + clones.data.data.operations_AddClone.id + ', botId: ' + clone.botId)
|
||||
return clones.data.data.operations_AddClone
|
||||
} catch (error) {
|
||||
console.log('createClone error: ' + error.response.data.errors[0])
|
||||
throw error
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user