From d397c87d683cbfe3547e9040c6f3c2a66a1fe3ed Mon Sep 17 00:00:00 2001 From: "mati108@gmail.com" <34651529+matbenitez@users.noreply.github.com> Date: Wed, 26 Dec 2018 19:41:13 -0300 Subject: [PATCH] Adding base version for Operations Module --- .gitignore | 0 CODE_OF_CONDUCT.md | 46 + CONTRIBUTING.md | 45 + LICENSE | 42 + README.md | 5 +- client/.gitignore | 21 + client/CODE_OF_CONDUCT.md | 46 + client/CONTRIBUTING.md | 45 + client/LICENSE | 42 + client/README.md | 4 + client/package-lock.json | 833 ++++++++++++++++++ client/package.json | 35 + client/src/GraphQL/Calls/clones.js | 32 + client/src/GraphQL/Calls/index.js | 7 + client/src/GraphQL/Calls/teams.js | 38 + client/src/GraphQL/Fragments/clone.js | 20 + client/src/GraphQL/Fragments/index.js | 5 + client/src/GraphQL/index.js | 56 ++ client/src/GraphQL/models.js | 10 + client/src/img/binance.png | Bin 0 -> 3509 bytes client/src/img/poloniex.png | Bin 0 -> 11209 bytes client/src/index.js | 21 + client/src/theme.js | 71 ++ client/src/utils/index.js | 74 ++ client/src/views/BannerTopBar/index.js | 18 + client/src/views/Clone/Add/index.js | 367 ++++++++ client/src/views/Clone/Add/styles.js | 37 + client/src/views/Clone/List/CloneDialog.js | 59 ++ client/src/views/Clone/List/ListClones.js | 228 +++++ client/src/views/Clone/List/index.js | 130 +++ client/src/views/Clone/List/styles.js | 82 ++ client/src/views/History/AuditLogList.js | 87 ++ client/src/views/Home.js | 43 + package-lock.json | 3 + server/src/config/clone-deployment.json | 31 + server/src/config/envConfig.js | 47 + server/src/config/logger.js | 42 + server/src/config/utils.js | 9 + server/src/enums/CloneMode.js | 11 + server/src/enums/CloneState.js | 19 + server/src/errors/index.js | 66 ++ server/src/errors/notAllowed.json | 8 + server/src/index.js | 43 + server/src/kubernetes/createClone.js | 99 +++ server/src/kubernetes/getClonePodStatus.js | 32 + server/src/kubernetes/getClonePodlogs.js | 34 + server/src/kubernetes/removeClone.js | 32 + server/src/models/clone.js | 47 + server/src/models/index.js | 5 + server/src/schema/index.js | 13 + .../src/schema/mutations/AddCloneMutation.js | 125 +++ .../schema/mutations/RemoveCloneMutation.js | 54 ++ server/src/schema/mutations/index.js | 16 + server/src/schema/queries/AuditLogsQuery.js | 40 + server/src/schema/queries/ExchangesQuery.js | 23 + server/src/schema/queries/ListClones.js | 41 + server/src/schema/queries/index.js | 14 + server/src/schema/types/AuditLogType.js | 29 + server/src/schema/types/CloneType.js | 28 + server/src/schema/types/ExchangeType.js | 16 + .../src/schema/types/SignedTransactionType.js | 15 + server/src/schema/types/index.js | 5 + server/src/schema/types/input/Clone.js | 24 + server/src/schema/types/input/index.js | 5 + 64 files changed, 3524 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 client/.gitignore create mode 100644 client/CODE_OF_CONDUCT.md create mode 100644 client/CONTRIBUTING.md create mode 100644 client/LICENSE create mode 100644 client/README.md create mode 100644 client/package-lock.json create mode 100644 client/package.json create mode 100644 client/src/GraphQL/Calls/clones.js create mode 100644 client/src/GraphQL/Calls/index.js create mode 100644 client/src/GraphQL/Calls/teams.js create mode 100644 client/src/GraphQL/Fragments/clone.js create mode 100644 client/src/GraphQL/Fragments/index.js create mode 100644 client/src/GraphQL/index.js create mode 100644 client/src/GraphQL/models.js create mode 100644 client/src/img/binance.png create mode 100644 client/src/img/poloniex.png create mode 100644 client/src/index.js create mode 100644 client/src/theme.js create mode 100644 client/src/utils/index.js create mode 100644 client/src/views/BannerTopBar/index.js create mode 100644 client/src/views/Clone/Add/index.js create mode 100644 client/src/views/Clone/Add/styles.js create mode 100644 client/src/views/Clone/List/CloneDialog.js create mode 100644 client/src/views/Clone/List/ListClones.js create mode 100644 client/src/views/Clone/List/index.js create mode 100644 client/src/views/Clone/List/styles.js create mode 100644 client/src/views/History/AuditLogList.js create mode 100644 client/src/views/Home.js create mode 100644 package-lock.json create mode 100644 server/src/config/clone-deployment.json create mode 100644 server/src/config/envConfig.js create mode 100644 server/src/config/logger.js create mode 100644 server/src/config/utils.js create mode 100644 server/src/enums/CloneMode.js create mode 100644 server/src/enums/CloneState.js create mode 100644 server/src/errors/index.js create mode 100644 server/src/errors/notAllowed.json create mode 100644 server/src/index.js create mode 100644 server/src/kubernetes/createClone.js create mode 100644 server/src/kubernetes/getClonePodStatus.js create mode 100644 server/src/kubernetes/getClonePodlogs.js create mode 100644 server/src/kubernetes/removeClone.js create mode 100644 server/src/models/clone.js create mode 100644 server/src/models/index.js create mode 100644 server/src/schema/index.js create mode 100644 server/src/schema/mutations/AddCloneMutation.js create mode 100644 server/src/schema/mutations/RemoveCloneMutation.js create mode 100644 server/src/schema/mutations/index.js create mode 100644 server/src/schema/queries/AuditLogsQuery.js create mode 100644 server/src/schema/queries/ExchangesQuery.js create mode 100644 server/src/schema/queries/ListClones.js create mode 100644 server/src/schema/queries/index.js create mode 100644 server/src/schema/types/AuditLogType.js create mode 100644 server/src/schema/types/CloneType.js create mode 100644 server/src/schema/types/ExchangeType.js create mode 100644 server/src/schema/types/SignedTransactionType.js create mode 100644 server/src/schema/types/index.js create mode 100644 server/src/schema/types/input/Clone.js create mode 100644 server/src/schema/types/input/index.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..c71b685b4 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -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 advancedalgos 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/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..311653120 --- /dev/null +++ b/CONTRIBUTING.md @@ -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! + +Advanced Algos LTD is a startup, 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 regards. + +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 Advanced Algos LTD ("Company"). + +**What is this?** This is the Company'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 Company. + +**Why is this?** The Company needs to be clear on what IP it owns and has rights to. Its customers, contributors, and investors depend on the Company having the legal rights to the products and services it is providing so that the Company can continue operating and doing business. + +The Company also believes that it's important to be clear on what it doesn't own. The Company doesn't want you looking over your shoulder every time you work on something personal or worrying that the Company will someday seize your open source non-lethal mousetrap simulation software. In other words, the Company 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 Company cannot, by and large, negotiate its terms. If you feel you have a particular circumstance that keeps you from signing, please let the Company'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 Company owns.** The Company owns any IP ("Company 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 Company product or service at the time you developed, invented, or created it, (ii) developed for use by the Company, or (iii) developed or promoted with existing Company IP or with the Company's endorsement. You hereby grant and assign, and will grant and assign, to the Company all rights and interests in all Company IP. + + "Company IP" includes concepts, designs, developments, discoveries, ideas, improvements, inventions, trade secrets, trademarks, and works of authorship. + +2. **What the Company doesn't own.** If you create IP that doesn't fit into one of the categories listed above, the Company doesn't own it. In other words, the Company 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 Company projects.** If you include your own IP – such as IP you created prior to working for the Company – into a Company product or service, it's still yours, of course, but you grant the Company 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 Company 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-Company projects.** The Company recognizes that you may be engaged in work that requires you to submit IP to entities other than the Company, such as open source projects used by the Company. 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 Company and so should be run through Legal first. + +5. **Voluntary Works** All Company IP that you are involved with or create as part of your work for the Company is voluntary work which does not require compensation. There might be incentives in place for certain kinds of contributions. Feel free to contact the Company 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 Company 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 Company IP or IP under License (together, "All IP"), and, if the Company asks, to provide those records to the Company. You agree to help the Company secure and defend its rights in All IP. For your help the Company will compensate you at a reasonable rate. If the Company is unable to secure your help, you authorize the Company 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 Company, you will have access to sensitive confidential information that is important to the Company's business. You are responsible for keeping this information confidential, including after you end your work for the Company. + + "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 Company because it is not publicly known. + + The Company's Confidential Information is sensitive and we expect that you'll treat it as such. You agree to only use the Company's Confidential Information for the purpose of performing your contribution and for the benefit of the Company. 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. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..c049df9cb --- /dev/null +++ b/LICENSE @@ -0,0 +1,42 @@ +COPYRIGHT NOTICE + +© Copyright 2018 Advanced Algos LTD. + + +LICENSE NOTICE + +Advanced Algos LTD. is a startup, currently at a very early stage. Because certain aspects of +the business are still not clearly defined, we have chosen to --temporarily-- grant a +restrictive license for the use of this repository, until those business aspects are clarified +and a proper open source license can be granted. + +Advanced Algos LTD. is publishing the code of this software for several reasons. However, +it shall not be considered free software yet. Advanced Algos LTD. is a for-profit company +in the crypto space and this module is still proprietary software. + +The reasons for opening up the code of this module 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 module does and how. Opening up the code of this module + 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 + +Advanced Algos LTD. 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 ADVANCED ALGOS LTD. You are granted permission to fork this repository + with the sole purpose of correcting bugs and developing functionality in agreement with + Advanced Algos LTD. 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. + + diff --git a/README.md b/README.md index aedd18b8e..e61542682 100644 --- a/README.md +++ b/README.md @@ -1 +1,4 @@ -# OperationsModule \ No newline at end of file + +## AA Operations Module + +This module allow users to control the bot instance on the virtual machine. diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 000000000..d30f40ef4 --- /dev/null +++ b/client/.gitignore @@ -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* diff --git a/client/CODE_OF_CONDUCT.md b/client/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..c71b685b4 --- /dev/null +++ b/client/CODE_OF_CONDUCT.md @@ -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 advancedalgos 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/ diff --git a/client/CONTRIBUTING.md b/client/CONTRIBUTING.md new file mode 100644 index 000000000..311653120 --- /dev/null +++ b/client/CONTRIBUTING.md @@ -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! + +Advanced Algos LTD is a startup, 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 regards. + +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 Advanced Algos LTD ("Company"). + +**What is this?** This is the Company'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 Company. + +**Why is this?** The Company needs to be clear on what IP it owns and has rights to. Its customers, contributors, and investors depend on the Company having the legal rights to the products and services it is providing so that the Company can continue operating and doing business. + +The Company also believes that it's important to be clear on what it doesn't own. The Company doesn't want you looking over your shoulder every time you work on something personal or worrying that the Company will someday seize your open source non-lethal mousetrap simulation software. In other words, the Company 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 Company cannot, by and large, negotiate its terms. If you feel you have a particular circumstance that keeps you from signing, please let the Company'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 Company owns.** The Company owns any IP ("Company 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 Company product or service at the time you developed, invented, or created it, (ii) developed for use by the Company, or (iii) developed or promoted with existing Company IP or with the Company's endorsement. You hereby grant and assign, and will grant and assign, to the Company all rights and interests in all Company IP. + + "Company IP" includes concepts, designs, developments, discoveries, ideas, improvements, inventions, trade secrets, trademarks, and works of authorship. + +2. **What the Company doesn't own.** If you create IP that doesn't fit into one of the categories listed above, the Company doesn't own it. In other words, the Company 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 Company projects.** If you include your own IP – such as IP you created prior to working for the Company – into a Company product or service, it's still yours, of course, but you grant the Company 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 Company 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-Company projects.** The Company recognizes that you may be engaged in work that requires you to submit IP to entities other than the Company, such as open source projects used by the Company. 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 Company and so should be run through Legal first. + +5. **Voluntary Works** All Company IP that you are involved with or create as part of your work for the Company is voluntary work which does not require compensation. There might be incentives in place for certain kinds of contributions. Feel free to contact the Company 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 Company 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 Company IP or IP under License (together, "All IP"), and, if the Company asks, to provide those records to the Company. You agree to help the Company secure and defend its rights in All IP. For your help the Company will compensate you at a reasonable rate. If the Company is unable to secure your help, you authorize the Company 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 Company, you will have access to sensitive confidential information that is important to the Company's business. You are responsible for keeping this information confidential, including after you end your work for the Company. + + "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 Company because it is not publicly known. + + The Company's Confidential Information is sensitive and we expect that you'll treat it as such. You agree to only use the Company's Confidential Information for the purpose of performing your contribution and for the benefit of the Company. 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. \ No newline at end of file diff --git a/client/LICENSE b/client/LICENSE new file mode 100644 index 000000000..c049df9cb --- /dev/null +++ b/client/LICENSE @@ -0,0 +1,42 @@ +COPYRIGHT NOTICE + +© Copyright 2018 Advanced Algos LTD. + + +LICENSE NOTICE + +Advanced Algos LTD. is a startup, currently at a very early stage. Because certain aspects of +the business are still not clearly defined, we have chosen to --temporarily-- grant a +restrictive license for the use of this repository, until those business aspects are clarified +and a proper open source license can be granted. + +Advanced Algos LTD. is publishing the code of this software for several reasons. However, +it shall not be considered free software yet. Advanced Algos LTD. is a for-profit company +in the crypto space and this module is still proprietary software. + +The reasons for opening up the code of this module 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 module does and how. Opening up the code of this module + 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 + +Advanced Algos LTD. 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 ADVANCED ALGOS LTD. You are granted permission to fork this repository + with the sole purpose of correcting bugs and developing functionality in agreement with + Advanced Algos LTD. 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. + + diff --git a/client/README.md b/client/README.md new file mode 100644 index 000000000..8bbd3204b --- /dev/null +++ b/client/README.md @@ -0,0 +1,4 @@ + +## AA Operations Web Client + +This client allows the users to interact via web browser. diff --git a/client/package-lock.json b/client/package-lock.json new file mode 100644 index 000000000..9a3c14063 --- /dev/null +++ b/client/package-lock.json @@ -0,0 +1,833 @@ +{ + "name": "@advancedalgos/operations-client", + "version": "0.0.1", + "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-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" + } + } + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 000000000..21b26466f --- /dev/null +++ b/client/package.json @@ -0,0 +1,35 @@ +{ + "name": "@advancedalgos/operations-client", + "version": "0.0.1", + "description": "Advanced Algos 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-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" + ] +} diff --git a/client/src/GraphQL/Calls/clones.js b/client/src/GraphQL/Calls/clones.js new file mode 100644 index 000000000..417200d08 --- /dev/null +++ b/client/src/GraphQL/Calls/clones.js @@ -0,0 +1,32 @@ +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_REMOVE_CLONE = gql` + mutation Operations_RemoveClone($id: ID!){ + operations_RemoveClone( id: $id) + } +`; + +export default { + OPERATIONS_LIST_CLONES, + OPERATIONS_ADD_CLONE, + OPERATIONS_REMOVE_CLONE +}; diff --git a/client/src/GraphQL/Calls/index.js b/client/src/GraphQL/Calls/index.js new file mode 100644 index 000000000..f410adf89 --- /dev/null +++ b/client/src/GraphQL/Calls/index.js @@ -0,0 +1,7 @@ +import clones from './clones'; +import teams from './teams'; + +export { + clones, + teams +}; diff --git a/client/src/GraphQL/Calls/teams.js b/client/src/GraphQL/Calls/teams.js new file mode 100644 index 000000000..1a5f02f5b --- /dev/null +++ b/client/src/GraphQL/Calls/teams.js @@ -0,0 +1,38 @@ +import gql from 'graphql-tag'; + +export const GET_ALL_TEAMS_QUERY = gql` + query Teams_FbByTeamMember { + teams_FbByTeamMember { + id + name + slug + fb { + id + name + slug + } + } + } +` + +export const GET_ALL_TEAM_BOTS_QUERY = gql` + query teamBotsQuery($teamId: String!) { + teams_TeamById(teamId: $teamId) { + fb { + id + name + slug + team{ + id + name + slug + } + } + } + } +` + +export default { + GET_ALL_TEAMS_QUERY, + GET_ALL_TEAM_BOTS_QUERY +}; diff --git a/client/src/GraphQL/Fragments/clone.js b/client/src/GraphQL/Fragments/clone.js new file mode 100644 index 000000000..f362235f5 --- /dev/null +++ b/client/src/GraphQL/Fragments/clone.js @@ -0,0 +1,20 @@ +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 + } +`; diff --git a/client/src/GraphQL/Fragments/index.js b/client/src/GraphQL/Fragments/index.js new file mode 100644 index 000000000..38211844b --- /dev/null +++ b/client/src/GraphQL/Fragments/index.js @@ -0,0 +1,5 @@ +import { clone } from './clone'; + +export { + clone +}; diff --git a/client/src/GraphQL/index.js b/client/src/GraphQL/index.js new file mode 100644 index 000000000..51f292fb3 --- /dev/null +++ b/client/src/GraphQL/index.js @@ -0,0 +1,56 @@ +import gql from 'graphql-tag' + +const getSecret = gql` + query($id: ID!){ + keyVault_Secret(id: $id) + } +` + +const editCloneMutation = gql` + mutation($id: ID!, $type: String!, $description: String!, $validFrom: Int!, + $validTo: Int!, $active: Boolean!, $botId: ID!){ + operations_EditClone( + id: $id, + type: $type, + description: $description, + validFrom: $validFrom, + validTo: $validTo, + active: $active, + botId: $botId + ){ + id + key + } + } +` + +const getCloneQuery = gql` + query($id: ID!){ + opeations_Clone(id: $id) { + id + authId + key + type + description + exchange + validFrom + validTo + active + botId + } + } +` + +const getAuditLog = gql` + query($key: String!){ + keyVault_AuditLogs(key: $key){ + id, + date, + action, + details + } + } +` + +export { getSecret, editCloneMutation, + getCloneQuery, getAuditLog } diff --git a/client/src/GraphQL/models.js b/client/src/GraphQL/models.js new file mode 100644 index 000000000..252f1ee61 --- /dev/null +++ b/client/src/GraphQL/models.js @@ -0,0 +1,10 @@ +const types = [ 'Live', 'Competition', 'Backtest' ] + +const exchanges = [ + { + id: '1', + name: 'Poloniex' + } +] + +export { types, exchanges } diff --git a/client/src/img/binance.png b/client/src/img/binance.png new file mode 100644 index 0000000000000000000000000000000000000000..18ce7c6384e7f79ff732282bae026488373dcc02 GIT binary patch literal 3509 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 literal 0 HcmV?d00001 diff --git a/client/src/img/poloniex.png b/client/src/img/poloniex.png new file mode 100644 index 0000000000000000000000000000000000000000..dfe1437817db82675aa8ddd0eecda22be7fc0e0f GIT binary patch literal 11209 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 ( + + + + + + + +) + +export default App diff --git a/client/src/theme.js b/client/src/theme.js new file mode 100644 index 000000000..a0abae869 --- /dev/null +++ b/client/src/theme.js @@ -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 diff --git a/client/src/utils/index.js b/client/src/utils/index.js new file mode 100644 index 000000000..d81838754 --- /dev/null +++ b/client/src/utils/index.js @@ -0,0 +1,74 @@ +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 +} diff --git a/client/src/views/BannerTopBar/index.js b/client/src/views/BannerTopBar/index.js new file mode 100644 index 000000000..263ea7bee --- /dev/null +++ b/client/src/views/BannerTopBar/index.js @@ -0,0 +1,18 @@ +import React from 'react' +import Typography from '@material-ui/core/Typography' + +const TopBar = ({ + size, + backgroundUrl, + title, + text +}) => ( +
+
+ {title} + {text} +
+
+) + +export default TopBar diff --git a/client/src/views/Clone/Add/index.js b/client/src/views/Clone/Add/index.js new file mode 100644 index 000000000..aa11e0efc --- /dev/null +++ b/client/src/views/Clone/Add/index.js @@ -0,0 +1,367 @@ +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} from '../../../GraphQL/models' +import { isDefined } from '../../../utils' + +import { + MenuItem, Button, TextField, FormControl, InputLabel, Input, Typography, + Paper, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, + Select, FormControlLabel, Checkbox +} from '@material-ui/core' + +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), + teamId:'', + botId:'', + mode:'', + resumeExecution: false, + beginDatetime: DateTime.local().minus({ days: 8 }).startOf('day'), + endDatetime: DateTime.local(), + waitTime: 1, + state : '', + stateDatetime: 0, + createDatetime: 0, + runAsTeam: false, + + //Error handlers + nameError: false, + teamError: false, + botError: false, + modeError: false, + isNewCloneConfirmationOpen: false, + serverResponse:'', + serverError: false + } + } + + render() { + const { classes } = this.props + if( !isDefined(this.state.user) ) { + return ( + + ) + } else return ( + + + +
+ + +
+ + Create a Bot Clone + + + + A bot clone it's a copy of your bot running on a virtual machine. + At the moment Live clones trade by default 0.001 BTC on Poloniex. + Please select the team and bot you want to clone! + + + + + Select Team and Bot + + + {({ loading, error, data }) => { + if (loading) { + return ( + + ) + } else if(!isDefined(data.teams_FbByTeamMember)){ + return ( + + ) + } else if (error) return `Error! There has been an error loading teams.` + + return ( + + ) + }} + + + + this.setState({mode:e.target.value})} + onBlur={(e)=>this.setState({modeError:false})} + error={this.state.modeError} + fullWidth + > + {types.map(option => ( + + {option} + + ))} + + + { this.state.mode == "Backtest" && + + } + rightArrowIcon={} + value={this.state.beginDatetime} + label='Backtest Start Date' + onChange={newVal => this.setState({ beginDatetime: newVal })} + className={classNames(classes.form, classes.textField)} + fullWidth + /> + + } + rightArrowIcon={} + value={this.state.endDatetime} + label='Backtest End Date' + onChange={newVal => this.setState({ endDatetime: newVal })} + className={classNames(classes.form, classes.textField)} + fullWidth + /> + + this.setState({waitTime:e.target.value})} + fullWidth + /> + + } + + + 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. + + this.setState({runAsTeam:e.target.checked })} + value="runAsTeam" + color="primary" + /> + } + label="Run as Team" + className={classNames(classes.form, classes.textField)} + /> + + + The Resume Execution option let's you pick up the context of + the last execution and continue from there. + + this.setState({resumeExecution:e.target.checked })} + value="resumeExecution" + color="primary" + /> + } + label="Resume Execution" + className={classNames(classes.form, classes.textField)} + /> + +
+ +
+ + + + + Creating bot clone + + + {this.state.serverResponse} + + + + + + +
+
+
+ ); + } + + 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:{ + teamId: this.state.teamId, + botId: this.state.botId, + 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 + } + + return this.props.addCloneMutation({ + variables: variables + //refetchQueries: [{query: getClonesQuery}], + }) + } + + handleNewCloneConfirmationOpen = () => { + this.setState({ isNewCloneConfirmationOpen: true }) + }; + + handleNewCloneConfirmationClose = () => { + this.setState({ + isNewCloneConfirmationOpen: false + }) + + if(!this.state.serverError) + this.setState({ + teamId:'', + botId:'', + mode:'', + resumeExecution: false, + beginDatetime: DateTime.local().minus({ days: 8 }).startOf('day'), + endDatetime: DateTime.local(), + waitTime: 1, + state : '', + stateDatetime: 0, + createDatetime: 0, + runAsTeam: false, + + //Error handlers + nameError: false, + teamError: false, + botError: false, + modeError: false, + isNewCloneConfirmationOpen: false, + serverResponse: '', + serverError: false + }) + }; + + validate(){ + let isError = false + + if(this.state.teamId.length < 1) { + isError = true + this.setState(state => ({ teamError: true })); + } + + if(this.state.mode.length < 1) { + isError = true + this.setState(state => ({ modeError: true })); + } + + return isError; + + } +} + +export default compose( + graphql(clones.OPERATIONS_ADD_CLONE, { name:'addCloneMutation' }), + withStyles(styles) +)(AddClone) diff --git a/client/src/views/Clone/Add/styles.js b/client/src/views/Clone/Add/styles.js new file mode 100644 index 000000000..c9339ec38 --- /dev/null +++ b/client/src/views/Clone/Add/styles.js @@ -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; diff --git a/client/src/views/Clone/List/CloneDialog.js b/client/src/views/Clone/List/CloneDialog.js new file mode 100644 index 000000000..46525d2c1 --- /dev/null +++ b/client/src/views/Clone/List/CloneDialog.js @@ -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 ( +
+ + + + Add a new Clone + + + + Description... + + + + +
+ ) + } +} + +export default withStyles(styles)(CloneDialog) diff --git a/client/src/views/Clone/List/ListClones.js b/client/src/views/Clone/List/ListClones.js new file mode 100644 index 000000000..c3354d988 --- /dev/null +++ b/client/src/views/Clone/List/ListClones.js @@ -0,0 +1,228 @@ +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 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 ( + + + }> +
+ {clone.teamId} +
+
+ {clone.botId} +
+
+ {clone.mode} +
+
+ + +
+ Created: + { isDefined(clone.beginDatetime) && + Begin: + } + { isDefined(clone.endDatetime) && + End: + } + { isDefined(clone.waitTime) && + Wait Time: + } + Resume Execution: + Run As Team: +
+
+ {toLocalTime(clone.createDatetime)} + { isDefined(clone.beginDatetime) && + {toLocalTime(clone.beginDatetime)} + } + { isDefined(clone.endDatetime) && + {toLocalTime(clone.endDatetime)} + } + { isDefined(clone.waitTime) && + {clone.waitTime} + } + {clone.resumeExecution ? 'Yes':'No'} + {clone.runAsTeam ? 'Yes':'No'} +
+
+ Virtual Machine Status + +
+
+ + + + + +
+ + + + Delete Clone + + + + 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. + + + + + + + + + + + + Last clone logs + + + + + + + + + +
+ ) + } + + 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) diff --git a/client/src/views/Clone/List/index.js b/client/src/views/Clone/List/index.js new file mode 100644 index 000000000..726fd6ded --- /dev/null +++ b/client/src/views/Clone/List/index.js @@ -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 ( + + ) + } else if (data.operations_Clones && data.operations_Clones.length > 0) { + return ( + + + +
+ { + data.operations_Clones.map((clone, i) => { + return ( + + ) + }) + } +
+
+ ) + } else if (data.error) { + return ( + + ) + } else { + return ( + + ) + } + } + + getBodyContent (data) { + if (data.loading) { + return ( + + ) + } else if (data.operations_Clones && data.operations_Clones.length > 0) { + return ( + + + +
+ { + data.operations_Clones.map((clone, i) => { + return ( + + ) + }) + } +
+
+ ) + } else if (data.error) { + return ( + + ) + } else { + return ( + + + ) + } + } + +} + +export default graphql(clones.OPERATIONS_LIST_CLONES)(BrowseClones) diff --git a/client/src/views/Clone/List/styles.js b/client/src/views/Clone/List/styles.js new file mode 100644 index 000000000..652be82cd --- /dev/null +++ b/client/src/views/Clone/List/styles.js @@ -0,0 +1,82 @@ +const styles = theme => ({ + root: { + flexGrow: 1, + padding: 10, + 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: { + marginLeft: theme.spacing.unit, + marginRight: theme.spacing.unit, + whiteSpace: 'pre', + overflowY:'auto', + overflowX:'hidden' + }, + heading: { + fontSize: theme.typography.pxToRem(18), + flexBasis: '33.33%', + fontWeight: '600' + }, + helper: { + borderLeft: `2px solid ${theme.palette.divider}`, + padding: `${theme.spacing.unit}px ${theme.spacing.unit * 2}px`, + }, + logsDialog:{ + width:'1750px', + overflowY:'auto', + overflowX:'hidden' + }, + details:{ + display:'flex' + }, + column: { + flexBasis: '15%', + maxWidth: '15%' + }, + column2: { + flexBasis: '42.5%', + maxWidth: '42.5%' + }, + column3: { + flexBasis: '42.5%', + maxWidth: '42.5%' + }, + cloneInfo1:{ + fontWeight: '600', + paddingLeft: '16px', + paddingTop: '16px' + }, + cloneInfo2:{ + paddingLeft: '16px', + paddingTop: '16px' + }, +}) + +export default styles diff --git a/client/src/views/History/AuditLogList.js b/client/src/views/History/AuditLogList.js new file mode 100644 index 000000000..e9d18f38c --- /dev/null +++ b/client/src/views/History/AuditLogList.js @@ -0,0 +1,87 @@ +import React, { Component } from 'react' +import {graphql, compose} from 'react-apollo' +import { getAuditLog } from '../../../queries' + +import { Table, TableBody, TableCell, TableHead, TableRow, Typography } from '@material-ui/core'; + +import { withStyles } from '@material-ui/core/styles' +import { toLocalTime } from '../../../utils' + +const styles = theme => ({ + root: { + flexGrow: 1, + padding: 20, + margin: 10 + } +}); + + +class AuditLogList extends Component { + + constructor(props){ + super(props) + const key = this.props.currentKey + this.state = { + id: key.id, + key: key.key, + date:'', + action: '' + } + } + + render() { + const { classes } = this.props + var data = this.props.data + if(data.loading){ + return Loading... + }else{ + + if(!data.keyVault_AuditLogs){ + return There has been an erorr. + } + if(data.keyVault_AuditLogs.length === 0){ + return There is no audit history for this key. + } + + return ( + + + + Date + Action + Details + + + + + {data.keyVault_AuditLogs.map(row => { + return ( + + + {toLocalTime(row.date)} + + {row.action} + {row.details} + + ); + })} + +
+ ) + } + } + +} + +export default compose( + graphql(getAuditLog, { // What follows is the way to pass a parameter to a query. + options: (props) => { + return { + variables: { + key: props.currentKey.id + } + } + } + }), + withStyles(styles) +)(AuditLogList) diff --git a/client/src/views/Home.js b/client/src/views/Home.js new file mode 100644 index 000000000..a91fd49a4 --- /dev/null +++ b/client/src/views/Home.js @@ -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 ( + + + +
+ + Welcome to the Operations Module! + + + Responsible for manage bot clones. + +
+ + The Operations module allows you to create copies of your bot + that will be executed on a virtual machine. + +
+
+ + + +
+
+ +
+ ) + } +} + +export default Home diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..48e341a09 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3 @@ +{ + "lockfileVersion": 1 +} diff --git a/server/src/config/clone-deployment.json b/server/src/config/clone-deployment.json new file mode 100644 index 000000000..e46470066 --- /dev/null +++ b/server/src/config/clone-deployment.json @@ -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.2", + "command": ["node", "Run.js"], + "env": [ + ] + } + ] + }, + "metadata": { + "labels": { + "app": "aadev" + } + } + } + } +} diff --git a/server/src/config/envConfig.js b/server/src/config/envConfig.js new file mode 100644 index 000000000..62d7d6073 --- /dev/null +++ b/server/src/config/envConfig.js @@ -0,0 +1,47 @@ +import 'dotenv/config' + +const env = [] + +env.push({ + "name": "PLATFORM_ENVIRONMENT", + "value": process.env.PLATFORM_ENVIRONMENT +}) + +// FIXME +env.push({ + "name": "GATEWAY_ENDPOINT", + "value": "https://platform-api-dev.advancedalgos.net/graphql" + //"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 diff --git a/server/src/config/logger.js b/server/src/config/logger.js new file mode 100644 index 000000000..434fb2354 --- /dev/null +++ b/server/src/config/logger.js @@ -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 diff --git a/server/src/config/utils.js b/server/src/config/utils.js new file mode 100644 index 000000000..11f9909fa --- /dev/null +++ b/server/src/config/utils.js @@ -0,0 +1,9 @@ +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' +} diff --git a/server/src/enums/CloneMode.js b/server/src/enums/CloneMode.js new file mode 100644 index 000000000..12b3eb37c --- /dev/null +++ b/server/src/enums/CloneMode.js @@ -0,0 +1,11 @@ +export const LIVE = 'Live'; +export const BACKTEST = 'Backtest'; +export const COMPETITION = 'Competition'; + +export const CloneModeEnum = [ + LIVE, + BACKTEST, + COMPETITION +]; + +export default CloneModeEnum; diff --git a/server/src/enums/CloneState.js b/server/src/enums/CloneState.js new file mode 100644 index 000000000..e82389e90 --- /dev/null +++ b/server/src/enums/CloneState.js @@ -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; diff --git a/server/src/errors/index.js b/server/src/errors/index.js new file mode 100644 index 000000000..dea8803fb --- /dev/null +++ b/server/src/errors/index.js @@ -0,0 +1,66 @@ +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) + logger.error(this.stack) + this.stack = '' + } +} + +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 = '' + } +} diff --git a/server/src/errors/notAllowed.json b/server/src/errors/notAllowed.json new file mode 100644 index 000000000..b76604aa2 --- /dev/null +++ b/server/src/errors/notAllowed.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "code": 401, + "message": "Wait, you're not supposed to be here, go through the gate as everyone else" + } + ] +} diff --git a/server/src/index.js b/server/src/index.js new file mode 100644 index 000000000..02cdaa6fd --- /dev/null +++ b/server/src/index.js @@ -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}.`) +}) diff --git a/server/src/kubernetes/createClone.js b/server/src/kubernetes/createClone.js new file mode 100644 index 000000000..ff89142a6 --- /dev/null +++ b/server/src/kubernetes/createClone.js @@ -0,0 +1,99 @@ +import logger from '../config/logger' +import { toPlatformDatetime } 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, cloneName) => { + try { + logger.info("createClone %j", cloneName) + const client = new Client({config: config.fromKubeconfig(), version: '1.9'}) + + // Make changes to base deployment config + deploymentManifest.metadata.name = cloneName + + logger.debug("createClone Set basic environment configuration.") + let env = envConfig + + logger.debug("createClone Configuration for team, bot and process.") + env.push({ + "name": "DEV_TEAM", + "value": clone.teamId + }) + + env.push({ + "name": "BOT", + "value": clone.botId + }) + + // TODO allow other types + env.push({ + "name": "PROCESS", + "value": "Trading-Process" + }) + + // TODO allow other types + env.push({ + "name": "TYPE", + "value": "Trading" + }) + + env.push({ + "name": "RUN_AS_TEAM", + "value": clone.runAsTeam.toString() + }) + + env.push({ + "name": "USER_LOGGED_IN", + "value": clone.userLoggedIn + }) + + logger.debug("createClone Configuration for the execution.") + env.push({ + "name": "START_MODE", + "value": clone.mode.toLowerCase() + }) + + 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() + }) + } + + deploymentManifest.spec.template.spec.containers[0].env = env + + // Create a new Deployment + const create = await client.apis.batch.v1.namespaces('default').jobs.post( + { body: deploymentManifest }) + logger.debug("Create: %j ", create) + + // Fetch the Deployment we just created + const deployment = await client.apis.batch.v1.namespaces('default').jobs( + deploymentManifest.metadata.name).get() + logger.debug("Job: %j ", deployment) + + logger.info("createClone %j succesfull", cloneName) + } catch (err) { + throw new KubernateError(err) + } +} +export default createClone diff --git a/server/src/kubernetes/getClonePodStatus.js b/server/src/kubernetes/getClonePodStatus.js new file mode 100644 index 000000000..ee4dfb835 --- /dev/null +++ b/server/src/kubernetes/getClonePodStatus.js @@ -0,0 +1,32 @@ +import logger from '../config/logger' +import { KubernateError } from '../errors' +import { Client, config } from 'kubernetes-client' + +const getClonePodStatus = async (cloneName) => { + try { + logger.info("getClonePodStatus %j", cloneName) + const client = new Client({config: config.fromKubeconfig(), version: '1.9'}) + + let query = { + "qs":{ + "labelSelector": "job-name="+cloneName + } + } + // Get The Pod + const pod = await client.api.v1.namespaces('default').pods.get(query) + logger.debug("pod: %j ", pod) + + let clonePodStatus = pod.body.items[0].status.containerStatuses[0].state + logger.debug("clonePodStatus: %j ", clonePodStatus) + + if (clonePodStatus.hasOwnProperty('terminated') + && clonePodStatus.terminated.hasOwnProperty('containerID')) { + delete clonePodStatus.terminated.containerID + } + logger.info("getClonePodStatus %j succesfull", cloneName) + return JSON.stringify(clonePodStatus, null, 2) + } catch (err) { + throw new KubernateError(err) + } +} +export default getClonePodStatus diff --git a/server/src/kubernetes/getClonePodlogs.js b/server/src/kubernetes/getClonePodlogs.js new file mode 100644 index 000000000..a3f8d129a --- /dev/null +++ b/server/src/kubernetes/getClonePodlogs.js @@ -0,0 +1,34 @@ +import logger from '../config/logger' +import { KubernateError } from '../errors' +import { Client, config } from 'kubernetes-client' + +const getClonePodStatus = async (cloneName) => { + try { + logger.info("getClonePodStatus %j", cloneName) + const client = new Client({config: config.fromKubeconfig(), version: '1.9'}) + + let query = { + "qs":{ + "labelSelector": "job-name="+cloneName + } + } + // Get the pod + const pod = await client.api.v1.namespaces('default').pods.get(query) + // logger.debug("pod: %j ", pod) + + let queryLogs = { + "qs":{ + "tailLines": "15" + } + } + 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("logs: %j ", logsArray) + + return JSON.stringify(logsArray, null, 2) + } catch (err) { + throw new KubernateError(err) + } +} +export default getClonePodStatus diff --git a/server/src/kubernetes/removeClone.js b/server/src/kubernetes/removeClone.js new file mode 100644 index 000000000..4bb2a3e8d --- /dev/null +++ b/server/src/kubernetes/removeClone.js @@ -0,0 +1,32 @@ +import logger from '../config/logger' +import { KubernateError } from '../errors' +import { Client, config } from 'kubernetes-client' + +const removeClone = async (cloneName) => { + try { + logger.info("removeClone %j", cloneName) + const client = new Client({config: config.fromKubeconfig(), version: '1.9'}) + + let query = { + "qs":{ + "labelSelector": "job-name="+cloneName + } + } + + // Remove the Job + const removed = await client.apis.batch.v1.namespaces('default').jobs( + cloneName).delete() + logger.debug("Job Removed: %j ", removed) + + // Get the pod + 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.debug("Pod Removed: %j ", podRemoved) + + logger.info("removeClone %j succesfull", cloneName) + } catch (err) { + throw new KubernateError(err) + } +} +export default removeClone diff --git a/server/src/models/clone.js b/server/src/models/clone.js new file mode 100644 index 000000000..f1b0bc3be --- /dev/null +++ b/server/src/models/clone.js @@ -0,0 +1,47 @@ +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 +}) + +const Clone = mongoose.model('Clone', cloneSchema) + +export default Clone diff --git a/server/src/models/index.js b/server/src/models/index.js new file mode 100644 index 000000000..288fb0f47 --- /dev/null +++ b/server/src/models/index.js @@ -0,0 +1,5 @@ +import Clone from './clone' + +export { + Clone +} diff --git a/server/src/schema/index.js b/server/src/schema/index.js new file mode 100644 index 000000000..4155e4999 --- /dev/null +++ b/server/src/schema/index.js @@ -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 diff --git a/server/src/schema/mutations/AddCloneMutation.js b/server/src/schema/mutations/AddCloneMutation.js new file mode 100644 index 000000000..9195fb0d0 --- /dev/null +++ b/server/src/schema/mutations/AddCloneMutation.js @@ -0,0 +1,125 @@ +import { CloneInputType } from '../types/input'; +import { CloneType } from '../types'; +import { Clone } from '../../models'; +import { CloneModeEnum , BACKTEST} from '../../enums/CloneMode'; + +import { + AuthentificationError, + WrongArgumentsError, + OperationsError, + CustomError +} from '../../errors' + +import axios from 'axios'; +import logger from '../../config/logger' +import createKubernetesClone from '../../kubernetes/createClone' +import { isDefined } 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.') + } + + // Disable execution dates if mode is not backtest + if((clone.hasOwnProperty('beginDatetime') + || clone.hasOwnProperty('endDatetime')) + && clone.mode !== BACKTEST){ + throw new WrongArgumentsError('Ony Backtest mode allows begin and end date selection.') + } + + let existingClone = await Clone.find( + { + authId: context.userId, + mode: clone.mode + }) + + logger.debug('addClone -> Checking existing clone %j', existingClone) + if(isDefined(existingClone) && existingClone.length > 0){ + throw new CustomError('You can only create one clone per mode. Remove the' + + ' existing clone of type: ' + clone.mode + ' and try again.') + } + + logger.debug('addClone -> Creating a new clone. %j', clone) + + try{ + const userTeam = await axios({ + url: process.env.GATEWAY_ENDPOINT, + method: 'post', + data: { + query: ` + query Teams_FbByTeamMember { + teams_FbByTeamMember { + id + name + slug + fb { + id + name + slug + } + members { + member { + alias + } + } + } + } + `, + }, + headers: { + authorization: context.authorization, + preshared: process.env.OPERATIONS_API_PRESHARED + } + }) + + clone.teamId = userTeam.data.data.teams_FbByTeamMember.slug + clone.botId = userTeam.data.data.teams_FbByTeamMember.fb[0].slug + clone.userLoggedIn = userTeam.data.data.teams_FbByTeamMember.members[0].member.alias + clone.createDatetime = new Date().valueOf() / 1000|0 + + let newClone = new Clone(clone) + + await createKubernetesClone(clone, newClone._id) + + newClone.id = newClone._id + newClone.authId = context.userId + + 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 + // saveAuditLog(newClone.id, 'addKey', context) + logger.debug('addClone -> Save clone sucessful.') + resolve(newClone) + } + }) + }) + } catch(error) { + logger.debug('addClone -> Error: %j', error) + throw new OperationsError('There has been an error creating the clone.') + } +} + +const mutation = { + addClone: { + type: CloneType, + args, + resolve + } +} + +export default mutation diff --git a/server/src/schema/mutations/RemoveCloneMutation.js b/server/src/schema/mutations/RemoveCloneMutation.js new file mode 100644 index 000000000..a9e741f63 --- /dev/null +++ b/server/src/schema/mutations/RemoveCloneMutation.js @@ -0,0 +1,54 @@ +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 saveAuditLog from './AddAuditLog' + +const args = { + id: {type: new GraphQLNonNull(GraphQLID)} +} + +const resolve = async (parent, { id }, context) => { + logger.debug('removeClone -> Entering Fuction.') + + if (!context.userId) { + throw new AuthentificationError() + } + + await removeKuberneteClone(id) + + var query = { + _id: id, + authId: context.userId + } + + //saveAuditLog(id, 'removeClone', context) + logger.debug('removeClone -> Removing Clone from DB.') + Clone.deleteOne(query, function (err) { + if (err){ + throw new OperationsError(err) + }else{ + return 'Clone Removed' + } + }) +} + +const mutation = { + removeClone: { + type: GraphQLString, + args, + resolve + } +} + +export default mutation diff --git a/server/src/schema/mutations/index.js b/server/src/schema/mutations/index.js new file mode 100644 index 000000000..44bae56f0 --- /dev/null +++ b/server/src/schema/mutations/index.js @@ -0,0 +1,16 @@ +import { + GraphQLObjectType +} from 'graphql' + +import AddCloneMutation from './AddCloneMutation' +import RemoveCloneMutation from './RemoveCloneMutation' + +const Mutation = new GraphQLObjectType({ + name: 'Mutation', + fields: Object.assign( + AddCloneMutation, + RemoveCloneMutation + ) +}) + +export default Mutation diff --git a/server/src/schema/queries/AuditLogsQuery.js b/server/src/schema/queries/AuditLogsQuery.js new file mode 100644 index 000000000..2ff8cfd85 --- /dev/null +++ b/server/src/schema/queries/AuditLogsQuery.js @@ -0,0 +1,40 @@ +import { + GraphQLList, + GraphQLString, + GraphQLNonNull +} from 'graphql' + +import { + AuthentificationError +} from '../../errors' + +import { AuditLogType } from '../types' +import { AuditLog } from '../../models' +import logger from '../../config/logger' + +const args = { + key: { type: new GraphQLNonNull(GraphQLString) } +} + +const resolve = (parent, { key }, context) => { + logger.debug('auditLogs -> Entering Fuction.') + + if (!context.userId) { + throw new AuthentificationError() + } + + return AuditLog.find({ + authId: context.userId, + keyId: key + }).sort({date: -1}).limit(20) +} + +const query = { + auditLogs: { + type: new GraphQLList(AuditLogType), + args, + resolve + } +} + +export default query diff --git a/server/src/schema/queries/ExchangesQuery.js b/server/src/schema/queries/ExchangesQuery.js new file mode 100644 index 000000000..dc7e37355 --- /dev/null +++ b/server/src/schema/queries/ExchangesQuery.js @@ -0,0 +1,23 @@ +import { + GraphQLList +} from 'graphql' +import { ExchangeType } from '../types' +import { Exchange } from '../../models' +import logger from '../../config/logger' + +const args = {} + +const resolve = (parent, args, context) => { + logger.debug('exchanges -> Entering Fuction.') + return Exchange +} + +const query = { + exchanges: { + type: new GraphQLList(ExchangeType), + args, + resolve + } +} + +export default query diff --git a/server/src/schema/queries/ListClones.js b/server/src/schema/queries/ListClones.js new file mode 100644 index 000000000..398b4f85d --- /dev/null +++ b/server/src/schema/queries/ListClones.js @@ -0,0 +1,41 @@ +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' + +const args = {} + +const resolve = async(parent, args, context) => { + logger.debug('List Clones -> Entering Fuction.') + + if (!context.userId) { + throw new AuthentificationError() + } + + let clones = await Clone.find({authId: context.userId}) + + for (var i = 0; i < clones.length; i++) { + clones[i].state = await getKuberneteClonePodStatus(clones[i]._id) + clones[i].lastLogs = await getKuberneteClonePodLogs(clones[i]._id) + } + return clones +} + +const query = { + clones: { + type: new GraphQLList(CloneType), + args, + resolve + } +} + +export default query diff --git a/server/src/schema/queries/index.js b/server/src/schema/queries/index.js new file mode 100644 index 000000000..dac2c4145 --- /dev/null +++ b/server/src/schema/queries/index.js @@ -0,0 +1,14 @@ +import { + GraphQLObjectType +} from 'graphql' + +import ListClones from './ListClones' + +const RootQuery = new GraphQLObjectType({ + name: 'RootQueryType', + fields: Object.assign( + ListClones + ) +}) + +export default RootQuery diff --git a/server/src/schema/types/AuditLogType.js b/server/src/schema/types/AuditLogType.js new file mode 100644 index 000000000..646ffd307 --- /dev/null +++ b/server/src/schema/types/AuditLogType.js @@ -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 diff --git a/server/src/schema/types/CloneType.js b/server/src/schema/types/CloneType.js new file mode 100644 index 000000000..44082e9be --- /dev/null +++ b/server/src/schema/types/CloneType.js @@ -0,0 +1,28 @@ +import { + GraphQLObjectType, + GraphQLString, + GraphQLInt, + GraphQLBoolean, + GraphQLID +} 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} + }) +}) +export default CloneType diff --git a/server/src/schema/types/ExchangeType.js b/server/src/schema/types/ExchangeType.js new file mode 100644 index 000000000..ddafd5a16 --- /dev/null +++ b/server/src/schema/types/ExchangeType.js @@ -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 diff --git a/server/src/schema/types/SignedTransactionType.js b/server/src/schema/types/SignedTransactionType.js new file mode 100644 index 000000000..ddee4a918 --- /dev/null +++ b/server/src/schema/types/SignedTransactionType.js @@ -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 diff --git a/server/src/schema/types/index.js b/server/src/schema/types/index.js new file mode 100644 index 000000000..4c2b8e040 --- /dev/null +++ b/server/src/schema/types/index.js @@ -0,0 +1,5 @@ +import CloneType from './CloneType' + +export { + CloneType +} diff --git a/server/src/schema/types/input/Clone.js b/server/src/schema/types/input/Clone.js new file mode 100644 index 000000000..62f626625 --- /dev/null +++ b/server/src/schema/types/input/Clone.js @@ -0,0 +1,24 @@ +import { + GraphQLString, + GraphQLInt, + GraphQLInputObjectType, + GraphQLBoolean, + GraphQLNonNull +} from 'graphql'; + +const Type = new GraphQLInputObjectType({ + name: 'CloneInput', + description: 'Payload for clone input', + fields: () => ({ + teamId: { type: new GraphQLNonNull(GraphQLString) }, + botId: { type: new GraphQLNonNull(GraphQLString) }, + mode: { type: GraphQLString }, + resumeExecution: { type: GraphQLBoolean }, + beginDatetime: { type: GraphQLInt }, + endDatetime: { type: GraphQLInt }, + waitTime: { type: GraphQLInt }, + runAsTeam: { type: GraphQLBoolean } + }) +}); + +export default Type; diff --git a/server/src/schema/types/input/index.js b/server/src/schema/types/input/index.js new file mode 100644 index 000000000..13c71b313 --- /dev/null +++ b/server/src/schema/types/input/index.js @@ -0,0 +1,5 @@ +import CloneInputType from './Clone'; + +export { + CloneInputType +};