Merge pull request #1 from AAVikings/develop

Preparing to remove develop branch
This commit is contained in:
z@tOr authored and GitHub committed 2019-01-29 18:58:36 -03:00
commit cce4b384c5
70 files changed
+10373 -1

No files matched your search

+4 -1
View File
@@ -1 +1,4 @@
# OperationsModule
## AA Operations Module
This module allow users to control the bot instance on the virtual machine.
+21
View File
@@ -0,0 +1,21 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+46
View File
@@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at feedback at superalgos dot org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+45
View File
@@ -0,0 +1,45 @@
# Contributing
We are thrilled you are interested in contributing to this repository. Your help may be essential to the project's success and we want you in!
Superalgos is a collective project, currently at a very early stage. Although this repository is not open source software yet, we do welcome contributions from the community.
Contributions are deemed _voluntary work_. There might be incentives in place for certain kinds of contributions. Contact us for more information in this regard.
In any case, contributors agree to provide an express grant of patent rights for their contributions. Submitting Pull Requests to this repository represents an express agreement with the terms of our Balanced Contributor Intellectual Property Agreement. This agreement is a direct implementation of GitHub's [Balanced Employee Intellectual Property Agreement 1.0.1](https://github.com/github/balanced-employee-ip-agreement/blob/master/Balanced_Employee_IP_Agreement.md).
# Balanced Contributor Intellectual Property Agreement 1.0.1
This BALANCED CONTRIBUTOR INTELLECTUAL PROPERTY AGREEMENT is between person or team submitting Pull Requests to this repository and Superalgos.org ("Project").
**What is this?** This is the Project's Intellectual Property Agreement ("Agreement"). If you've worked in the technology space before, there's a good chance that you've run across one or more of these in the past. This document is the official, entire and exclusive agreement on what intellectual property ("IP") is yours, and what belongs to the Project.
**Why is this?** The Project needs to be clear on what IP it owns and has rights to. Its customers, contributors, and investors depend on the Project having the legal rights to the products and services it is providing so that the Project can continue operating and doing business.
The Project also believes that it's important to be clear on what it doesn't own. The Project doesn't want you looking over your shoulder every time you work on something personal or worrying that the Project will someday seize your open source non-lethal mousetrap simulation software. In other words, the Project isn't interested in appropriating your personal projects.
**Read this.** Please read this document and be sure you understand it before you submit any Pull Requests. Due to legal risk and corporate obligations, the Project cannot, by and large, negotiate its terms. If you feel you have a particular circumstance that keeps you from signing, please let the Project's legal department ("Legal") know. And, of course, you're always free and encouraged to get your own legal counsel to explain anything you're not clear on.
Cool? Then, by submitting your first Pull Request, and as a condition of your contribution, you agree to the following:
1. **What the Project owns.** The Project owns any IP ("Project IP") that you create, or help create _as its contributor_, but only if it meets one or more of these additional conditions: The IP was (i) related to an existing or prospective Project product or service at the time you developed, invented, or created it, (ii) developed for use by the Project, or (iii) developed or promoted with existing Project IP or with the Project's endorsement. You hereby grant and assign, and will grant and assign, to the Project all rights and interests in all Project IP.
"Project IP" includes concepts, designs, developments, discoveries, ideas, improvements, inventions, trade secrets, trademarks, and works of authorship.
2. **What the Project doesn't own.** If you create IP that doesn't fit into one of the categories listed above, the Project doesn't own it. In other words, the Project doesn't own concepts, designs, developments, discoveries, ideas, improvements, inventions, trade secrets, trademarks, or works of authorship that you develop, invent, or create that do not meet any of the conditions in Section 1.
3. **Contribution of your IP to Project projects.** If you include your own IP – such as IP you created prior to working for the Project – into a Project product or service, it's still yours, of course, but you grant the Project a non-exclusive, irrevocable, fully paid-up, royalty-free, perpetual, sublicensable, transferable, worldwide license to use it without restriction in any way or implementation, in modified form, or as is, by itself, or incorporated into another product or service ("License"). If you include your name in any project over which the Project would have rights under this Agreement, such as in a copyright notice or a comment in code or documentation, then the License covers the rights associated with that use of your name as well.
4. **Your contributions to non-Project projects.** The Project recognizes that you may be engaged in work that requires you to submit IP to entities other than the Project, such as open source projects used by the Project. Please make sure that Legal is aware of what you're working on so that Legal can help with any licensing issues. If any project asks you to sign a contribution agreement, you should check with Legal before doing so. These agreements may be legally binding on the Project and so should be run through Legal first.
5. **Voluntary Works** All Project IP that you are involved with or create as part of your work for the Project is voluntary work which does not require compensation. There might be incentives in place for certain kinds of contributions. Feel free to contact the Project to learn more.
6. **No conflicts.** You agree that you don't have any outstanding agreements or obligations that conflict with those in this Agreement, and that you won't enter into conflicting agreements in the future.
7. **IP protection.** The Project might someday need to show the work that went into the development of IP that it uses or has used, or to make government filings to establish that it owns the IP or has rights over it. To help in those situations, you agree not to destroy any records you maintain relating to the development of any Project IP or IP under License (together, "All IP"), and, if the Project asks, to provide those records to the Project. You agree to help the Project secure and defend its rights in All IP. For your help the Project will compensate you at a reasonable rate. If the Project is unable to secure your help, you authorize the Project to act on your behalf (as your agent and attorney-in-fact) in securing all rights related to All IP.
8. **Confidentiality.** As an contributor or contractor of the Project, you will have access to sensitive confidential information that is important to the Project's business. You are responsible for keeping this information confidential, including after you end your work for the Project.
"Confidential Information" includes non-public technical details about our products or services, financial information, business strategies and forecasts, customer data, or any other information, data or know-how that is valuable to the Project because it is not publicly known.
The Project's Confidential Information is sensitive and we expect that you'll treat it as such. You agree to only use the Project's Confidential Information for the purpose of performing your contribution and for the benefit of the Project. You will do your best to keep Confidential Information secret. If you are not sure if something is Confidential Information, you should assume that it is, until you can confirm otherwise.
+39
View File
@@ -0,0 +1,39 @@
COPYRIGHT NOTICE
© Copyright 2018 Superalgos.org
LICENSE NOTICE
Superalgos is a collaborative project currently at a very early stage. Because certain aspects of
the project are still not clearly defined, we have chosen to --temporarily-- grant a restrictive
license for the use of this repository, until those aspects are clarified and a proper open source
license can be granted.
Superalgos.org is publishing the code of this software for several reasons. However, it shall not be considered free software yet. Superalgos is a collaborative project in the crypto space and this
software is still proprietary software.
The reasons for opening up the code of this software at this stage are the following:
* We believe that transparency is paramount to achieving our goals, so we want everyone to
be able to understand what this software does and how. Opening up the code of this software
allows anyone to take a close look.
* We welcome contributions from the community. Contributions are regarded as voluntary work.
There may be incentives in place for certain kinds of contributions. Contact us to learn more.
Contributors agree to provide an express grant of patent rights for their contributions.
LICENSE WORDING
Superalgos.org grants a limited license for the following uses:
1. CODE REVIEW AND AUDITS. You are granted permission to clone this repository to analyze,
review or audit the code.
2. COLLABORATING WITH Superalgos.org. You are granted permission to fork this repository
with the sole purpose of correcting bugs and developing functionality in agreement with
Superalgos.org. By contributing to this repository you explicitely agree to the terms
detailed in CONTRIBUTING.MD.
PLEASE SEE CONTRIBUTING.MD FOR MORE DETAILS ON THE IMPLICATIONS SURROUNDING YOUR CONTRIBUTIONS TO THIS REPOSITORY.
+4
View File
@@ -0,0 +1,4 @@
## AA Operations Web Client
This client allows the users to interact via web browser.
+841
View File
@@ -0,0 +1,841 @@
{
"name": "@superalgos/operations-client",
"version": "0.0.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@babel/runtime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz",
"integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==",
"requires": {
"regenerator-runtime": "0.12.1"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
}
}
},
"@material-ui/core": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.2.2.tgz",
"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"
},
"dependencies": {
"@babel/runtime": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.2.tgz",
"integrity": "sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg==",
"requires": {
"regenerator-runtime": "0.12.1"
}
}
}
},
"@material-ui/icons": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-3.0.1.tgz",
"integrity": "sha512-1kNcxYiIT1x8iDPEAlgmKrfRTIV8UyK6fLVcZ9kMHIKGWft9I451V5mvSrbCjbf7MX1TbLWzZjph0aVCRf9MqQ==",
"requires": {
"@babel/runtime": "7.0.0",
"recompose": "0.29.0"
},
"dependencies": {
"recompose": {
"version": "0.29.0",
"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"
}
}
}
},
"@types/async": {
"version": "2.0.49",
"resolved": "http://registry.npmjs.org/@types/async/-/async-2.0.49.tgz",
"integrity": "sha512-Benr3i5odUkvpFkOpzGqrltGdbSs+EVCkEBGXbuR7uT0VzhXKIkhem6PDzHdx5EonA+rfbB3QvP6aDOw5+zp5Q==",
"optional": true
},
"@types/jss": {
"version": "9.5.6",
"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"
}
},
"@types/prop-types": {
"version": "15.5.6",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.5.6.tgz",
"integrity": "sha512-ZBFR7TROLVzCkswA3Fmqq+IIJt62/T7aY/Dmz+QkU7CaW2QFqAitCE8Ups7IzmGhcN1YWMBT4Qcoc07jU9hOJQ=="
},
"@types/react": {
"version": "16.4.16",
"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/react-transition-group": {
"version": "2.0.14",
"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/zen-observable": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz",
"integrity": "sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg=="
},
"apollo-cache": {
"version": "1.1.17",
"resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.1.17.tgz",
"integrity": "sha512-7sp24n2HZO4vXgTaKNomLyIfGxG4gDdDkBB0jkRzRi7HhnKmfwhiF/RCiKNbgLdrPX151INdls0KwIVliD0dHQ==",
"requires": {
"apollo-utilities": "1.0.21"
}
},
"apollo-client": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.4.2.tgz",
"integrity": "sha512-g1z23umaVSoKLj9xNl0aAnk2KBF4JeBi7MeKFc9CGTixH7TkqeQUQtxcjrC7j2h4KmDbuhOAHOFUGf8YshN+ag==",
"requires": {
"@types/async": "2.0.49",
"@types/zen-observable": "0.8.0",
"apollo-cache": "1.1.17",
"apollo-link": "1.2.3",
"apollo-link-dedup": "1.0.10",
"apollo-utilities": "1.0.21",
"symbol-observable": "1.2.0",
"zen-observable": "0.8.9"
}
},
"apollo-link": {
"version": "1.2.3",
"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-link-dedup": {
"version": "1.0.10",
"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-utilities": {
"version": "1.0.21",
"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"
}
},
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
},
"brcast": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/brcast/-/brcast-3.0.1.tgz",
"integrity": "sha512-eI3yqf9YEqyGl9PCNTR46MGvDylGtaHjalcz6Q3fAPnP/PhpKkkve52vFdfGpwp4VUvK6LUr4TQN+2stCrEwTg=="
},
"change-emitter": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz",
"integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
},
"classnames": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz",
"integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q=="
},
"core-js": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
},
"css-vendor": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-0.3.8.tgz",
"integrity": "sha1-ZCHP0wNM5mT+dnOXL9ARn8KJQfo=",
"requires": {
"is-in-browser": "1.1.3"
}
},
"csstype": {
"version": "2.5.7",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-2.5.7.tgz",
"integrity": "sha512-Nt5VDyOTIIV4/nRFswoCKps1R5CD1hkiyjBE9/thNaNZILLEviVw9yWQw15+O+CpNjQKB/uvdcxFFOrSflY3Yw=="
},
"debounce": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz",
"integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg=="
},
"deepmerge": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
"integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA=="
},
"dom-helpers": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.3.1.tgz",
"integrity": "sha512-2Sm+JaYn74OiTM2wHvxJOo3roiq/h25Yi69Fqk269cNUwIXsCvATB6CRSFC9Am/20G2b28hGv/+7NiWydIrPvg=="
},
"dom-walk": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz",
"integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=",
"dev": true
},
"encoding": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
"requires": {
"iconv-lite": "0.4.24"
}
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
},
"fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
"fbjs": {
"version": "0.8.17",
"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"
}
},
"fbjs-css-vars": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.1.tgz",
"integrity": "sha512-IM+v/C40MNZWqsLErc32e0TyIk/NhkkQZL0QmjBh6zi1eXv0/GeVKmKmueQX7nn9SXQBQbTUcB8zuexIF3/88w=="
},
"fclone": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz",
"integrity": "sha1-EOhdo4v+p/xZk0HClu4ddyZu5kA="
},
"global": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz",
"integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=",
"dev": true,
"requires": {
"min-document": "2.19.0",
"process": "0.5.2"
}
},
"graphql": {
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.2.tgz",
"integrity": "sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==",
"requires": {
"iterall": "1.2.2"
}
},
"graphql-tag": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.0.tgz",
"integrity": "sha512-9FD6cw976TLLf9WYIUPCaaTpniawIjHWZSwIRZSjrfufJamcXbVVYfN2TWvJYbw0Xf2JjYbl1/f2+wDnBVw3/w=="
},
"history": {
"version": "4.7.2",
"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"
},
"dependencies": {
"warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"requires": {
"loose-envify": "1.4.0"
}
}
}
},
"hoist-non-react-statics": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
},
"hyphenate-style-name": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz",
"integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es="
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"requires": {
"safer-buffer": "2.1.2"
}
},
"indefinite-observable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-1.0.1.tgz",
"integrity": "sha1-CZFUI8yNb36xy3iCrRNGM8mm7cM=",
"requires": {
"symbol-observable": "1.0.4"
},
"dependencies": {
"symbol-observable": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz",
"integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0="
}
}
},
"invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"requires": {
"loose-envify": "1.4.0"
}
},
"is-in-browser": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
"integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
},
"is-plain-object": {
"version": "2.0.4",
"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"
},
"dependencies": {
"isobject": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
}
}
},
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
},
"isomorphic-fetch": {
"version": "2.2.1",
"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"
}
},
"iterall": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz",
"integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA=="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"jss": {
"version": "9.8.7",
"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"
},
"dependencies": {
"warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"requires": {
"loose-envify": "1.4.0"
}
}
}
},
"jss-camel-case": {
"version": "6.1.0",
"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"
}
},
"jss-default-unit": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/jss-default-unit/-/jss-default-unit-8.0.2.tgz",
"integrity": "sha512-WxNHrF/18CdoAGw2H0FqOEvJdREXVXLazn7PQYU7V6/BWkCV0GkmWsppNiExdw8dP4TU1ma1dT9zBNJ95feLmg=="
},
"jss-global": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/jss-global/-/jss-global-3.0.0.tgz",
"integrity": "sha512-wxYn7vL+TImyQYGAfdplg7yaxnPQ9RaXY/cIA8hawaVnmmWxDHzBK32u1y+RAvWboa3lW83ya3nVZ/C+jyjZ5Q=="
},
"jss-nested": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/jss-nested/-/jss-nested-6.0.1.tgz",
"integrity": "sha512-rn964TralHOZxoyEgeq3hXY8hyuCElnvQoVrQwKHVmu55VRDd6IqExAx9be5HgK0yN/+hQdgAXQl/GUrBbbSTA==",
"requires": {
"warning": "3.0.0"
},
"dependencies": {
"warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
"integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
"requires": {
"loose-envify": "1.4.0"
}
}
}
},
"jss-props-sort": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/jss-props-sort/-/jss-props-sort-6.0.0.tgz",
"integrity": "sha512-E89UDcrphmI0LzmvYk25Hp4aE5ZBsXqMWlkFXS0EtPkunJkRr+WXdCNYbXbksIPnKlBenGB9OxzQY+mVc70S+g=="
},
"jss-vendor-prefixer": {
"version": "7.0.0",
"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"
}
},
"keycode": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
"integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ="
},
"lodash.flowright": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/lodash.flowright/-/lodash.flowright-3.5.0.tgz",
"integrity": "sha1-K1//OZcW1+fcVyT+k0n2cGUYTWc="
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"loose-envify": {
"version": "1.4.0",
"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"
}
},
"luxon": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.5.0.tgz",
"integrity": "sha512-9IfJ0a5jA3poJSQnamaoOdPG0ulYk1JUI5IYmEdUhUc8dto4t6TVTu+oIM7fpSYy01GwUAA8kGUZMVe7Qx9Y+Q=="
},
"material-ui-pickers": {
"version": "1.0.1",
"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"
}
},
"min-document": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
"integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
"dev": true,
"requires": {
"dom-walk": "0.1.1"
}
},
"node-fetch": {
"version": "1.7.3",
"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"
}
},
"normalize-scroll-left": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.1.2.tgz",
"integrity": "sha512-F9YMRls0zCF6BFIE2YnXDRpHPpfd91nOIaNdDgrx5YMoPLo8Wqj+6jNXHQsYBavJeXP4ww8HCt0xQAKc5qk2Fg=="
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"path-to-regexp": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz",
"integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=",
"requires": {
"isarray": "0.0.1"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
}
}
},
"popper.js": {
"version": "1.14.4",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.4.tgz",
"integrity": "sha1-juwdj/AqWjoVLdQ0FKFce3n9abY="
},
"process": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz",
"integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=",
"dev": true
},
"promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"requires": {
"asap": "2.0.6"
}
},
"prop-types": {
"version": "15.6.2",
"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"
}
},
"react": {
"version": "16.6.1",
"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"
}
},
"react-apollo": {
"version": "2.2.4",
"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"
},
"dependencies": {
"core-js": {
"version": "2.5.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
"integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw=="
},
"fbjs": {
"version": "1.0.0",
"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"
}
},
"hoist-non-react-statics": {
"version": "3.0.1",
"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-dom": {
"version": "16.6.1",
"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"
}
},
"react-event-listener": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.6.4.tgz",
"integrity": "sha512-t7VSjIuUFmN+GeyKb+wm025YLeojVB85kJL6sSs0wEBJddfmKBEQz+CNBZ2zBLKVWkPy/fZXM6U5yvojjYBVYQ==",
"requires": {
"@babel/runtime": "7.0.0",
"prop-types": "15.6.2",
"warning": "4.0.2"
}
},
"react-hot-loader": {
"version": "4.3.11",
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.3.11.tgz",
"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"
}
},
"react-is": {
"version": "16.5.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.5.2.tgz",
"integrity": "sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ=="
},
"react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-number-format": {
"version": "4.0.6",
"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"
}
},
"react-router": {
"version": "4.3.1",
"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"
}
},
"react-router-dom": {
"version": "4.3.1",
"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"
}
},
"react-text-mask": {
"version": "5.4.1",
"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"
}
},
"react-transition-group": {
"version": "2.5.0",
"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"
}
},
"recompose": {
"version": "0.30.0",
"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"
}
},
"regenerator-runtime": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz",
"integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg=="
},
"resolve-pathname": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz",
"integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"scheduler": {
"version": "0.11.0",
"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"
}
},
"setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
},
"shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
"dev": true
},
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
},
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
},
"ua-parser-js": {
"version": "0.7.18",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz",
"integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA=="
},
"value-equal": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz",
"integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw=="
},
"warning": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.2.tgz",
"integrity": "sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug==",
"requires": {
"loose-envify": "1.4.0"
}
},
"whatwg-fetch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
},
"zen-observable": {
"version": "0.8.9",
"resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.9.tgz",
"integrity": "sha512-Y9kPzjGvIZ5jchSlqlCpBW3I82zBBL4z+ulXDRVA1NwsKzjt5kwAi+gOYIy0htNkfuehGZZtP5mRXHRV6TjDWw=="
},
"zen-observable-ts": {
"version": "0.8.10",
"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"
}
}
}
}
+36
View File
@@ -0,0 +1,36 @@
{
"name": "@superalgos/operations-client",
"version": "0.0.3",
"description": "Superalgos Operations client-side web app",
"main": "src/index.js",
"dependencies": {
"@material-ui/core": "^3.2.2",
"@material-ui/icons": "^3.0.1",
"apollo-client": "^2.4.2",
"classnames": "^2.2.6",
"graphql": "^14.0.2",
"graphql-tag": "^2.10.0",
"luxon": "^1.4.4",
"material-ui-pickers": "^1.0.1",
"react": "^16.6.1",
"react-apollo": "^2.2.4",
"react-dom": "^16.6.1",
"react-number-format": "^4.0.6",
"react-router-dom": "^4.3.1"
},
"devDependencies": {
"react-hot-loader": "^4.3.11"
},
"scripts": {
"start": "set PORT=3002 && react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}
+42
View File
@@ -0,0 +1,42 @@
import gql from 'graphql-tag';
import { clone } from '../Fragments';
const OPERATIONS_ADD_CLONE = gql`
mutation Operations_AddClone($clone: operations_CloneInput!) {
operations_AddClone(clone: $clone) {
...clone
}
}
${clone}
`;
const OPERATIONS_LIST_CLONES = gql`
query Operations_Clones{
operations_Clones {
...clone
}
}
${clone}
`;
const OPERATIONS_HISTORY_CLONES = gql`
query Operations_HistoryClones{
operations_HistoryClones {
...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
};
+7
View File
@@ -0,0 +1,7 @@
import clones from './clones';
import teams from './teams';
export {
clones,
teams
};
+21
View File
@@ -0,0 +1,21 @@
import gql from 'graphql-tag';
export const GET_ALL_TEAMS_QUERY = gql`
query Teams_FbByTeamMember {
teams_FbByTeamMember {
id
name
slug
fb {
id
name
slug
kind
}
}
}
`
export default {
GET_ALL_TEAMS_QUERY
};
+33
View File
@@ -0,0 +1,33 @@
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
kind
teamName
botName
botAvatar
teamAvatar
}
`;
+6
View File
@@ -0,0 +1,6 @@
import { clone } from './clone'
export {
clone
};
+1
View File
@@ -0,0 +1 @@
+15
View File
@@ -0,0 +1,15 @@
const types = [ 'Live', 'Competition', 'Backtest' ]
const startModes = [ 'All Months', 'One Month', 'No Time' ]
const availableMonths = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"]
const exchanges = [
{
id: '1',
name: 'Poloniex'
}
]
export { types, exchanges, startModes, availableMonths }
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+23
View File
@@ -0,0 +1,23 @@
import React, { Component } 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'
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} />
</Switch>
</MuiPickersUtilsProvider>
)
export default App
+71
View File
@@ -0,0 +1,71 @@
import { createMuiTheme } from '@material-ui/core/styles'
export const theme = createMuiTheme({
palette: {
primary: { main: '#303036' }, // DARK.
secondary: { main: '#CC5835' } // RUSTED_RED.
},
typography: {
fontFamily: '"Saira","Saira Condenses", sans-serif',
useNextVariants: true
},
overrides: {
Paper: {
root: {
backgroundColor: '#ffffff'
},
elevation: 0,
square: true
}
}
})
export const globalStyles = theme => ({
'@global': {
body: {
backgroundColor: theme.palette.common.white
}
},
appBar: {
position: 'relative'
},
toolbarTitle: {
flex: 1
},
layout: {
width: 'auto',
marginLeft: theme.spacing.unit * 3,
marginRight: theme.spacing.unit * 3,
[theme.breakpoints.up(900 + theme.spacing.unit * 3 * 2)]: {
width: 900,
marginLeft: 'auto',
marginRight: 'auto'
}
},
heroContent: {
maxWidth: 600,
margin: '0 auto',
padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px`
},
cardHeader: {
backgroundColor: theme.palette.grey[200]
},
cardPricing: {
display: 'flex',
justifyContent: 'center',
alignItems: 'baseline',
marginBottom: theme.spacing.unit * 2
},
cardActions: {
[theme.breakpoints.up('sm')]: {
paddingBottom: theme.spacing.unit * 2
}
},
footer: {
marginTop: theme.spacing.unit * 8,
borderTop: `1px solid ${theme.palette.divider}`,
padding: `${theme.spacing.unit * 6}px 0`
}
})
export default theme
+83
View File
@@ -0,0 +1,83 @@
export function toLocalTime (epoch) {
const localDate = new Date(0)
localDate.setUTCSeconds(epoch)
const options = { year: 'numeric', month: 'long', day: '2-digit' }
return (
`${localDate.toLocaleDateString(options)
} at ${
localDate.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}`
)
}
export function toUTCTimestamp (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
}
}
export function isDefined (d) {
return d !== null && typeof d !== 'undefined'
}
export const isArray = Array.isArray
export function isObject (d) {
return isDefined(d) && typeof d === 'object' && !Array.isArray(d)
}
export function isEmpty (obj) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) return false
}
return true
}
export function validObject (object, ...keys) {
return keys.reduce((a, b) => (a || { })[b], object) !== undefined
}
export function mergeDeep (target, ...sources) {
if (!sources.length) return target
const source = sources.shift()
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} })
mergeDeep(target[key], source[key])
} else {
Object.assign(target, { [key]: source[key] })
}
}
}
return mergeDeep(target, ...sources)
}
export function slugify (string) {
const a = 'àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'
const b = 'aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'
const p = new RegExp(a.split('').join('|'), 'g')
return string.toString().toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
.replace(/&/g, '-and-') // Replace & with 'and'
.replace(/[^\w-]+/g, '') // Remove all non-word characters
.replace(/--+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, '') // Trim - from end of text
}
export function getIndicatorYears () {
const localDate = new Date()
let availableYears = []
for (var i = 2015; i <= localDate.getUTCFullYear(); i++) {
availableYears[i-2015] = i
}
return availableYears
}
+18
View File
@@ -0,0 +1,18 @@
import React from 'react'
import Typography from '@material-ui/core/Typography'
const TopBar = ({
size,
backgroundUrl,
title,
text
}) => (
<div className={`bannerTopBar ${size}`} style={backgroundUrl ? { backgroundImage: `url(${backgroundUrl})` } : {}}>
<div className='caption'>
<Typography className='title' variant='h1' align='center'>{title}</Typography>
<Typography className='text' variant='h2' align='center'>{text}</Typography>
</div>
</div>
)
export default TopBar
+522
View File
@@ -0,0 +1,522 @@
import React, { Component } from 'react'
import { graphql, compose, Query } from 'react-apollo'
import { clones, teams } 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 { types, startModes, availableMonths, processNames } from '../../../GraphQL/models'
import { isDefined, getIndicatorYears } from '../../../utils'
import {
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'
class AddClone extends Component {
constructor(props){
super(props)
let user = localStorage.getItem('user')
this.state = {
user: JSON.parse(user),
selectedBot: {'id':''},
mode: '',
resumeExecution: false,
beginDatetime: DateTime.local().minus({ days: 8 }).startOf('day'),
endDatetime: DateTime.local(),
waitTime: 1,
state: '',
stateDatetime: 0,
createDatetime: 0,
runAsTeam: false,
financialBeings:[],
// Indicator Bot
startYear: 2019,
endYear: 2019,
month: 1,
processName: '',
//Error handlers
nameError: false,
teamError: false,
botError: false,
modeError: false,
isNewCloneConfirmationOpen: false,
serverResponse: '',
serverError: false,
processNameError: false
}
}
render() {
const { classes } = this.props
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'
/>
)
} 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'
/>
<div className='container'>
<Paper className={classNames('container', classes.root)}>
<form noValidate autoComplete="off" onSubmit={this.submitForm.bind(this)}>
<Typography className={classes.typography} variant='h5' gutterBottom>
Create a Bot Clone
</Typography>
<Typography className={classes.typography} variant='subtitle1' align='justify'>
A bot clone it's a copy of your bot running on a virtual machine.
Please select from your teams and bots the one you want to clone!
</Typography>
<FormControl className={classNames(classes.form, classes.textField)} fullWidth>
<InputLabel shrink htmlFor='select'>
Select Team and Bot
</InputLabel>
<Query query={teams.GET_ALL_TEAMS_QUERY} >
{({ loading, error, data }) => {
if (error) return `Error! There has been an error loading teams.`
if (loading) {
return (
<Select
value='loading'
input={<Input />}
displayEmpty
className={classes.selectEmpty}
disabled
>
<MenuItem key='1' value='loading'>Loading...</MenuItem>
</Select>
)
}
if(!isDefined(data.teams_FbByTeamMember)){
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.financialBeings = data.teams_FbByTeamMember.fb
}
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)}
>
{
data.teams_FbByTeamMember.fb.map(financialBeing => (
<MenuItem key={financialBeing.id} value={financialBeing.id}>
{data.teams_FbByTeamMember.name + '/' + financialBeing.name}
</MenuItem>
))
}
</Select>
)
}}
</Query>
<FormHelperText>{this.state.selectedBot.kind}</FormHelperText>
</FormControl>
{ /* TRADER BOT */ }
{ 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
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
>
{types.map(option => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
))}
</TextField>
{ 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
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>
}
{ /* INDICATOR AND EXTRACTOR BOTS */ }
{ (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
>
{startModes.map(option => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
))}
</TextField>
{ this.state.mode === "All Months" &&
<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>
<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 === "One Month" &&
<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})}
fullWidth
>
{getIndicatorYears().map((option, index) => (
<MenuItem key={option} value={index}>
{option}
</MenuItem>
))}
</TextField>
<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>
}
<div className={classes.actionButton} >
<Button
type="submit"
variant='contained' color='secondary'>
Clone Bot!
</Button>
</div>
</form>
<Dialog
open={this.state.isNewCloneConfirmationOpen}
onClose={this.handleNewCloneConfirmationClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Creating bot clone</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{this.state.serverResponse}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleNewCloneConfirmationClose} color="primary" autoFocus>
Ok
</Button>
</DialogActions>
</Dialog>
</Paper>
</div>
</React.Fragment>
);
}
setSelectedBot(botId){
if(isDefined(this.financialBeings)){
for (var i = 0; i < this.financialBeings.length; i++) {
if(this.financialBeings[i].id === botId){
this.setState({selectedBot:this.financialBeings[i]})
return
}
}
}
}
async submitForm(e){
e.preventDefault()
let error = this.validate()
if (!error){
let serverResponse = await this.createCloneOnServer()
error = serverResponse.errors || !isDefined(serverResponse.data.operations_AddClone)
if(error){
this.state.serverResponse = serverResponse.errors[0].message
this.state.serverError = true
}else{
this.state.serverResponse = "The new clone was sucessfully created."
this.state.serverError = false
}
this.handleNewCloneConfirmationOpen()
}
}
async createCloneOnServer(){
let variables = {
clone:{
botId: this.state.selectedBot.id,
mode: this.state.mode,
resumeExecution: this.state.resumeExecution,
runAsTeam: this.state.runAsTeam
}
}
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"){
variables.clone.processName = this.state.processName
variables.clone.startYear = this.state.startYear
variables.clone.endYear = this.state.endYear
variables.clone.month = this.state.month
}
return this.props.addCloneMutation({
variables: variables
})
}
handleNewCloneConfirmationOpen = () => {
this.setState({ isNewCloneConfirmationOpen: true })
};
handleNewCloneConfirmationClose = () => {
this.setState({
isNewCloneConfirmationOpen: false
})
if(!this.state.serverError)
this.setState({
selectedBot: {'id':''},
mode: '',
resumeExecution: false,
beginDatetime: DateTime.local().minus({ days: 8 }).startOf('day'),
endDatetime: DateTime.local(),
waitTime: 1,
state : '',
stateDatetime: 0,
createDatetime: 0,
runAsTeam: false,
processName: '',
//Error handlers
nameError: false,
teamError: false,
botError: false,
modeError: false,
isNewCloneConfirmationOpen: false,
serverResponse: '',
serverError: false,
processNameError: false
})
};
validate(){
let isError = false
if(this.state.selectedBot.length < 1) {
isError = true
this.setState(state => ({ selectedBotError: true }));
}
if(this.state.mode.length < 1) {
isError = true
this.setState(state => ({ modeError: true }));
}
if(this.state.selectedBot.kind !== "TRADER"
&& this.state.processName.length < 1 ) {
isError = true
this.setState(state => ({ processNameError: true }));
}
return isError;
}
}
export default compose(
graphql(clones.OPERATIONS_ADD_CLONE, { name:'addCloneMutation' }),
withStyles(styles)
)(AddClone)
+37
View File
@@ -0,0 +1,37 @@
const styles = theme => ({
root: {
width: '100%',
flexGrow: 1,
padding: 10,
marginTop: '5%',
marginBottom: '10%'
},
container: {
display: 'flex',
flexWrap: 'wrap',
padding: 20,
height: '100%'
},
textField: {
width: '80%',
marginLeft:'10%',
marginBottom: 10
},
actionButton: {
textAlign: 'center',
marginTop: 10
},
typography: {
width: '80%',
marginLeft: '10%',
marginTop: 40
},
form: {
marginTop: 20
},
selectEmpty: {
marginTop: theme.spacing.unit * 2,
},
});
export default styles;
+130
View File
@@ -0,0 +1,130 @@
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) {
super(props)
let user = localStorage.getItem('user')
this.state = {
user: JSON.parse(user)
}
}
render () {
let data = this.props.data
if (data.loading) {
return (
<TopBar
size='big'
title='Bot Clone History'
text='Loading your Bot Clones...'
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
/>
)
} else if (data.operations_HistoryClones && data.operations_HistoryClones.length > 0) {
return (
<React.Fragment>
<TopBar
size='medium'
title='Bot Clone History'
text='All your Bot Clones are here.'
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
/>
<div className='container'>
{
data.operations_HistoryClones.map((clone, i) => {
return (
<ListClones key={clone.id} currentClone={clone} isHistory={true}/>
)
})
}
</div>
</React.Fragment>
)
} else if (data.error) {
return (
<TopBar
size='big'
title='Bot Clone History'
text='Please login to gain access to your Bot Clones.'
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
/>
)
} else {
return (
<TopBar
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'
/>
)
}
}
getBodyContent (data) {
if (data.loading) {
return (
<TopBar
size='big'
title='Manage your Bot Clones'
text='Loading Bot Clones...'
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
/>
)
} else if (data.operations_HistoryClones && data.operations_HistoryClones.length > 0) {
return (
<React.Fragment>
<TopBar
size='big'
title='Manage your Bot Clones'
text='Loading Bot Clones...'
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
/>
<div className='container'>
{
data.operations_HistoryClones.map((clone, i) => {
return (
<ListClones key={clone.id} currentClone={clone} />
)
})
}
</div>
</React.Fragment>
)
} else if (data.error) {
return (
<TopBar
size='big'
title='No Bot Clones to display'
text='Please Login to access your Bot Clones.'
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
/>
)
} else {
return (
<TopBar
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'
/>
)
}
}
}
export default graphql(clones.OPERATIONS_HISTORY_CLONES)(HistoryClones)
@@ -0,0 +1,59 @@
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
} from '@material-ui/core'
class CloneDialog extends Component {
constructor (props) {
super(props)
this.state = {
selected:'',
isNewCloneDialogOpen: false,
}
}
handleNewCloneDialogOpen = () => {
this.setState({ isNewCloneDialogOpen: true })
};
handleNewCloneDialogClose = () => {
this.setState({ isNewCloneDialogOpen: false })
};
render () {
const { classes } = this.props
return (
<div className={classes.root}>
<Button variant="contained" color="secondary"
aria-label="addNewClone" className={classes.button}
onClick={this.handleNewCloneDialogOpen} >
Add a new Clone
</Button>
<Dialog
open={this.state.isNewCloneDialogOpen}
onClose={this.handleNewCloneDialogClose}
aria-labelledby="addClone-dialog-title"
>
<DialogTitle id="addClone-dialog-title">
Add a new Clone
</DialogTitle>
<DialogContent>
<DialogContentText>
Description...
</DialogContentText>
<AddClone handleNewCloneDialogClose={this.handleNewCloneDialogClose}/>
</DialogContent>
</Dialog>
</div>
)
}
}
export default withStyles(styles)(CloneDialog)
+274
View File
@@ -0,0 +1,274 @@
import React, { Component } from 'react'
import { graphql, compose } from 'react-apollo'
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,
DialogContentText, DialogTitle, DialogActions,
ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary,
ExpansionPanelActions, Chip, Divider
} from '@material-ui/core'
import Avatar from '@material-ui/core/Avatar';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { isDefined, toLocalTime } from '../../../utils'
import classNames from 'classnames'
import { withStyles } from '@material-ui/core/styles'
import styles from './styles'
class ListClones extends Component {
constructor (props) {
super(props)
this.state = {
isRemoveDialogOpen: false,
isLogsDialogOpen: false,
}
}
render () {
const { classes } = this.props
const clone = this.props.currentClone
return (
<React.Fragment>
<ExpansionPanel className={classes.root}>
<ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}>
<div className={classes.heading}>
<Avatar src={clone.teamAvatar} className={classes.avatar} />
<Typography className={classes.heading}>{clone.teamName}</Typography>
</div>
<div className={classes.heading}>
<Avatar src={clone.botAvatar} className={classes.avatar} />
<Typography className={classes.heading}>{clone.botName}</Typography>
</div>
<div className={classes.heading}>
<Typography className={classes.heading}>{clone.mode}</Typography>
</div>
</ExpansionPanelSummary>
<Divider />
<ExpansionPanelDetails className={classes.details}>
<div className={classes.column3}>
<Typography className={classes.cloneInfoTitle}>Context Information</Typography>
<div className={classes.details}>
<div className={classes.column4}>
<Typography className={classes.cloneInfoBold}>Created:</Typography>
{ isDefined(clone.beginDatetime) &&
<Typography className={classes.cloneInfoBold}>Begin:</Typography>
}
{ isDefined(clone.endDatetime) &&
<Typography className={classes.cloneInfoBold}>End:</Typography>
}
{ isDefined(clone.waitTime) &&
<Typography className={classes.cloneInfoBold}>Wait Time:</Typography>
}
<Typography className={classes.cloneInfoBold}>Resume Execution:</Typography>
<Typography className={classes.cloneInfoBold}>Run As Team:</Typography>
</div>
<div className={classes.column4}>
<Typography className={classes.cloneInfoNormal}>{toLocalTime(clone.createDatetime)}</Typography>
{ isDefined(clone.beginDatetime) &&
<Typography className={classes.cloneInfoNormal}>{toLocalTime(clone.beginDatetime)}</Typography>
}
{ isDefined(clone.endDatetime) &&
<Typography className={classes.cloneInfoNormal}>{toLocalTime(clone.endDatetime)}</Typography>
}
{ isDefined(clone.waitTime) &&
<Typography className={classes.cloneInfoNormal}>{clone.waitTime}</Typography>
}
<Typography className={classes.cloneInfoNormal}>{clone.resumeExecution ? 'Yes':'No'}</Typography>
<Typography className={classes.cloneInfoNormal}>{clone.runAsTeam ? 'Yes':'No'}</Typography>
</div>
</div>
</div>
<div className={classNames(classes.column3, classes.helper)}>
<Typography className={classes.cloneInfoTitle}>Execution Information</Typography>
<div className={classes.details}>
<div className={classes.column4}>
<Typography className={classes.cloneInfoBold}>Last Execution:</Typography>
{ (clone.kind === 'TRADER' && isDefined(clone.assetA) ) &&
<React.Fragment>
<Typography className={classes.cloneInfoBold}>Buy Average:</Typography>
<Typography className={classes.cloneInfoBold}>Sell Average:</Typography>
<Typography className={classes.cloneInfoBold}>Market Rate:</Typography>
<Typography className={classes.cloneInfoBold}>ROI {clone.assetA}:</Typography>
<Typography className={classes.cloneInfoBold}>ROI {clone.assetB}:</Typography>
</React.Fragment>
}
</div>
<div className={classes.column4}>
<Typography className={classes.cloneInfoNormal}>{isDefined(clone.summaryDate) ? toLocalTime(clone.summaryDate): 'waiting update...'}</Typography>
{ ((clone.kind === 'INDICATOR' || clone.kind === 'EXTRACTOR') && isDefined(clone.assetA) ) &&
<React.Fragment>
<Typography className={classes.cloneInfoNormal}>{clone.buyAverage}</Typography>
<Typography className={classes.cloneInfoNormal}>{clone.sellAverage}</Typography>
<Typography className={classes.cloneInfoNormal}>{clone.marketRate}</Typography>
<Typography className={classes.cloneInfoNormal}>{clone.combinedProfitsA}</Typography>
<Typography className={classes.cloneInfoNormal}>{clone.combinedProfitsB}</Typography>
</React.Fragment>
}
</div>
</div>
</div>
<div className={classNames(classes.column3, classes.helper)}>
<Typography className={classes.cloneInfoTitle}>Virtual Machine Status</Typography>
<TextField
id="outlined-state"
defaultValue={clone.state}
margin="normal"
InputProps={{
readOnly: true,
}}
fullWidth
multiline
/>
</div>
</ExpansionPanelDetails>
{
!this.props.isHistory &&
<Divider />
}
{
!this.props.isHistory &&
<ExpansionPanelActions>
<Button
className={classes.buttonList}
variant='contained' color='secondary' size='small'
onClick={() => this.setState({ isLogsDialogOpen: true })}>
View Logs
</Button>
<Button
className={classes.buttonList}
variant='contained' color='secondary' size='small'
onClick={this.handleRemoveDialogOpen}>
Delete
</Button>
</ExpansionPanelActions>
}
</ExpansionPanel>
<Dialog
open={this.state.isRemoveDialogOpen}
onClose={this.handleRemoveDialogClose}
aria-labelledby="removeClone-dialog-title"
>
<DialogTitle id="removeClone-dialog-title">
Delete Clone
</DialogTitle>
<DialogContent>
<DialogContentText>
Are you sure you want to delete this clone?
Deleting this clone will prevent any running bots associated to this clone to access your account at the exchange.
</DialogContentText>
<DialogActions>
<Button onClick={this.handleRemoveDialogOK} className={classes.buttonList}
variant='contained' color='secondary' size='small'
>
Proceed
</Button>
<Button onClick={this.handleRemoveDialogCancel}
className={classes.buttonList}
variant='contained' color='secondary' size='small'
>
Cancel
</Button>
</DialogActions>
</DialogContent>
</Dialog>
<Dialog
open={this.state.isLogsDialogOpen}
onClose={this.handleLogsDialogClose}
aria-labelledby="logs-dialog-title"
>
<DialogTitle id="logs-dialog-title">
Last clone logs
</DialogTitle>
<DialogContent className={classes.logsDialog}>
<Typography className={classes.typography} variant='subtitle1' align='justify'>
Here you can check the clone output directly from the virtual machine.
The last 20 lines are displayed, but you can check the Logs Module for more details.
</Typography>
<div className={classes.logsWrapper}>
<TextField
id="outlined-logs"
defaultValue={this.props.currentClone.lastLogs}
className={classes.textArea}
margin="normal"
InputProps={{
readOnly: true,
}}
fullWidth
multiline
/>
</div>
</DialogContent>
<DialogActions>
<Button onClick={this.handleLogsDialogClose} className={classes.buttonList}
variant='contained' color='secondary' size='small' autoFocus
>
Close
</Button>
</DialogActions>
</Dialog>
</React.Fragment>
)
}
canStopClone(){
const state = JSON.parse(this.props.currentClone.state)
if(isDefined(state) && state.hasOwnProperty("running")){
return true
} else {
return false
}
}
canRestartClone(){
const state = JSON.parse(this.props.currentClone.state)
if(isDefined(state) && state.hasOwnProperty("terminated")){
return true
} else {
return false
}
}
removeClone (clone) {
this.props.mutate({
variables:{
id: clone.id
},
refetchQueries: [{query: clones.OPERATIONS_LIST_CLONES}]
})
}
showLogs () {
this.setState({ isLogsDialogOpen: true })
}
handleLogsDialogClose = () => {
this.setState({ isLogsDialogOpen: false })
}
handleRemoveDialogOpen = () => {
this.setState({ isRemoveDialogOpen: true })
}
handleRemoveDialogOK = () => {
this.removeClone(this.props.currentClone)
this.setState({ isRemoveDialogOpen: false })
}
handleRemoveDialogCancel = () => {
this.setState({ isRemoveDialogOpen: false })
}
}
export default compose(
graphql(clones.OPERATIONS_REMOVE_CLONE),
withStyles(styles)
)(ListClones)
+130
View File
@@ -0,0 +1,130 @@
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) {
super(props)
let user = localStorage.getItem('user')
this.state = {
user: JSON.parse(user)
}
}
render () {
let data = this.props.data
if (data.loading) {
return (
<TopBar
size='big'
title='Your Bot Clones'
text='Loading your Bot Clones...'
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
/>
)
} else if (data.operations_Clones && data.operations_Clones.length > 0) {
return (
<React.Fragment>
<TopBar
size='medium'
title='Your Bot Clones'
text='All your Bot Clones are here.'
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
/>
<div className='container'>
{
data.operations_Clones.map((clone, i) => {
return (
<ListClones key={clone.id} currentClone={clone} />
)
})
}
</div>
</React.Fragment>
)
} else if (data.error) {
return (
<TopBar
size='big'
title='Your Bot Clones'
text='Please login to gain access to your Bot Clones.'
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
/>
)
} else {
return (
<TopBar
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'
/>
)
}
}
getBodyContent (data) {
if (data.loading) {
return (
<TopBar
size='big'
title='Manage your Bot Clones'
text='Loading Bot Clones...'
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
/>
)
} else if (data.operations_Clones && data.operations_Clones.length > 0) {
return (
<React.Fragment>
<TopBar
size='big'
title='Manage your Bot Clones'
text='Loading Bot Clones...'
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
/>
<div className='container'>
{
data.operations_Clones.map((clone, i) => {
return (
<ListClones key={clone.id} currentClone={clone} />
)
})
}
</div>
</React.Fragment>
)
} else if (data.error) {
return (
<TopBar
size='big'
title='No Bot Clones to display'
text='Please Login to access your Bot Clones.'
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
/>
)
} else {
return (
<TopBar
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'
/>
)
}
}
}
export default graphql(clones.OPERATIONS_LIST_CLONES)(BrowseClones)
+91
View File
@@ -0,0 +1,91 @@
const styles = theme => ({
root: {
flexGrow: 1,
margin: 10
},
image: {
width: 128,
height: '100%',
cursor: 'default'
},
img: {
margin: 'auto',
display: 'block',
maxWidth: '100%',
maxHeight: '100%'
},
buttonList: {
margin: theme.spacing.unit,
float: 'right',
},
buttonGrid: {
marginTop: -20,
},
button: {
display: 'block',
marginLeft: 'auto',
marginRight: 'auto',
},
textField: {
marginLeft: theme.spacing.unit,
marginRight: theme.spacing.unit,
},
textArea: {
width:'1750px',
marginLeft: theme.spacing.unit,
marginRight: theme.spacing.unit,
whiteSpace: 'pre',
},
heading: {
fontSize: theme.typography.pxToRem(18),
flexBasis: '33.33%',
fontWeight: '600',
whiteSpace: 'nowrap',
display: 'inline-flex',
alignItems: 'center',
paddingRight: theme.typography.pxToRem(20)
},
helper: {
borderLeft: `2px solid ${theme.palette.divider}`,
padding: `0px ${theme.spacing.unit * 2}px`,
},
logsDialog:{
overflowY:'hidden',
overflowX:'hidden'
},
logsWrapper:{
overflowY:'auto',
overflowX:'auto'
},
details:{
display:'flex'
},
column2: {
flexBasis: '50%',
maxWidth: '50%'
},
column3: {
flexBasis: '33.33%',
maxWidth: '33.33%'
},
cloneInfoTitle:{
fontWeight: '600',
flexBasis: '100%',
maxWidth: '100%'
},
cloneInfoBold:{
fontWeight: '600',
paddingLeft: '10px',
paddingTop: '10px'
},
cloneInfoNormal:{
paddingLeft: '10px',
paddingTop: '10px'
},
avatar: {
margin: 10,
},
})
export default styles
+43
View File
@@ -0,0 +1,43 @@
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 () {
return (
<React.Fragment>
<TopBar
size='big'
title='Operations Module'
text='Responsible for manage bot clones.'
backgroundUrl='https://superalgos.org/img/photos/ecosystem.jpg'
/>
<div className='homePage container'>
<Typography variant='h1' align='center' className='title'>
Welcome to the Operations Module!
</Typography>
<Typography variant='h2' align='center' className='subtitle'>
Responsible for manage bot clones.
</Typography>
<div className='column'>
<Typography align='justify'>
The Operations module allows you to create copies of your bot
that will be executed on a virtual machine.
</Typography>
</div>
<div className='column'>
<Typography align='justify'>
</Typography>
</div>
</div>
</React.Fragment>
)
}
}
export default Home
+3
View File
@@ -0,0 +1,3 @@
{
"presets": ["env", "stage-2"]
}
+28
View File
@@ -0,0 +1,28 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
/logs
# misc
.env
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
#Docker
Dockerfile
docker-compose.prod.yml
+46
View File
@@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at feedback at superalgos dot org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+45
View File
@@ -0,0 +1,45 @@
# Contributing
We are thrilled you are interested in contributing to this repository. Your help may be essential to the project's success and we want you in!
Superalgos is a collective project, currently at a very early stage. Although this repository is not open source software yet, we do welcome contributions from the community.
Contributions are deemed _voluntary work_. There might be incentives in place for certain kinds of contributions. Contact us for more information in this regard.
In any case, contributors agree to provide an express grant of patent rights for their contributions. Submitting Pull Requests to this repository represents an express agreement with the terms of our Balanced Contributor Intellectual Property Agreement. This agreement is a direct implementation of GitHub's [Balanced Employee Intellectual Property Agreement 1.0.1](https://github.com/github/balanced-employee-ip-agreement/blob/master/Balanced_Employee_IP_Agreement.md).
# Balanced Contributor Intellectual Property Agreement 1.0.1
This BALANCED CONTRIBUTOR INTELLECTUAL PROPERTY AGREEMENT is between person or team submitting Pull Requests to this repository and Superalgos.org ("Project").
**What is this?** This is the Project's Intellectual Property Agreement ("Agreement"). If you've worked in the technology space before, there's a good chance that you've run across one or more of these in the past. This document is the official, entire and exclusive agreement on what intellectual property ("IP") is yours, and what belongs to the Project.
**Why is this?** The Project needs to be clear on what IP it owns and has rights to. Its customers, contributors, and investors depend on the Project having the legal rights to the products and services it is providing so that the Project can continue operating and doing business.
The Project also believes that it's important to be clear on what it doesn't own. The Project doesn't want you looking over your shoulder every time you work on something personal or worrying that the Project will someday seize your open source non-lethal mousetrap simulation software. In other words, the Project isn't interested in appropriating your personal projects.
**Read this.** Please read this document and be sure you understand it before you submit any Pull Requests. Due to legal risk and corporate obligations, the Project cannot, by and large, negotiate its terms. If you feel you have a particular circumstance that keeps you from signing, please let the Project's legal department ("Legal") know. And, of course, you're always free and encouraged to get your own legal counsel to explain anything you're not clear on.
Cool? Then, by submitting your first Pull Request, and as a condition of your contribution, you agree to the following:
1. **What the Project owns.** The Project owns any IP ("Project IP") that you create, or help create _as its contributor_, but only if it meets one or more of these additional conditions: The IP was (i) related to an existing or prospective Project product or service at the time you developed, invented, or created it, (ii) developed for use by the Project, or (iii) developed or promoted with existing Project IP or with the Project's endorsement. You hereby grant and assign, and will grant and assign, to the Project all rights and interests in all Project IP.
"Project IP" includes concepts, designs, developments, discoveries, ideas, improvements, inventions, trade secrets, trademarks, and works of authorship.
2. **What the Project doesn't own.** If you create IP that doesn't fit into one of the categories listed above, the Project doesn't own it. In other words, the Project doesn't own concepts, designs, developments, discoveries, ideas, improvements, inventions, trade secrets, trademarks, or works of authorship that you develop, invent, or create that do not meet any of the conditions in Section 1.
3. **Contribution of your IP to Project projects.** If you include your own IP – such as IP you created prior to working for the Project – into a Project product or service, it's still yours, of course, but you grant the Project a non-exclusive, irrevocable, fully paid-up, royalty-free, perpetual, sublicensable, transferable, worldwide license to use it without restriction in any way or implementation, in modified form, or as is, by itself, or incorporated into another product or service ("License"). If you include your name in any project over which the Project would have rights under this Agreement, such as in a copyright notice or a comment in code or documentation, then the License covers the rights associated with that use of your name as well.
4. **Your contributions to non-Project projects.** The Project recognizes that you may be engaged in work that requires you to submit IP to entities other than the Project, such as open source projects used by the Project. Please make sure that Legal is aware of what you're working on so that Legal can help with any licensing issues. If any project asks you to sign a contribution agreement, you should check with Legal before doing so. These agreements may be legally binding on the Project and so should be run through Legal first.
5. **Voluntary Works** All Project IP that you are involved with or create as part of your work for the Project is voluntary work which does not require compensation. There might be incentives in place for certain kinds of contributions. Feel free to contact the Project to learn more.
6. **No conflicts.** You agree that you don't have any outstanding agreements or obligations that conflict with those in this Agreement, and that you won't enter into conflicting agreements in the future.
7. **IP protection.** The Project might someday need to show the work that went into the development of IP that it uses or has used, or to make government filings to establish that it owns the IP or has rights over it. To help in those situations, you agree not to destroy any records you maintain relating to the development of any Project IP or IP under License (together, "All IP"), and, if the Project asks, to provide those records to the Project. You agree to help the Project secure and defend its rights in All IP. For your help the Project will compensate you at a reasonable rate. If the Project is unable to secure your help, you authorize the Project to act on your behalf (as your agent and attorney-in-fact) in securing all rights related to All IP.
8. **Confidentiality.** As an contributor or contractor of the Project, you will have access to sensitive confidential information that is important to the Project's business. You are responsible for keeping this information confidential, including after you end your work for the Project.
"Confidential Information" includes non-public technical details about our products or services, financial information, business strategies and forecasts, customer data, or any other information, data or know-how that is valuable to the Project because it is not publicly known.
The Project's Confidential Information is sensitive and we expect that you'll treat it as such. You agree to only use the Project's Confidential Information for the purpose of performing your contribution and for the benefit of the Project. You will do your best to keep Confidential Information secret. If you are not sure if something is Confidential Information, you should assume that it is, until you can confirm otherwise.
+39
View File
@@ -0,0 +1,39 @@
COPYRIGHT NOTICE
© Copyright 2018 Superalgos.org
LICENSE NOTICE
Superalgos is a collaborative project currently at a very early stage. Because certain aspects of
the project are still not clearly defined, we have chosen to --temporarily-- grant a restrictive
license for the use of this repository, until those aspects are clarified and a proper open source
license can be granted.
Superalgos.org is publishing the code of this software for several reasons. However, it shall not be considered free software yet. Superalgos is a collaborative project in the crypto space and this
software is still proprietary software.
The reasons for opening up the code of this software at this stage are the following:
* We believe that transparency is paramount to achieving our goals, so we want everyone to
be able to understand what this software does and how. Opening up the code of this software
allows anyone to take a close look.
* We welcome contributions from the community. Contributions are regarded as voluntary work.
There may be incentives in place for certain kinds of contributions. Contact us to learn more.
Contributors agree to provide an express grant of patent rights for their contributions.
LICENSE WORDING
Superalgos.org grants a limited license for the following uses:
1. CODE REVIEW AND AUDITS. You are granted permission to clone this repository to analyze,
review or audit the code.
2. COLLABORATING WITH Superalgos.org. You are granted permission to fork this repository
with the sole purpose of correcting bugs and developing functionality in agreement with
Superalgos.org. By contributing to this repository you explicitely agree to the terms
detailed in CONTRIBUTING.MD.
PLEASE SEE CONTRIBUTING.MD FOR MORE DETAILS ON THE IMPLICATIONS SURROUNDING YOUR CONTRIBUTIONS TO THIS REPOSITORY.
+4
View File
@@ -0,0 +1,4 @@
## AA Operations Server
Server side functions support the business rules for the module.
+6363
View File
File diff suppressed because it is too large. Load diff
+31
View File
@@ -0,0 +1,31 @@
{
"name": "operations-server",
"version": "1.0.0",
"description": "Superalgos Operations backend API",
"main": "src/index.js",
"scripts": {
"start": "nodemon --exec babel-node --presets env,stage-2 src/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"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.4"
},
"dependencies": {
"@kubernetes/client-node": "^0.7.2",
"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",
"lodash": "^4.17.10",
"mongoose": "^5.2.12",
"winston": "^3.1.0"
}
}
+31
View File
@@ -0,0 +1,31 @@
{
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"labels": {
"app": "aadev"
},
"name": "aadev-deployment"
},
"spec": {
"template": {
"spec": {
"restartPolicy": "Never",
"containers": [
{
"name": "aadev",
"image": "benme/aadev:v3.3",
"command": ["node", "Run.js"],
"env": [
]
}
]
},
"metadata": {
"labels": {
"app": "aadev"
}
}
}
}
}
+45
View File
@@ -0,0 +1,45 @@
import 'dotenv/config'
const env = []
env.push({
"name": "PLATFORM_ENVIRONMENT",
"value": process.env.PLATFORM_ENVIRONMENT
})
env.push({
"name": "GATEWAY_ENDPOINT",
"value": process.env.GATEWAY_ENDPOINT
})
env.push({
"name": "STORAGE_BASE_URL",
"value": process.env.STORAGE_BASE_URL
})
env.push({
"name": "STORAGE_CONNECTION_STRING",
"value": process.env.STORAGE_CONNECTION_STRING
})
env.push({
"name": "AUTH_URL",
"value": process.env.AUTH_URL
})
env.push({
"name": "AUTH_CLIENT_ID",
"value": process.env.AUTH_CLIENT_ID
})
env.push({
"name": "AUTH_CLIENT_SECRET",
"value": process.env.AUTH_CLIENT_SECRET
})
env.push({
"name": "AUTH_AUDIENCE",
"value": process.env.AUTH_AUDIENCE
})
export default env
+42
View File
@@ -0,0 +1,42 @@
import { createLogger, format, transports } from 'winston'
const { combine, timestamp, simple } = format
import { inspect } from 'util'
const options = {
file: {
level: 'info',
filename: '../logs/app.log',
handleExceptions: true,
json: true,
maxsize: 5242880, // 5MB
maxFiles: 5,
colorize: false,
timestamp: true
},
console: {
level: 'debug',
handleExceptions: true,
json: false,
colorize: true,
timestamp: true
}
}
const logger = createLogger({
level: 'info',
format: format.combine(
format.colorize(),
format.splat(),
format.timestamp({ format: 'YY/MM/DD HH:mm' }),
format.printf(
info => `${info.timestamp} - ${info.level}: ${JSON.parse(JSON.stringify(inspect(info.message)))}`
)
),
transports: [
new transports.File(options.file),
new transports.Console(options.console)
],
exitOnError: false // do not exit on handled exceptions
})
export default logger
+23
View File
@@ -0,0 +1,23 @@
export function toPlatformDatetime (epoch) {
const platformDate = new Date(0)
platformDate.setUTCSeconds(epoch)
return platformDate.toISOString()
}
export function isDefined (d) {
return d !== null && typeof d !== 'undefined'
}
export function getJobNameFromClone (teamSlug, botSlug, mode) {
return teamSlug + '-' + botSlug + '-' + mode.toLowerCase()
}
export function getSelectedBot(botsByUser, selectedBotId){
if(isDefined(botsByUser)){
for (var i = 0; i < botsByUser.fb.length; i++) {
if(botsByUser.fb[i].id === selectedBotId){
return botsByUser.fb[i]
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
export const LIVE = 'Live'
export const BACKTEST = 'Backtest'
export const COMPETITION = 'Competition'
export const ALL_MONTHS = 'All Months'
export const ONE_MONTH = 'One Month'
export const NO_TIME = 'No Time'
export const CloneModeEnum = [
LIVE,
BACKTEST,
COMPETITION,
ALL_MONTHS,
ONE_MONTH,
NO_TIME
]
export default CloneModeEnum
+19
View File
@@ -0,0 +1,19 @@
export const UNPUBLISHED = 'UNPUBLISHED';
export const PUBLISHED = 'PUBLISHED';
export const CANCELLED = 'CANCELLED';
export const RUNNING = 'RUNNING';
export const INTERRUPTED = 'INTERRUPTED';
export const FINISHED = 'FINISHED';
export const ARCHIVED = 'ARCHIVED';
export const CloneStateEnum = [
UNPUBLISHED,
PUBLISHED,
CANCELLED,
RUNNING,
INTERRUPTED,
FINISHED,
ARCHIVED,
];
export default CloneStateEnum;
+64
View File
@@ -0,0 +1,64 @@
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'
constructor(){
super()
logger.error(this.stack)
}
}
export class DatabaseError extends Error {
code = 404
message = 'Ressource not found : ' + this.message
constructor(message){
super(message)
logger.error(this.stack)
}
}
export class WrongArgumentsError extends Error {
code = 400
message = 'Wrong arguments : ' + this.message
constructor(message){
super(message)
logger.error(this.stack)
}
}
export class ServiceUnavailableError extends Error {
code = 503
message = 'At least one service is unresponding ' + this.message
constructor(message){
super(message)
logger.error(this.stack)
}
}
export class OperationsError extends Error {
code = 500
message = 'OperationsModule Internal Error. ' + this.message
constructor(message){
super(message)
}
}
export class KubernateError extends Error {
code = 500
message = 'OperationsModule Kubernate Error. ' + this.message
constructor(message){
super(message)
logger.error(this.stack)
}
}
export class CustomError extends Error {
code = 500
message = this.message
constructor(message){
super(message)
logger.error(this.stack)
this.stack = ''
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"errors": [
{
"code": 401,
"message": "Wait, you're not supposed to be here, go through the gate as everyone else"
}
]
}
+30
View File
@@ -0,0 +1,30 @@
import logger from '../config/logger'
import axios from 'axios'
const teamAvatar = async(authorization, teamId) => {
logger.debug('GraphQL call teamAvatar.')
return axios({
url: process.env.GATEWAY_ENDPOINT,
method: 'post',
data: {
query: `
query Teams_Team($teamId: String!){
teams_TeamById(teamId:$teamId) {
profile{
avatar
}
}
}
`,
variables: {
teamId: teamId
}
},
headers: {
authorization: authorization
}
})
};
export default teamAvatar
@@ -0,0 +1,39 @@
import logger from '../config/logger'
import axios from 'axios'
const teams_FbByTeamMember = (authorization) => {
logger.debug('GraphQL call teams_FbByTeamMember')
return axios({
url: process.env.GATEWAY_ENDPOINT,
method: 'post',
data: {
query: `
query Teams_FbByTeamMember {
teams_FbByTeamMember {
id
name
slug
fb {
id
name
slug
kind
avatar
}
members {
member {
alias
}
}
}
}
`,
},
headers: {
authorization: authorization
}
})
};
export default teams_FbByTeamMember
+43
View File
@@ -0,0 +1,43 @@
import express from 'express'
import 'dotenv/config'
import graphqlHTTP from 'express-graphql'
import schema from './schema'
import mongoose from 'mongoose'
import cors from 'cors'
import logger from './config/logger'
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();
} else {
res.send(wrongPreshared);
}
});
// Allow cross origing request
app.use(cors())
// Database Connection
mongoose.connect(`mongodb://${process.env.DB_USERNAME}:${process.env.DB_PASSWORD}@${process.env.DB_DOMAIN}`, { useNewUrlParser: true })
mongoose.connection.once('open', () => {
logger.info('Connected to the DB.')
})
app.use('/graphql', graphqlHTTP(req => {
return {
schema: schema,
context: {
userId: req.headers.userid,
authorization: req.headers.authorization
},
graphiql: true
}
}))
app.listen(process.env.PORT, () => {
logger.info(`Listening on port ${process.env.PORT}.`)
})
+106
View File
@@ -0,0 +1,106 @@
import logger from '../config/logger'
import { toPlatformDatetime, getJobNameFromClone, isDefined } from '../config/utils'
import envConfig from '../config/envConfig'
import { KubernateError } from '../errors'
import { Client, config } from 'kubernetes-client'
// Get base Deployment config
import deploymentManifest from '../config/clone-deployment.json'
import { BACKTEST} from '../enums/CloneMode';
const createClone = async (clone, teamSlug, botSlug) => {
try {
let cloneName = getJobNameFromClone(teamSlug, botSlug, clone.mode)
logger.info("createClone %s", cloneName)
const client = new Client({config: config.fromKubeconfig(), version: '1.9'})
// Make changes to base deployment config
deploymentManifest.metadata.name = cloneName
logger.debug("createClone Environment and Auth Configuration.")
let env = envConfig
logger.debug("createClone General Financial Being Configuration.")
env.push({
"name": "DEV_TEAM",
"value": clone.teamId
})
env.push({
"name": "BOT",
"value": clone.botId
})
env.push({
"name": "KIND",
"value": clone.kind
})
env.push({
"name": "PROCESS",
"value": clone.processName
})
env.push({
"name": "CLONE_ID",
"value": clone.id.toString()
})
env.push({
"name": "START_MODE",
"value": clone.mode.toLowerCase()
})
env.push({
"name": "RUN_AS_TEAM",
"value": clone.runAsTeam.toString()
})
env.push({
"name": "USER_LOGGED_IN",
"value": clone.userLoggedIn
})
logger.debug("createClone Trading Configuration.")
if(clone.kind === 'Trading'){
env.push({
"name": "RESUME_EXECUTION",
"value": clone.resumeExecution.toString()
})
if(clone.mode === BACKTEST){
env.push({
"name": "BEGIN_DATE_TIME",
"value": toPlatformDatetime(clone.beginDatetime)
})
env.push({
"name": "END_DATE_TIME",
"value": toPlatformDatetime(clone.endDatetime)
})
env.push({
"name": "WAIT_TIME",
"value": clone.waitTime.toString()
})
}
} else if(clone.kind === 'Indicator' || clone.kind === 'Extractor'){
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(
{ body: deploymentManifest })
logger.info("createClone %s on Kubernates success.", cloneName)
} catch (err) {
throw new KubernateError(err)
}
}
export default createClone
+34
View File
@@ -0,0 +1,34 @@
import logger from '../config/logger'
import { KubernateError } from '../errors'
import { Client, config } from 'kubernetes-client'
const getClonePodLogs = async (cloneName) => {
try {
logger.debug("getClonePodLogs on kubernates started.")
const client = new Client({config: config.fromKubeconfig(), version: '1.9'})
let query = {
"qs":{
"labelSelector": "job-name="+cloneName
}
}
const pod = await client.api.v1.namespaces('default').pods.get(query)
let queryLogs = {
"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')
logger.debug("getClonePodLogs on kubernates successful.")
return JSON.stringify(logsArray, null, 2)
} catch (err) {
throw new KubernateError(err)
}
}
export default getClonePodLogs
@@ -0,0 +1,30 @@
import logger from '../config/logger'
import { KubernateError } from '../errors'
import { Client, config } from 'kubernetes-client'
const getClonePodStatus = async (cloneName) => {
try {
logger.info("getClonePodStatus on kubernates started. ")
const client = new Client({config: config.fromKubeconfig(), version: '1.9'})
let query = {
"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 (clonePodStatus.hasOwnProperty('terminated')
&& clonePodStatus.terminated.hasOwnProperty('containerID')) {
delete clonePodStatus.terminated.containerID
}
logger.info("getClonePodStatus %s on kubernates successful", cloneName)
return JSON.stringify(clonePodStatus, null, 2)
} catch (err) {
throw new KubernateError(err)
}
}
export default getClonePodStatus
+30
View File
@@ -0,0 +1,30 @@
import logger from '../config/logger'
import { KubernateError } from '../errors'
import { Client, config } from 'kubernetes-client'
import { getJobNameFromClone } from '../config/utils'
const removeClone = async (clone, teamSlug, botSlug) => {
try {
let cloneName = getJobNameFromClone(teamSlug, botSlug, clone.mode)
logger.info("removeClone %s", cloneName)
const client = new Client({config: config.fromKubeconfig(), version: '1.9'})
let query = {
"qs":{
"labelSelector": "job-name="+cloneName
}
}
const removed = await client.apis.batch.v1.namespaces('default').jobs(
cloneName).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()
logger.info("removeClone %s on kubernates succesful", cloneName)
} catch (err) {
throw new KubernateError(err)
}
}
export default removeClone
+56
View File
@@ -0,0 +1,56 @@
import mongoose, { Schema } from 'mongoose'
import {
UNPUBLISHED,
CloneStateEnum,
} from '../enums/CloneState';
import {
BACKTEST,
CloneModeEnum,
} from '../enums/CloneMode';
const cloneSchema = new Schema({
authId: {
type: String,
required: true,
},
teamId: {
type: String,
required: true,
},
botId: {
type: String,
required: true,
},
mode: {
type: String,
enum: CloneModeEnum,
default: BACKTEST,
},
resumeExecution: Boolean,
beginDatetime: Number,
endDatetime: Number,
waitTime: Number,
state: {
type: String,
enum: CloneStateEnum,
default: UNPUBLISHED,
},
stateDatetime: Number,
createDatetime: Number,
runAsTeam: Boolean,
active: Boolean,
summaryDate: Number,
buyAverage: Number,
sellAverage: Number,
marketRate: Number,
combinedProfitsA: Number,
combinedProfitsB: Number,
assetA: String,
assetB: String
})
const Clone = mongoose.model('Clone', cloneSchema)
export default Clone
+5
View File
@@ -0,0 +1,5 @@
import Clone from './clone'
export {
Clone
}
+13
View File
@@ -0,0 +1,13 @@
import {
GraphQLSchema
} from 'graphql'
import Mutation from './mutations'
import RootQuery from './queries'
const Schema = new GraphQLSchema({
query: RootQuery,
mutation: Mutation
})
export default Schema
@@ -0,0 +1,90 @@
import { CloneInputType } from '../types/input';
import { CloneType } from '../types';
import { Clone } from '../../models';
import { CloneModeEnum } from '../../enums/CloneMode';
import teams_FbByTeamMember from '../../graphQLCalls/teams_FbByTeamMember';
import {
AuthentificationError,
WrongArgumentsError,
OperationsError,
CustomError
} from '../../errors'
import logger from '../../config/logger'
import createKubernetesClone from '../../kubernetes/createClone'
import { isDefined, getSelectedBot } from '../../config/utils'
const args = {
clone: {type: CloneInputType }
}
const resolve = async(parent, { clone }, context) => {
logger.debug('addClone -> Entering Fuction.')
if (!context.userId) {
throw new AuthentificationError()
}
if(!Object.values(CloneModeEnum).includes(clone.mode)){
throw new WrongArgumentsError('The mode selected is not valid.')
}
let existingClone = await Clone.find(
{
authId: context.userId,
mode: clone.mode,
botId: clone.botId,
active: true
})
logger.debug('addClone -> Checking existing clone %j', existingClone)
if(existingClone.length > 0){
throw new CustomError('You can only have one active clone by mode. Remove the'
+ ' existing clone of type: ' + clone.mode + ' and try again.')
}
try{
// Authorization is handled by the teams module
let botsByUser = await teams_FbByTeamMember(context.authorization)
let selectedBot = getSelectedBot(botsByUser.data.data.teams_FbByTeamMember, clone.botId)
clone.teamId = botsByUser.data.data.teams_FbByTeamMember.id
clone.botId = selectedBot.id
clone.userLoggedIn = botsByUser.data.data.teams_FbByTeamMember.members[0].member.alias //TODO change by context info if possible
clone.createDatetime = new Date().valueOf() / 1000|0
clone.active = true
logger.debug('addClone -> Creating a new clone. %j', clone)
let newClone = new Clone(clone)
newClone.id = newClone._id
newClone.authId = context.userId
await createKubernetesClone(newClone, botsByUser.data.data.teams_FbByTeamMember.slug, selectedBot.slug )
return new Promise((resolve, reject) => {
newClone.save((err) => {
if (err){
logger.debug('addClone -> Error: %j', err)
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.error('addClone -> Error: %s', err.stack)
throw new OperationsError('There has been an error creating the clone.')
}
}
const mutation = {
addClone: {
type: CloneType,
args,
resolve
}
}
export default mutation
@@ -0,0 +1,72 @@
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 teams_FbByTeamMember from '../../graphQLCalls/teams_FbByTeamMember'
import { isDefined, getSelectedBot } from '../../config/utils'
const args = {
id: { type: new GraphQLNonNull(GraphQLID) }
}
const resolve = async (parent, { id }, context) => {
logger.debug('removeClone -> Entering Fuction.')
if (!context.userId) {
throw new AuthentificationError()
}
let clone = await Clone.findOne({
_id: id,
authId: context.userId,
active: true
})
if(!isDefined(clone)){
throw new CustomError('You are not authorized to remove this clone.')
}
// Authorization is also handled by the teams module
let botsByUser = await teams_FbByTeamMember(context.authorization)
let selectedBot = getSelectedBot(botsByUser.data.data.teams_FbByTeamMember, clone.botId)
logger.debug('removeClone -> Removing Clone from Kubernates.')
await removeKuberneteClone(clone, botsByUser.data.data.teams_FbByTeamMember.slug, selectedBot.slug)
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.')
}
})
}
const mutation = {
removeClone: {
type: GraphQLString,
args,
resolve
}
}
export default mutation
@@ -0,0 +1,71 @@
import {
GraphQLNonNull,
GraphQLID,
GraphQLString,
GraphQLInt,
GraphQLFloat
} from 'graphql'
import {
OperationsError
} from '../../errors'
import { Clone } from '../../models'
import logger from '../../config/logger'
const args = {
id: { type: new GraphQLNonNull(GraphQLID) },
summaryDate: { type: GraphQLInt },
buyAverage: { type: GraphQLFloat },
sellAverage: { type: GraphQLFloat },
marketRate: { type: GraphQLFloat },
combinedProfitsA: { type: GraphQLFloat },
combinedProfitsB: { type: GraphQLFloat },
assetA: { type: GraphQLString },
assetB: { type: GraphQLString }
}
const resolve = async (parent,
{ id, summaryDate, buyAverage, sellAverage, marketRate, combinedProfitsA,
combinedProfitsB, assetA, assetB
}) => {
logger.debug('UpdateExecutionSummary -> Entering Function.')
const query = { _id: id }
const options = { new: true }
const update = {
summaryDate: summaryDate,
buyAverage: buyAverage,
sellAverage: sellAverage,
marketRate: marketRate,
combinedProfitsA: combinedProfitsA,
combinedProfitsB: combinedProfitsB,
assetA: assetA,
assetB: assetB
}
let sucessful
await Clone.findOneAndUpdate(query, update, options, (err, doc) => {
if (err){
throw new OperationsError(err)
} else {
logger.debug('Execution summary updated')
sucessful = true
}
})
if(sucessful){
return 'Execution summary updated.'
}
}
const mutation = {
updateExecutionSummary: {
type: GraphQLString,
args,
resolve
}
}
export default mutation
+18
View File
@@ -0,0 +1,18 @@
import {
GraphQLObjectType
} from 'graphql'
import AddCloneMutation from './AddCloneMutation'
import RemoveCloneMutation from './RemoveCloneMutation'
import UpdateExecutionSummary from './UpdateExecutionSummary'
const Mutation = new GraphQLObjectType({
name: 'Mutation',
fields: Object.assign(
AddCloneMutation,
RemoveCloneMutation,
UpdateExecutionSummary
)
})
export default Mutation
@@ -0,0 +1,50 @@
import {
GraphQLList
} from 'graphql'
import {
AuthentificationError
} 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 teams_FbByTeamMember from '../../graphQLCalls/teams_FbByTeamMember'
import cloneDetails from './cloneDetails'
const args = {}
const resolve = async(parent, args, context) => {
logger.debug('History Clones -> Entering Fuction.')
if (!context.userId) {
throw new AuthentificationError()
}
let clones = await Clone.find({
authId: context.userId,
active: false
})
// TODO Refactor this code once financial beings api is ready
// Authorization is handled by the teams module
let botsByUser = await teams_FbByTeamMember(context.authorization)
for (var i = 0; i < clones.length; i++) {
clones[i] = await cloneDetails(context.authorization, botsByUser, clones[i])
}
return clones
}
const query = {
historyClones: {
type: new GraphQLList(CloneType),
args,
resolve
}
}
export default query
+58
View File
@@ -0,0 +1,58 @@
import {
GraphQLList
} from 'graphql'
import {
AuthentificationError,
OperationsError
} from '../../errors'
import { CloneType } from '../types'
import { Clone } from '../../models'
import logger from '../../config/logger'
import getCloneStatus from '../../kubernetes/getClonePodStatus'
import getCloneLogs from '../../kubernetes/getClonePodLogs'
import teams_FbByTeamMember from '../../graphQLCalls/teams_FbByTeamMember'
import cloneDetails from './cloneDetails'
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
})
// TODO Refactor this code once financial beings api is ready
// Authorization is handled by the teams module
let botsByUser = await teams_FbByTeamMember(context.authorization)
for (var i = 0; i < clones.length; i++) {
clones[i] = await cloneDetails(context.authorization, botsByUser, clones[i])
let state = await getCloneStatus(clones[i].cloneName)
clones[i].state = state.substring(1, state.length-1)
let lastLogs = await getCloneLogs(clones[i].cloneName)
clones[i].lastLogs = lastLogs.substring(1, lastLogs.length-1)
}
return clones
} catch (err){
logger.error('List Clones error: %s', err.stack)
throw new OperationsError('There has been an error listing the clones.')
}
}
const query = {
clones: {
type: new GraphQLList(CloneType),
args,
resolve
}
}
export default query
+21
View File
@@ -0,0 +1,21 @@
import logger from '../../config/logger'
import { getJobNameFromClone, getSelectedBot } from '../../config/utils'
import teamAvatar from '../../graphQLCalls/teamAvatar'
const cloneDetails = async(authorization, botsByUser, clone) => {
logger.debug('Retrieving clone details.')
// TODO Refactor this code once financial beings api is ready
let selectedBot = getSelectedBot(botsByUser.data.data.teams_FbByTeamMember, clone.botId)
clone.cloneName = getJobNameFromClone(botsByUser.data.data.teams_FbByTeamMember.slug, selectedBot.slug, clone.mode)
clone.kind = selectedBot.kind
clone.teamName = botsByUser.data.data.teams_FbByTeamMember.name
clone.botName = selectedBot.name
clone.botAvatar = selectedBot.avatar
let team = await teamAvatar(authorization, botsByUser.data.data.teams_FbByTeamMember.id)
clone.teamAvatar = team.data.data.teams_TeamById.profile.avatar
logger.debug('Retrieving clone details complete.')
return clone
};
export default cloneDetails
+16
View File
@@ -0,0 +1,16 @@
import {
GraphQLObjectType
} from 'graphql'
import ListClones from './ListClones'
import HistoryClones from './HistoryClones'
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: Object.assign(
ListClones,
HistoryClones
)
})
export default RootQuery
+29
View File
@@ -0,0 +1,29 @@
import {
GraphQLObjectType,
GraphQLID,
GraphQLString,
GraphQLInt
} from 'graphql'
import {
KeyType
} from './index'
const AuditLogType = new GraphQLObjectType({
name: 'AuditLog',
fields: () => ({
id: {type: GraphQLID},
authId: {type: GraphQLID},
action: {type: GraphQLString},
details: {type: GraphQLString},
date: {type: GraphQLInt},
key: {
type: KeyType,
resolve (parent, args) {
return parent.key
}
}
})
})
export default AuditLogType
+42
View File
@@ -0,0 +1,42 @@
import {
GraphQLObjectType,
GraphQLString,
GraphQLInt,
GraphQLBoolean,
GraphQLID,
GraphQLFloat
} from 'graphql'
const CloneType = new GraphQLObjectType({
name: 'Clone',
fields: () => ({
id: {type: GraphQLID},
authId: {type: GraphQLID},
teamId: {type: GraphQLString},
botId: {type: GraphQLString},
mode: {type: GraphQLString},
resumeExecution: {type: GraphQLBoolean},
beginDatetime: {type: GraphQLInt},
endDatetime: {type: GraphQLInt},
waitTime: {type: GraphQLInt},
state: {type: GraphQLString},
stateDatetime: {type: GraphQLInt},
createDatetime: {type: GraphQLInt},
lastLogs: {type: GraphQLString},
runAsTeam: {type: GraphQLBoolean},
summaryDate: {type: GraphQLInt},
buyAverage: {type: GraphQLFloat},
sellAverage: {type: GraphQLFloat},
marketRate: {type: GraphQLFloat},
combinedProfitsA: {type: GraphQLFloat},
combinedProfitsB: {type: GraphQLFloat},
assetA: {type: GraphQLString},
assetB: {type: GraphQLString},
kind: {type: GraphQLString},
teamName: {type: GraphQLString},
botName: {type: GraphQLString},
botAvatar: {type: GraphQLString},
teamAvatar: {type: GraphQLString}
})
})
export default CloneType
+16
View File
@@ -0,0 +1,16 @@
import {
GraphQLObjectType,
GraphQLID,
GraphQLString
} from 'graphql'
const ExchangeType = new GraphQLObjectType({
name: 'Exchange',
fields: () => ({
id: {type: GraphQLID},
name: {type: GraphQLString},
description: {type: GraphQLString}
})
})
export default ExchangeType
@@ -0,0 +1,15 @@
import {
GraphQLObjectType,
GraphQLString
} from 'graphql'
const SignedTransactionType = new GraphQLObjectType({
name: 'SignedTransaction',
fields: () => ({
key: {type: GraphQLString},
signature: {type: GraphQLString},
date: {type: GraphQLString}
})
})
export default SignedTransactionType
+5
View File
@@ -0,0 +1,5 @@
import CloneType from './CloneType'
export {
CloneType
}
+29
View File
@@ -0,0 +1,29 @@
import {
GraphQLString,
GraphQLInt,
GraphQLInputObjectType,
GraphQLBoolean,
GraphQLNonNull
} from 'graphql';
const Type = new GraphQLInputObjectType({
name: 'CloneInput',
description: 'Payload for clone input',
fields: () => ({
botId: { type: new GraphQLNonNull(GraphQLString) },
mode: { type: GraphQLString },
resumeExecution: { type: GraphQLBoolean },
beginDatetime: { type: GraphQLInt },
endDatetime: { type: GraphQLInt },
waitTime: { type: GraphQLInt },
runAsTeam: { type: GraphQLBoolean },
startYear: { type: GraphQLInt },
endYear: { type: GraphQLInt },
month: { type: GraphQLInt },
interval: { type: GraphQLInt },
processName: { type: GraphQLString },
kind: { type: GraphQLString }
})
});
export default Type;
+5
View File
@@ -0,0 +1,5 @@
import CloneInputType from './Clone'
export {
CloneInputType
};