Merge pull request #65 from Superalgos/develop

Development progress update for the demo node.
This commit is contained in:
benme authored and GitHub committed 2019-04-10 17:43:01 +02:00
commit 9ec2db7d45
56 files changed
+5015 -2463

No files matched your search

+12
View File
@@ -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
+155 -155
View File
@@ -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
View File
@@ -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": {
+9 -9
View File
@@ -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
};
}
+6 -4
View File
@@ -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
}
+16
View File
@@ -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
}
+2 -2
View File
@@ -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
};
}
-34
View File
@@ -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
}
`;
+35 -5
View File
@@ -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
View File
@@ -1 +0,0 @@
+54 -16
View File
@@ -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
View File
@@ -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>
)
+3 -3
View File
@@ -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
View File
@@ -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)
+6 -6
View File
@@ -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
+9 -13
View File
@@ -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 -4
View File
@@ -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 {
+19 -11
View File
@@ -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)
+8 -12
View File
@@ -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'
/>
)
}
+19 -20
View File
@@ -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
View File
@@ -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>
+1903
View File
File diff suppressed because it is too large. Load diff
+17
View File
@@ -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"
}
}
+27
View File
@@ -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
-2
View File
@@ -3,8 +3,6 @@
# dependencies
/node_modules
/src/config/clone-deployment.json
# testing
/coverage
+49 -2
View File
@@ -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/).
+1494 -1368
View File
File diff suppressed because it is too large. Load diff
+13 -13
View File
@@ -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"
}
}
+31
View File
@@ -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"
}
}
}
}
}
+6 -6
View File
@@ -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
-1
View File
@@ -6,7 +6,6 @@ export const ONE_MONTH = 'oneMonth'
export const NO_TIME = 'noTime'
export const FIXED_INTERVAL = 'fixedInterval'
export const CloneModeEnum = [
LIVE,
BACKTEST,
-3
View File
@@ -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)
}
}
+28
View File
@@ -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
+27 -24
View File
@@ -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
View File
@@ -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())
+163 -75
View File
@@ -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
+17 -11
View File
@@ -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
+18 -11
View File
@@ -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
+7 -9
View File
@@ -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
View File
@@ -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
}
})
+13 -49
View File
@@ -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 -4
View File
@@ -1,7 +1,4 @@
import {
GraphQLSchema
} from 'graphql'
import { GraphQLSchema } from 'graphql'
import Mutation from './mutations'
import RootQuery from './queries'
+56 -35
View File
@@ -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 -4
View File
@@ -1,7 +1,4 @@
import {
GraphQLObjectType
} from 'graphql'
import { GraphQLObjectType } from 'graphql'
import AddCloneMutation from './AddCloneMutation'
import RemoveCloneMutation from './RemoveCloneMutation'
import UpdateExecutionSummary from './UpdateExecutionSummary'
+49
View File
@@ -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
+32 -41
View File
@@ -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
+45 -38
View File
@@ -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
+55 -39
View File
@@ -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
+4 -5
View File
@@ -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
)
})
+3 -1
View File
@@ -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 -4
View File
@@ -1,7 +1,4 @@
import {
GraphQLObjectType,
GraphQLID
} from 'graphql'
import { GraphQLObjectType, GraphQLID } from 'graphql'
const GetClonesType = new GraphQLObjectType({
name: 'GetClones',
+8 -5
View File
@@ -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
+1 -1
View File
@@ -2,4 +2,4 @@ import CloneInputType from './Clone'
export {
CloneInputType
};
}
+142
View File
@@ -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
}
}