Merge pull request #30 from AAVikings/master

Adding integration with key vault
This commit is contained in:
benme authored and GitHub committed 2019-02-14 19:32:41 -03:00
commit c8f359cf67
46 files changed
+482 -429

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
.scannerwork
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@superalgos/operations-client",
"version": "0.0.2",
"version": "0.0.14",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@superalgos/operations-client",
"version": "0.0.13",
"version": "0.0.14",
"description": "Superalgos Operations client-side web app",
"main": "src/index.js",
"dependencies": {
+7 -7
View File
@@ -1,5 +1,5 @@
import gql from 'graphql-tag';
import { clone } from '../Fragments';
import gql from 'graphql-tag'
import { clone } from '../Fragments'
const OPERATIONS_ADD_CLONE = gql`
mutation Operations_AddClone($clone: operations_CloneInput!) {
@@ -8,7 +8,7 @@ const OPERATIONS_ADD_CLONE = gql`
}
}
${clone}
`;
`
const OPERATIONS_LIST_CLONES = gql`
query Operations_Clones{
@@ -17,7 +17,7 @@ const OPERATIONS_LIST_CLONES = gql`
}
}
${clone}
`;
`
const OPERATIONS_HISTORY_CLONES = gql`
query Operations_HistoryClones{
@@ -26,17 +26,17 @@ const OPERATIONS_HISTORY_CLONES = gql`
}
}
${clone}
`;
`
const OPERATIONS_REMOVE_CLONE = gql`
mutation Operations_RemoveClone($id: ID!){
operations_RemoveClone( id: $id)
}
`;
`
export default {
OPERATIONS_LIST_CLONES,
OPERATIONS_HISTORY_CLONES,
OPERATIONS_ADD_CLONE,
OPERATIONS_REMOVE_CLONE
};
}
+6 -4
View File
@@ -1,7 +1,9 @@
import clones from './clones';
import teams from './teams';
import clones from './clones'
import teams from './teams'
import keys from './keys'
export {
clones,
teams
};
teams,
keys
}
+15
View File
@@ -0,0 +1,15 @@
import gql from 'graphql-tag'
export const GET_ALL_KEYS = gql`
query {
keyVault_AvailableKeys {
id
key
description
}
}
`
export default {
GET_ALL_KEYS
}
+2 -2
View File
@@ -1,4 +1,4 @@
import gql from 'graphql-tag';
import gql from 'graphql-tag'
export const GET_ALL_TEAMS_QUERY = gql`
query {
@@ -18,4 +18,4 @@ export const GET_ALL_TEAMS_QUERY = gql`
export default {
GET_ALL_TEAMS_QUERY
};
}
+3 -2
View File
@@ -1,4 +1,4 @@
import gql from 'graphql-tag';
import gql from 'graphql-tag'
export const clone = gql`
fragment clone on operations_Clone {
@@ -30,5 +30,6 @@ export const clone = gql`
botAvatar
teamAvatar
processName
keyId
}
`;
`
+1 -2
View File
@@ -1,6 +1,5 @@
import { clone } from './clone'
export {
clone
};
}
-1
View File
@@ -1 +0,0 @@
+8 -11
View File
@@ -1,16 +1,15 @@
const availableMonths = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"]
const availableMonths = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December']
const tradingStartModes ={
live: "Live",
competition: "Competition",
backtest: "Backtest"
const tradingStartModes = {
live: 'Live',
backtest: 'Backtest'
}
const indicatorStartModes = {
allMonths: "All Months",
oneMonth: "One Month",
noTime: "No Time"
allMonths: 'All Months',
oneMonth: 'One Month',
noTime: 'No Time'
}
const exchanges = [
@@ -20,6 +19,4 @@ const exchanges = [
}
]
export { tradingStartModes, exchanges, indicatorStartModes, availableMonths }
+5 -7
View File
@@ -1,9 +1,7 @@
import React, { Component } from 'react'
import React from 'react'
import { Route, Switch } from 'react-router-dom'
import { MuiPickersUtilsProvider } from 'material-ui-pickers'
import LuxonUtils from 'material-ui-pickers/utils/luxon-utils'
import BrowseClones from './views/Clone/List'
import HistoryClones from './views/Clone/History'
import AddClone from './views/Clone/Add'
@@ -12,10 +10,10 @@ import Home from './views/Home'
const App = () => (
<MuiPickersUtilsProvider utils={LuxonUtils}>
<Switch>
<Route exact path='/operations/' component={Home} />
<Route path='/operations/browse' component={BrowseClones} />
<Route path='/operations/history' component={HistoryClones} />
<Route path='/operations/add' component={AddClone} />
<Route exact path='/clones/' component={Home} />
<Route path='/clones/browse' component={BrowseClones} />
<Route path='/clones/history' component={HistoryClones} />
<Route path='/clones/add' component={AddClone} />
</Switch>
</MuiPickersUtilsProvider>
)
+3 -3
View File
@@ -10,13 +10,13 @@ export function toLocalTime (epoch) {
}
export function toUTCTimestamp (localDate) {
if(isDefined(localDate)){
if (isDefined(localDate)) {
const date = new Date(localDate.valueOf())
const utcDate = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(),
date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(),
date.getUTCSeconds())
return utcDate.valueOf() / 1000|0
return utcDate.valueOf() / 1000 | 0
}
}
@@ -77,7 +77,7 @@ export function getIndicatorYears () {
const localDate = new Date()
let availableYears = []
for (var i = 2015; i <= localDate.getUTCFullYear(); i++) {
availableYears[i-2015] = i
availableYears[i - 2015] = i
}
return availableYears
}
+73 -23
View File
@@ -1,30 +1,20 @@
import React, { Component } from 'react'
import { graphql, compose, Query } from 'react-apollo'
import { clones, teams } from '../../../GraphQL/Calls'
import { clones, teams, keys } from '../../../GraphQL/Calls'
import { DateTime } from 'luxon';
import TopBar from '../../BannerTopBar'
import { withStyles } from '@material-ui/core/styles'
import classNames from 'classnames'
import styles from './styles'
import { tradingStartModes, indicatorStartModes, availableMonths, processNames } from '../../../GraphQL/models'
import { tradingStartModes, indicatorStartModes, availableMonths } from '../../../GraphQL/models'
import { isDefined, getIndicatorYears } from '../../../utils'
import {
MenuItem, Button, TextField, FormControl, InputLabel, Input, Typography,
Paper, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions,
Select, FormControlLabel, Checkbox, FormHelperText
} from '@material-ui/core'
import NumberFormat from 'react-number-format'
import { DateTimePicker } from 'material-ui-pickers'
import {
ChevronLeft,
ChevronRight,
} from '@material-ui/icons'
import { ChevronLeft, ChevronRight } from '@material-ui/icons'
class AddClone extends Component {
@@ -45,6 +35,7 @@ class AddClone extends Component {
runAsTeam: false,
teams: [],
teamId: '',
keyId: '',
// Indicator Bot
startYear: 2019,
@@ -60,7 +51,8 @@ class AddClone extends Component {
isNewCloneConfirmationOpen: false,
serverResponse: '',
serverError: false,
processNameError: false
processNameError: false,
keyIdError: false
}
}
@@ -230,6 +222,60 @@ class AddClone extends Component {
</React.Fragment>
}
{ this.state.mode == "live" &&
<React.Fragment>
<Typography className={classes.typography} variant='subtitle1' align='justify'>
Select one of your available Exchange API Keys to be used by this clone.
</Typography>
<Query
query={keys.GET_ALL_KEYS}
>
{({ loading, error, data }) => {
if (loading) return (
<TextField
select
label="Exchange API Key"
className={classNames(classes.margin, classes.textField, classes.form)}
value={this.state.keyId}
onChange={(e)=> this.setState({keyId:e.target.value})}
onBlur={(e)=>this.setState({keyIdError:false})}
error={this.state.keyIdError}
fullWidth
>
<MenuItem key='1' value='Loading keys...'>
'Loading keys...'
</MenuItem>
</TextField>
);
if (error) return `Error! ${error.message}`;
const list = data.keyVault_AvailableKeys.map((key) => (
<MenuItem key={key.id} value={key.id}>
{key.key}
</MenuItem>
));
return (
<TextField
select
label="Exchange API Key"
className={classNames(classes.margin, classes.textField, classes.form)}
value={this.state.keyId}
onChange={(e)=> this.setState({keyId:e.target.value})}
onBlur={(e)=>this.setState({keyIdError:false})}
error={this.state.keyIdError}
fullWidth
>
{ list }
</TextField>
);
}}
</Query>
</React.Fragment>
}
<Typography className={classes.typography} variant='subtitle1' align='justify'>
If Run as Team is selected, the clone will be taken from the
team respository instead of your own team member folder.
@@ -399,7 +445,8 @@ class AddClone extends Component {
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleNewCloneConfirmationClose} color="primary" autoFocus>
<Button onClick={this.handleNewCloneConfirmationClose}
variant='contained' color='secondary' autoFocus>
Ok
</Button>
</DialogActions>
@@ -431,7 +478,7 @@ class AddClone extends Component {
let serverResponse = await this.createCloneOnServer()
error = serverResponse.errors || !isDefined(serverResponse.data.operations_AddClone)
if(error){
this.state.serverResponse = serverResponse.errors[0].message
this.state.serverResponse = serverResponse.errors[0].message || serverResponse.errors[0]
this.state.serverError = true
}else{
this.state.serverResponse = "The new clone was sucessfully created."
@@ -453,15 +500,16 @@ class AddClone extends Component {
}
}
if(this.state.mode === "backtest"){
variables.clone.beginDatetime = this.state.beginDatetime.valueOf() / 1000|0
variables.clone.endDatetime = this.state.endDatetime.valueOf() / 1000|0
variables.clone.waitTime = this.state.waitTime
}
if(this.state.selectedBot.kind === "TRADER"){
variables.clone.runAsTeam = this.state.runAsTeam
variables.clone.processName = 'Trading-Process'
if(this.state.mode === "backtest"){
variables.clone.beginDatetime = this.state.beginDatetime.valueOf() / 1000|0
variables.clone.endDatetime = this.state.endDatetime.valueOf() / 1000|0
variables.clone.waitTime = this.state.waitTime
} else {
variables.clone.keyId = this.state.keyId
}
} else {
variables.clone.processName = this.state.processName
variables.clone.startYear = this.state.startYear
@@ -508,6 +556,7 @@ class AddClone extends Component {
processName: '',
teams: [],
teamId: '',
keyId: '',
//Error handlers
nameError: false,
@@ -517,7 +566,8 @@ class AddClone extends Component {
isNewCloneConfirmationOpen: false,
serverResponse: '',
serverError: false,
processNameError: false
processNameError: false,
keyIdError: false
})
};
+5 -5
View File
@@ -14,7 +14,7 @@ const styles = theme => ({
},
textField: {
width: '80%',
marginLeft:'10%',
marginLeft: '10%',
marginBottom: 10
},
actionButton: {
@@ -30,8 +30,8 @@ const styles = theme => ({
marginTop: 20
},
selectEmpty: {
marginTop: theme.spacing.unit * 2,
},
});
marginTop: theme.spacing.unit * 2
}
})
export default styles;
export default styles
+1 -5
View File
@@ -2,12 +2,8 @@ import React, { Component } from 'react'
import { graphql } from 'react-apollo'
import { clones } from '../../../GraphQL/Calls'
import TopBar from '../../BannerTopBar'
//import CloneDialog from './CloneDialog'
import ListClones from '../List/ListClones'
// Material-ui
import { Typography, Paper } from '@material-ui/core'
class HistoryClones extends Component {
constructor (props) {
@@ -44,7 +40,7 @@ class HistoryClones extends Component {
{
data.operations_HistoryClones.map((clone, i) => {
return (
<ListClones key={clone.id} currentClone={clone} isHistory={true}/>
<ListClones key={clone.id} currentClone={clone} isHistory />
)
})
}
+1 -4
View File
@@ -1,12 +1,9 @@
import React, { Component } from 'react'
import AddClone from './AddClone'
import AddIcon from '@material-ui/icons/Add'
import { withStyles } from '@material-ui/core/styles'
import styles from './styles'
import {
Button, Dialog, DialogContent, DialogContentText, DialogTitle, Paper, Typography
Button, Dialog, DialogContent, DialogContentText, DialogTitle
} from '@material-ui/core'
class CloneDialog extends Component {
+2 -6
View File
@@ -1,19 +1,15 @@
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,
Typography, Button, TextField, Dialog, DialogContent,
DialogContentText, DialogTitle, DialogActions,
ExpansionPanel, ExpansionPanelDetails, ExpansionPanelSummary,
ExpansionPanelActions, Chip, Divider
ExpansionPanelActions, Divider
} from '@material-ui/core'
import Avatar from '@material-ui/core/Avatar';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { tradingStartModes, indicatorStartModes } from '../../../GraphQL/models'
import { isDefined, toLocalTime } from '../../../utils'
import classNames from 'classnames'
-4
View File
@@ -2,12 +2,8 @@ import React, { Component } from 'react'
import { graphql } from 'react-apollo'
import { clones } from '../../../GraphQL/Calls'
import TopBar from '../../BannerTopBar'
//import CloneDialog from './CloneDialog'
import ListClones from './ListClones'
// Material-ui
import { Typography, Paper } from '@material-ui/core'
class BrowseClones extends Component {
constructor (props) {
+19 -20
View File
@@ -17,27 +17,26 @@ const styles = theme => ({
},
buttonList: {
margin: theme.spacing.unit,
float: 'right',
float: 'right'
},
buttonGrid: {
marginTop: -20,
marginTop: -20
},
button: {
display: 'block',
marginLeft: 'auto',
marginRight: 'auto',
marginRight: 'auto'
},
textField: {
marginLeft: theme.spacing.unit,
marginRight: theme.spacing.unit,
marginRight: theme.spacing.unit
},
textArea: {
width:'1750px',
margin: theme.spacing.unit,
whiteSpace: 'pre',
width:'98%',
overflowY:'hidden',
overflowX:'hidden'
width: '98%',
overflowY: 'hidden',
overflowX: 'hidden'
},
heading: {
fontSize: theme.typography.pxToRem(18),
@@ -50,15 +49,15 @@ const styles = theme => ({
},
helper: {
borderLeft: `2px solid ${theme.palette.divider}`,
padding: `0px ${theme.spacing.unit * 2}px`,
padding: `0px ${theme.spacing.unit * 2}px`
},
logsWrapper:{
width:'1750px',
overflowY:'auto',
overflowX:'hidden'
logsWrapper: {
width: '1750px',
overflowY: 'auto',
overflowX: 'hidden'
},
details:{
display:'flex'
details: {
display: 'flex'
},
column2: {
flexBasis: '50%',
@@ -68,23 +67,23 @@ const styles = theme => ({
flexBasis: '33.33%',
maxWidth: '33.33%'
},
cloneInfoTitle:{
cloneInfoTitle: {
fontWeight: '600',
flexBasis: '100%',
maxWidth: '100%'
},
cloneInfoBold:{
cloneInfoBold: {
fontWeight: '600',
paddingLeft: '10px',
paddingTop: '10px'
},
cloneInfoNormal:{
cloneInfoNormal: {
paddingLeft: '10px',
paddingTop: '10px'
},
avatar: {
margin: 10,
},
margin: 10
}
})
export default styles
+1 -4
View File
@@ -1,6 +1,5 @@
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 {
@@ -29,9 +28,7 @@ class Home extends Component {
</Typography>
</div>
<div className='column'>
<Typography align='justify'>
</Typography>
<Typography align='justify' />
</div>
</div>
+29
View File
@@ -2,3 +2,32 @@
## AA Operations Server
Server side functions support the business rules for the module.
## Available Methods
# Active clones List
- List active personal clones
- List active team clones (future release)
# History clone List
- List history personal clones
- List history team clones (future release)
# Get clone by id
- Get existing clones corresponding to the clone ids passed by parameter and returns:
- Clone id
- Team id
- Bot id
# Add new clones:
Receive a list of clones, create each of them and return the newly created clone.
A clone can be created:
- As a personal clone (run from the personal user folder)
- As a team clone (on competitions, run from the team folder)
Who can create each type:
- Personal clones are managed by the user who creates it, not visible for any other user.
- Team clones are managed by any ADMIN type team member (on the team member role attribute),
and all team admins will see it listed.
- Events Module:
At enroll time, the clone is created and the authId is sent
-1
View File
@@ -6,7 +6,6 @@ export const ONE_MONTH = 'oneMonth'
export const NO_TIME = 'noTime'
export const FIXED_INTERVAL = 'fixedInterval'
export const CloneModeEnum = [
LIVE,
BACKTEST,
-3
View File
@@ -1,5 +1,3 @@
import logger from '../config/logger'
export class AuthentificationError extends Error {
code = 401
message = 'Autentification not found, you have to be authentificated to perform this action.'
@@ -53,6 +51,5 @@ export class KubernateError extends Error {
message = 'OperationsModule Kubernate Error. ' + this.message
constructor(message){
super(message)
logger.error(this.stack)
}
}
+28
View File
@@ -0,0 +1,28 @@
import logger from '../config/logger'
import axios from 'axios'
const authorizeClon = (authorization, keyId, cloneId, releaseClon) => {
logger.debug('authorizeClon -> Entering function.')
return axios({
url: process.env.GATEWAY_ENDPOINT,
method: 'post',
data: {
query: `
mutation keyVault_AuthorizeClone($keyId: String!, $cloneId: String!, $releaseClon: Boolean!){
keyVault_AuthorizeClone(keyId: $keyId, cloneId: $cloneId, releaseClon:$releaseClon)
}
`,
variables: {
keyId: keyId,
cloneId: cloneId,
releaseClon: releaseClon
}
},
headers: {
authorization: authorization
}
})
}
export default authorizeClon
+5 -5
View File
@@ -1,8 +1,8 @@
import logger from '../config/logger'
import axios from 'axios'
const teamQuery = async(authorization, teamId) => {
logger.debug('GraphQL retrieving team information. %s', teamId)
const teamQuery = (authorization, teamId) => {
logger.debug('teamQuery -> Entering function')
return axios({
url: process.env.GATEWAY_ENDPOINT,
@@ -34,13 +34,13 @@ const teamQuery = async(authorization, teamId) => {
}
`,
variables: {
teamId: teamId
}
teamId: teamId
}
},
headers: {
authorization: authorization
}
})
};
}
export default teamQuery
+4 -4
View File
@@ -5,17 +5,17 @@ import schema from './schema'
import mongoose from 'mongoose'
import cors from 'cors'
import logger from './config/logger'
import wrongPreshared from './errors/notAllowed.json';
import wrongPreshared from './errors/notAllowed.json'
const app = express()
app.post('/graphql', (req, res, next) => {
if (req.headers.preshared === process.env.OPERATIONS_API_PRESHARED) {
next();
next()
} else {
res.send(wrongPreshared);
res.send(wrongPreshared)
}
});
})
// Allow cross origing request
app.use(cors())
+73 -71
View File
@@ -2,138 +2,140 @@ import logger from '../config/logger'
import { toPlatformDatetime, isDefined } from '../config/utils'
import { KubernateError } from '../errors'
import { Client, config } from 'kubernetes-client'
// Get base Deployment config
import deploymentManifest from '../config/clone-deployment.json'
import { BACKTEST, NO_TIME } from '../enums/CloneMode'
import { TRADER, INDICATOR, EXTRACTION } from '../enums/BotTypes'
const createClone = async (clone) => {
try {
logger.debug("createClone %s", clone.id)
logger.debug('createClone %s', clone.id)
const client = new Client({config: config.fromKubeconfig(), version: '1.9'})
// Make changes to base deployment config
deploymentManifest.metadata.name = clone.id
logger.debug("createClone Environment and Auth Configuration.")
logger.debug('createClone Environment and Auth Configuration.')
let env = []
env.push({
"name": "PLATFORM_ENVIRONMENT",
"value": process.env.PLATFORM_ENVIRONMENT
'name': 'PLATFORM_ENVIRONMENT',
'value': process.env.PLATFORM_ENVIRONMENT
})
env.push({
"name": "GATEWAY_ENDPOINT",
"value": process.env.GATEWAY_ENDPOINT
'name': 'GATEWAY_ENDPOINT',
'value': process.env.GATEWAY_ENDPOINT
})
env.push({
"name": "STORAGE_BASE_URL",
"value": process.env.STORAGE_BASE_URL
'name': 'STORAGE_BASE_URL',
'value': process.env.STORAGE_BASE_URL
})
env.push({
"name": "STORAGE_CONNECTION_STRING",
"value": process.env.STORAGE_CONNECTION_STRING
'name': 'STORAGE_CONNECTION_STRING',
'value': process.env.STORAGE_CONNECTION_STRING
})
env.push({
"name": "AUTH_URL",
"value": process.env.AUTH_URL
'name': 'AUTH_URL',
'value': process.env.AUTH_URL
})
env.push({
"name": "AUTH_CLIENT_ID",
"value": process.env.AUTH_CLIENT_ID
'name': 'AUTH_CLIENT_ID',
'value': process.env.AUTH_CLIENT_ID
})
env.push({
"name": "AUTH_CLIENT_SECRET",
"value": process.env.AUTH_CLIENT_SECRET
'name': 'AUTH_CLIENT_SECRET',
'value': process.env.AUTH_CLIENT_SECRET
})
env.push({
"name": "AUTH_AUDIENCE",
"value": process.env.AUTH_AUDIENCE
'name': 'AUTH_AUDIENCE',
'value': process.env.AUTH_AUDIENCE
})
logger.debug("createClone General Financial Being Configuration.")
logger.debug('createClone General Financial Being Configuration.')
env.push({
"name": "DEV_TEAM",
"value": clone.teamSlug
'name': 'DEV_TEAM',
'value': clone.teamSlug
})
env.push({
"name": "BOT",
"value": clone.botSlug
'name': 'BOT',
'value': clone.botSlug
})
env.push({
"name": "TYPE",
"value": clone.botType
'name': 'TYPE',
'value': clone.botType
})
env.push({
"name": "CLONE_ID",
"value": clone.id.toString()
'name': 'CLONE_ID',
'value': clone.id.toString()
})
env.push({
"name": "START_MODE",
"value": clone.mode
'name': 'START_MODE',
'value': clone.mode
})
env.push({
"name": "RUN_AS_TEAM",
"value": clone.runAsTeam.toString()
'name': 'RUN_AS_TEAM',
'value': clone.runAsTeam.toString()
})
env.push({
"name": "USER_LOGGED_IN",
"value": clone.userLoggedIn
'name': 'USER_LOGGED_IN',
'value': clone.userLoggedIn
})
env.push({
"name": "PROCESS",
"value": clone.processName
'name': 'PROCESS',
'value': clone.processName
})
logger.debug("createClone Trading Configuration.")
if(clone.botType === TRADER ){
logger.debug('createClone Trading Configuration.')
if (clone.botType === TRADER) {
env.push({
"name": "RESUME_EXECUTION",
"value": clone.resumeExecution.toString()
'name': 'RESUME_EXECUTION',
'value': clone.resumeExecution.toString()
})
if(clone.mode === BACKTEST){
if (clone.mode === BACKTEST) {
env.push({
"name": "BEGIN_DATE_TIME",
"value": toPlatformDatetime(clone.beginDatetime)
'name': 'BEGIN_DATE_TIME',
'value': toPlatformDatetime(clone.beginDatetime)
})
env.push({
"name": "END_DATE_TIME",
"value": toPlatformDatetime(clone.endDatetime)
'name': 'END_DATE_TIME',
'value': toPlatformDatetime(clone.endDatetime)
})
env.push({
"name": "WAIT_TIME",
"value": clone.waitTime.toString()
'name': 'WAIT_TIME',
'value': clone.waitTime.toString()
})
} else {
env.push({
'name': 'KEY_ID',
'value': clone.keyId
})
}
} else if (clone.botType === INDICATOR || clone.botType === EXTRACTION) {
if (clone.mode !== NO_TIME) {
env.push({
'name': 'MIN_YEAR',
'value': isDefined(clone.startYear) ? clone.startYear.toString() : ''
})
env.push({
'name': 'MAX_YEAR',
'value': isDefined(clone.endYear) ? clone.endYear.toString() : ''
})
env.push({
'name': 'MONTH',
'value': isDefined(clone.month) ? clone.month.toString() : ''
})
env.push({
'name': 'INTERVAL',
'value': isDefined(clone.interval) ? clone.interval.toString() : ''
})
}
} else if(clone.botType === INDICATOR || clone.botType === EXTRACTION ){
if(clone.mode !== NO_TIME){
env.push({
"name": "MIN_YEAR",
"value": isDefined(clone.startYear) ? clone.startYear.toString() : ''
})
env.push({
"name": "MAX_YEAR",
"value": isDefined(clone.endYear) ? clone.endYear.toString() : ''
})
env.push({
"name": "MONTH",
"value": isDefined(clone.month) ? clone.month.toString() : ''
})
env.push({
"name": "INTERVAL",
"value": isDefined(clone.interval) ? clone.interval.toString() : ''
})
}
}
deploymentManifest.spec.template.spec.containers[0].env = env
const create = await client.apis.batch.v1.namespaces('default').jobs.post(
await client.apis.batch.v1.namespaces('default').jobs.post(
{ body: deploymentManifest })
logger.debug("createClone %s on Kubernates success.", clone.id)
logger.debug('createClone %s on Kubernates success.', clone.id)
} catch (err) {
throw new KubernateError(err)
throw new KubernateError(err)
}
}
export default createClone
+7 -7
View File
@@ -4,31 +4,31 @@ import { Client, config } from 'kubernetes-client'
const getClonePodLogs = async (cloneName) => {
try {
logger.debug("getClonePodLogs on kubernates started.")
logger.debug('getClonePodLogs on kubernates started.')
const client = new Client({config: config.fromKubeconfig(), version: '1.9'})
let query = {
"qs":{
"labelSelector": "job-name="+cloneName
'qs': {
'labelSelector': 'job-name=' + cloneName
}
}
const pod = await client.api.v1.namespaces('default').pods.get(query)
let queryLogs = {
"qs":{
"tailLines": "20"
'qs': {
'tailLines': '20'
}
}
const logs = await client.api.v1.namespaces('default')
.pods(pod.body.items[0].metadata.name).log.get(queryLogs)
let logsArray = logs.body.split('\n')
logger.debug("getClonePodLogs on kubernates successful.")
logger.debug('getClonePodLogs on kubernates successful.')
return JSON.stringify(logsArray, null, 2)
} catch (err) {
throw new KubernateError(err)
throw new KubernateError(err)
}
}
export default getClonePodLogs
+5 -5
View File
@@ -4,12 +4,12 @@ import { Client, config } from 'kubernetes-client'
const getClonePodStatus = async (cloneName) => {
try {
logger.debug("getClonePodStatus on kubernates started. ")
logger.debug('getClonePodStatus on kubernates started. ')
const client = new Client({config: config.fromKubeconfig(), version: '1.9'})
let query = {
"qs":{
"labelSelector": "job-name="+cloneName
'qs': {
'labelSelector': 'job-name=' + cloneName
}
}
@@ -21,10 +21,10 @@ const getClonePodStatus = async (cloneName) => {
&& clonePodStatus.terminated.hasOwnProperty('containerID')) {
delete clonePodStatus.terminated.containerID
}
logger.debug("getClonePodStatus %s on kubernates successful", cloneName)
logger.debug('getClonePodStatus %s on kubernates successful', cloneName)
return JSON.stringify(clonePodStatus, null, 2)
} catch (err) {
throw new KubernateError(err)
throw new KubernateError(err)
}
}
export default getClonePodStatus
+7 -9
View File
@@ -4,25 +4,23 @@ import { Client, config } from 'kubernetes-client'
const removeClone = async (clone) => {
try {
logger.debug("removeClone %s", clone.id)
logger.debug('removeClone %s', clone.id)
const client = new Client({config: config.fromKubeconfig(), version: '1.9'})
let query = {
"qs":{
"labelSelector": "job-name=" + clone.id
'qs': {
'labelSelector': 'job-name=' + clone.id
}
}
const removed = await client.apis.batch.v1.namespaces('default').jobs(
clone.id).delete()
await client.apis.batch.v1.namespaces('default').jobs(clone.id).delete()
const pod = await client.api.v1.namespaces('default').pods.get(query)
const podRemoved = await client.api.v1.namespaces('default')
.pods(pod.body.items[0].metadata.name).delete()
await client.api.v1.namespaces('default').pods(pod.body.items[0].metadata.name).delete()
logger.debug("removeClone %s on kubernates succesful", clone.id)
logger.debug('removeClone %s on kubernates succesful', clone.id)
} catch (err) {
throw new KubernateError(err)
throw new KubernateError(err)
}
}
export default removeClone
+13 -12
View File
@@ -2,31 +2,32 @@ import mongoose, { Schema } from 'mongoose'
import {
UNPUBLISHED,
CloneStateEnum,
} from '../enums/CloneState';
CloneStateEnum
} from '../enums/CloneState'
import {
BACKTEST,
CloneModeEnum,
} from '../enums/CloneMode';
CloneModeEnum
} from '../enums/CloneMode'
const cloneSchema = new Schema({
authId: {
type: String,
required: true,
required: true
},
teamId: {
type: String,
required: true,
type: String,
required: true
},
botId: {
type: String,
required: true,
type: String,
required: true
},
keyId: String,
mode: {
type: String,
enum: CloneModeEnum,
default: BACKTEST,
default: BACKTEST
},
resumeExecution: Boolean,
beginDatetime: Number,
@@ -35,7 +36,7 @@ const cloneSchema = new Schema({
state: {
type: String,
enum: CloneStateEnum,
default: UNPUBLISHED,
default: UNPUBLISHED
},
stateDatetime: Number,
createDatetime: Number,
@@ -51,7 +52,7 @@ const cloneSchema = new Schema({
assetB: String,
processName: {
type: String,
required: true,
required: true
}
})
+15 -15
View File
@@ -2,11 +2,11 @@ import logger from '../config/logger'
import { isDefined } from '../config/utils'
import { AutorizationError } from '../errors'
const cloneDetails = async(authId, team, clone) => {
logger.debug('Retrieving clone details.')
const cloneDetails = (authId, team, clone) => {
logger.debug('cloneDetails -> Entering function.')
if(!(authId === (process.env.AUTH_CLIENT_ID + '@clients') || isAuthorized(team, authId))){
logger.debug('User %s is not authorized to manage team %s.', authId, team.slug )
if (!(authId === (process.env.AUTH_CLIENT_ID + '@clients') || isAuthorized(team, authId))) {
logger.debug('User %s is not authorized to manage team %s.', authId, team.slug)
throw new AutorizationError()
}
@@ -20,29 +20,29 @@ const cloneDetails = async(authId, team, clone) => {
clone.botName = selectedBot.name
clone.botAvatar = selectedBot.avatar
clone.teamAvatar = team.profile.avatar
clone.userLoggedIn = team.members[0].member.alias //TODO change by context info if possible
clone.userLoggedIn = team.members[0].member.alias // TODO change by context info if possible
logger.debug('Retrieving clone details complete.')
logger.debug('cloneDetails -> Retrieving clone details complete.')
return clone
};
}
function getSelectedBot(team, selectedBotId){
logger.debug('Getting selected bot.')
if(isDefined(team)){
function getSelectedBot (team, selectedBotId) {
logger.debug('cloneDetails -> Getting selected bot.')
if (isDefined(team)) {
for (var i = 0; i < team.fb.length; i++) {
if(team.fb[i].id === selectedBotId){
if (team.fb[i].id === selectedBotId) {
return team.fb[i]
}
}
}
}
function isAuthorized(team, authId){
logger.debug('Checking if the user is authorized by the teams module.')
function isAuthorized (team, authId) {
logger.debug('cloneDetails -> Checking if the user is authorized by the teams module.')
let authorized = false
if(isDefined(team)){
if (isDefined(team)) {
for (var i = 0; i < team.members.length; i++) {
if(team.members[i].member.authId === authId){
if (team.members[i].member.authId === authId) {
authorized = true
break
}
+1 -4
View File
@@ -1,7 +1,4 @@
import {
GraphQLSchema
} from 'graphql'
import { GraphQLSchema } from 'graphql'
import Mutation from './mutations'
import RootQuery from './queries'
+36 -31
View File
@@ -1,23 +1,24 @@
import { CloneInputType } from '../types/input';
import { CloneType } from '../types';
import { Clone } from '../../models';
import { CloneModeEnum } from '../../enums/CloneMode';
import { BotTypesEnum } from '../../enums/BotTypes';
import { CloneInputType } from '../types/input'
import { CloneType } from '../types'
import { Clone } from '../../models'
import { CloneModeEnum } from '../../enums/CloneMode'
import { BotTypesEnum } from '../../enums/BotTypes'
import teamQuery from '../../graphQLCalls/teamQuery'
import authorizeClone from '../../graphQLCalls/authorizeClone'
import cloneDetails from '../cloneDetails'
import {
AuthentificationError,
WrongArgumentsError,
OperationsError
OperationsError,
AutorizationError
} from '../../errors'
import logger from '../../config/logger'
import createKubernetesClone from '../../kubernetes/createClone'
import { isDefined } from '../../config/utils'
import { LIVE } from '../../enums/CloneMode'
const args = {
clone: {type: CloneInputType }
clone: { type: CloneInputType }
}
const resolve = async(parent, { clone }, context) => {
@@ -27,46 +28,50 @@ const resolve = async(parent, { clone }, context) => {
throw new AuthentificationError()
}
if(!Object.values(CloneModeEnum).includes(clone.mode)){
if (!Object.values(CloneModeEnum).includes(clone.mode)) {
throw new WrongArgumentsError('The mode selected is not valid.')
}
if(!Object.values(BotTypesEnum).includes(clone.botType)){
if (!Object.values(BotTypesEnum).includes(clone.botType)) {
throw new WrongArgumentsError('The bot type selected is not valid.')
}
try{
if (clone.mode === LIVE && !isDefined(clone.keyId)) {
throw new WrongArgumentsError('The key was not provided to run the clone in Live mode.')
}
try {
let team = await teamQuery(context.authorization, clone.teamId)
clone = await cloneDetails(context.userId, team.data.data.teams_TeamById, clone)
clone.createDatetime = new Date().valueOf() / 1000|0
clone.createDatetime = new Date().valueOf() / 1000 | 0
clone.active = true
logger.debug('addClone -> Creating a new clone.')
let newClone = new Clone(clone)
newClone.authId = context.userId
clone.id = newClone._id
await createKubernetesClone(clone)
return new Promise((resolve, reject) => {
newClone.save((err) => {
if (err){
logger.error('addClone -> Error: %s', err.stack)
reject('There has been an error storing the clone.')
}
else {
//TODO transaction on database
logger.debug('addClone -> Save clone sucessful.')
resolve(newClone)
}
})
})
} catch(err) {
logger.debug('addClone -> Creating the clone on the Database.')
let savedClone = await newClone.save()
if (clone.mode === LIVE) {
logger.debug('addClone -> Authorizing clone to use the key.')
let response = await authorizeClone(context.authorization, clone.keyId, clone.id, false)
logger.debug('addClone -> Authorizing clone to use the key.')
if (response.data.data.keyVault_AuthorizeClone === clone.keyId) {
await createKubernetesClone(clone)
} else {
throw new AutorizationError()
}
}
logger.debug('addClone -> Clone created sucessfully.')
return savedClone
} catch (err) {
logger.error('addClone -> Error: %s', err.stack)
throw new OperationsError('There has been an error creating the clone.')
}
}
const mutation = {
const AddCloneMutation = {
addClone: {
type: CloneType,
args,
@@ -74,4 +79,4 @@ const mutation = {
}
}
export default mutation
export default AddCloneMutation
@@ -1,14 +1,5 @@
import {
GraphQLNonNull,
GraphQLID,
GraphQLString
} from 'graphql'
import {
AuthentificationError,
OperationsError
} from '../../errors'
import { GraphQLNonNull, GraphQLID, GraphQLString } from 'graphql'
import { AuthentificationError, OperationsError } from '../../errors'
import { Clone } from '../../models'
import logger from '../../config/logger'
import removeKuberneteClone from '../../kubernetes/removeClone'
@@ -33,13 +24,16 @@ const resolve = async (parent, { id }, context) => {
active: true
})
if(!isDefined(clone)){
if (!isDefined(clone)) {
throw new OperationsError('You are not authorized to remove this clone.')
}
let team = await teamQuery(context.authorization, clone.teamId)
clone = await cloneDetails(context.userId, team.data.data.teams_TeamById, clone)
logger.debug('removeClone -> Release the clone key.')
await authorizeClone(context.authorization, clone.keyId, clone.id, true)
logger.debug('removeClone -> Removing Clone from Kubernates.')
await removeKuberneteClone(clone)
@@ -51,17 +45,17 @@ const resolve = async (parent, { id }, context) => {
const update = { active: false }
Clone.findOneAndUpdate(query, update, options, (err, doc) => {
if (err){
if (err) {
logger.error('removeClone -> Error removing clone from the DB. %s', err.stack)
throw new OperationsError(err)
}else{
} else {
logger.debug('removeClone -> Clone Removed from the DB.')
return('Clone Removed.')
return ('Clone Removed.')
}
})
}
const mutation = {
const RemoveCloneMutation = {
removeClone: {
type: GraphQLString,
args,
@@ -69,4 +63,4 @@ const mutation = {
}
}
export default mutation
export default RemoveCloneMutation
@@ -5,16 +5,11 @@ import {
GraphQLInt,
GraphQLFloat
} from 'graphql'
import {
OperationsError
} from '../../errors'
import { OperationsError } from '../../errors'
import { Clone } from '../../models'
import logger from '../../config/logger'
import teamQuery from '../../graphQLCalls/teamQuery'
import cloneDetails from '../cloneDetails'
import removeKuberneteClone from '../../kubernetes/removeClone'
import { isDefined } from '../../config/utils'
const args = {
@@ -33,31 +28,27 @@ const resolve = async (parent,
{ id, summaryDate, buyAverage, sellAverage, marketRate, combinedProfitsA,
combinedProfitsB, assetA, assetB
}, context) => {
logger.debug('UpdateExecutionSummary -> Entering Function.')
if (!context.userId) {
throw new AuthentificationError()
}
try{
try {
let sucessful
let clone = await Clone.findOne( {
let clone = await Clone.findOne({
_id: id,
active: true
})
if(!isDefined(clone)){
return "Clone not found."
if (!isDefined(clone)) {
return 'Clone not found.'
}
let team = await teamQuery(context.authorization, clone.teamId)
clone = await cloneDetails(context.userId, team.data.data.teams_TeamById, clone)
logger.debug('UpdateExecutionSummary -> Removing Clone from Kubernates.')
await removeKuberneteClone(clone)
const query = { _id: id }
const options = { new: true }
const update = {
@@ -74,7 +65,7 @@ const resolve = async (parent,
logger.debug('UpdateExecutionSummary -> Updating clone on database.')
await Clone.update(query, update, options, (err, clone) => {
if (err){
if (err) {
logger.error('Error updating execution summary on the database. %s', err.stack)
throw err
} else {
@@ -83,16 +74,16 @@ const resolve = async (parent,
}
})
if(sucessful)
if (sucessful) {
return 'Execution summary updated.'
}
} catch (error) {
logger.error('Error updating the execution summary. %s', error.stack)
throw new OperationsError(error.message)
}
}
const mutation = {
const UpdateExecutionSummary = {
updateExecutionSummary: {
type: GraphQLString,
args,
@@ -100,4 +91,4 @@ const mutation = {
}
}
export default mutation
export default UpdateExecutionSummary
+1 -4
View File
@@ -1,7 +1,4 @@
import {
GraphQLObjectType
} from 'graphql'
import { GraphQLObjectType } from 'graphql'
import AddCloneMutation from './AddCloneMutation'
import RemoveCloneMutation from './RemoveCloneMutation'
import UpdateExecutionSummary from './UpdateExecutionSummary'
+32 -41
View File
@@ -1,12 +1,5 @@
import {
GraphQLList,
GraphQLID
} from 'graphql'
import {
OperationsError
} from '../../errors'
import { GraphQLList, GraphQLID } from 'graphql'
import { OperationsError } from '../../errors'
import { GetClonesType } from '../types'
import { Clone } from '../../models'
import logger from '../../config/logger'
@@ -18,43 +11,41 @@ const args = {
}
const resolve = async(parent, { cloneIdList }, context) => {
logger.debug('Get Clones -> Entering Fuction. %j', cloneIdList)
try{
let clones = []
for (var i = 0; i < cloneIdList.length; i++) {
logger.debug('Get Clones -> Entering Fuction.')
try {
let clones = []
for (var i = 0; i < cloneIdList.length; i++) {
if (mongoose.Types.ObjectId.isValid(cloneIdList[i])) {
let existingClone = await Clone.findOne({
_id: cloneIdList[i]
})
if(mongoose.Types.ObjectId.isValid(cloneIdList[i])){
let existingClone = await Clone.findOne({
_id: cloneIdList[i]
})
if (isDefined(existingClone)) {
clones.push({
id: cloneIdList[i],
teamId: existingClone.teamId,
botId: existingClone.botId
})
} else {
clones.push({
id: cloneIdList[i]
})
}
} else {
clones.push({
id: cloneIdList[i]
})
}
}
if(isDefined(existingClone)){
clones.push({
id: cloneIdList[i],
teamId: existingClone.teamId,
botId: existingClone.botId
})
}else{
clones.push({
id: cloneIdList[i]
})
}
} else {
clones.push({
id: cloneIdList[i]
})
}
}
return clones
} catch (err){
return clones
} catch (err) {
logger.error('Get Clones error: %s', err.stack)
throw new OperationsError('There has been an error getting the clones.')
}
}
}
const query = {
const GetClones = {
getClones: {
type: new GraphQLList(GetClonesType),
args,
@@ -62,4 +53,4 @@ const query = {
}
}
export default query
export default GetClones
+19 -28
View File
@@ -1,17 +1,8 @@
import {
GraphQLList
} from 'graphql'
import {
AuthentificationError,
OperationsError
} from '../../errors'
import { GraphQLList } from 'graphql'
import { AuthentificationError, OperationsError } from '../../errors'
import { CloneType } from '../types'
import { Clone } from '../../models'
import logger from '../../config/logger'
import getKuberneteClonePodStatus from '../../kubernetes/getClonePodStatus'
import getKuberneteClonePodLogs from '../../kubernetes/getClonePodLogs'
import cloneDetails from '../cloneDetails'
import teamQuery from '../../graphQLCalls/teamQuery'
@@ -19,30 +10,30 @@ const args = {}
const resolve = async(parent, args, context) => {
logger.debug('Retrieving History Clones.')
try{
if (!context.userId) {
throw new AuthentificationError()
}
try {
if (!context.userId) {
throw new AuthentificationError()
}
let clones = await Clone.find({
authId: context.userId,
active: false
}).sort({createDatetime: -1})
let clones = await Clone.find({
authId: context.userId,
active: false
}).sort({createDatetime: -1})
for (var i = 0; i < clones.length; i++) {
for (var i = 0; i < clones.length; i++) {
let team = await teamQuery(context.authorization, clones[i].teamId)
clones[i] = await cloneDetails(context.userId, team.data.data.teams_TeamById, clones[i])
clones[i].botType = clones[i].kind // Only for listing we show the teams value
}
}
return clones
} catch (err){
logger.error('Retrieving History Clones error: %s', err.stack)
throw new OperationsError('There has been an error retrieving history clones.')
}
return clones
} catch (err) {
logger.error('Retrieving History Clones error: %s', err.stack)
throw new OperationsError('There has been an error retrieving history clones.')
}
}
const query = {
const HistoryClones = {
historyClones: {
type: new GraphQLList(CloneType),
args,
@@ -50,4 +41,4 @@ const query = {
}
}
export default query
export default HistoryClones
+19 -26
View File
@@ -1,12 +1,5 @@
import {
GraphQLList
} from 'graphql'
import {
AuthentificationError,
OperationsError
} from '../../errors'
import { GraphQLList } from 'graphql'
import { AuthentificationError, OperationsError } from '../../errors'
import { CloneType } from '../types'
import { Clone } from '../../models'
import logger from '../../config/logger'
@@ -19,33 +12,33 @@ const args = {}
const resolve = async(parent, args, context) => {
logger.debug('List Clones -> Entering Fuction.')
try{
if (!context.userId) {
throw new AuthentificationError()
}
try {
if (!context.userId) {
throw new AuthentificationError()
}
let clones = await Clone.find({
authId: context.userId,
active: true
}).sort({createDatetime: -1})
let clones = await Clone.find({
authId: context.userId,
active: true
}).sort({createDatetime: -1})
for (var i = 0; i < clones.length; i++) {
for (var i = 0; i < clones.length; i++) {
let team = await teamQuery(context.authorization, clones[i].teamId)
clones[i] = await cloneDetails(context.userId, team.data.data.teams_TeamById, clones[i])
let state = await getCloneStatus(clones[i].id)
clones[i].state = state.substring(1, state.length-1)
clones[i].state = state.substring(1, state.length - 1)
let lastLogs = await getCloneLogs(clones[i].id)
clones[i].lastLogs = lastLogs.substring(1, lastLogs.length-1)
clones[i].lastLogs = lastLogs.substring(1, lastLogs.length - 1)
clones[i].botType = clones[i].kind // Only for listing we show the teams value
}
return clones
} catch (err){
}
return clones
} catch (err) {
logger.error('List Clones error: %s', err.stack)
throw new OperationsError('There has been an error listing the clones.')
}
}
}
const query = {
const ListClones = {
clones: {
type: new GraphQLList(CloneType),
args,
@@ -53,4 +46,4 @@ const query = {
}
}
export default query
export default ListClones
+1 -4
View File
@@ -1,7 +1,4 @@
import {
GraphQLObjectType
} from 'graphql'
import { GraphQLObjectType } from 'graphql'
import ListClones from './ListClones'
import HistoryClones from './HistoryClones'
import GetClones from './GetClones'
+2 -1
View File
@@ -37,7 +37,8 @@ const CloneType = new GraphQLObjectType({
teamName: {type: GraphQLString},
botName: {type: GraphQLString},
botAvatar: {type: GraphQLString},
teamAvatar: {type: GraphQLString}
teamAvatar: {type: GraphQLString},
keyId: {type: GraphQLString}
})
})
export default CloneType
+1 -4
View File
@@ -1,7 +1,4 @@
import {
GraphQLObjectType,
GraphQLID
} from 'graphql'
import { GraphQLObjectType, GraphQLID } from 'graphql'
const GetClonesType = new GraphQLObjectType({
name: 'GetClones',
+6 -5
View File
@@ -4,7 +4,7 @@ import {
GraphQLInputObjectType,
GraphQLBoolean,
GraphQLNonNull
} from 'graphql';
} from 'graphql'
const Type = new GraphQLInputObjectType({
name: 'CloneInput',
@@ -23,8 +23,9 @@ const Type = new GraphQLInputObjectType({
month: { type: GraphQLInt },
interval: { type: GraphQLInt },
processName: { type: new GraphQLNonNull(GraphQLString) },
botType: { type: new GraphQLNonNull(GraphQLString) }
})
});
botType: { type: new GraphQLNonNull(GraphQLString) },
keyId: { type: GraphQLString }
})
})
export default Type;
export default Type
+1 -1
View File
@@ -2,4 +2,4 @@ import CloneInputType from './Clone'
export {
CloneInputType
};
}