diff --git a/client/package.json b/client/package.json
index 934363ed9..f010087e1 100644
--- a/client/package.json
+++ b/client/package.json
@@ -1,6 +1,6 @@
{
"name": "@superalgos/operations-client",
- "version": "0.0.3",
+ "version": "0.0.4",
"description": "Superalgos Operations client-side web app",
"main": "src/index.js",
"dependencies": {
diff --git a/client/src/GraphQL/Fragments/clone.js b/client/src/GraphQL/Fragments/clone.js
index 3cecd787d..26a5aab59 100644
--- a/client/src/GraphQL/Fragments/clone.js
+++ b/client/src/GraphQL/Fragments/clone.js
@@ -24,7 +24,7 @@ export const clone = gql`
combinedProfitsB
assetA
assetB
- kind
+ botType
teamName
botName
botAvatar
diff --git a/client/src/views/Clone/Add/index.js b/client/src/views/Clone/Add/index.js
index b58257b37..b24d35a0c 100644
--- a/client/src/views/Clone/Add/index.js
+++ b/client/src/views/Clone/Add/index.js
@@ -435,7 +435,8 @@ class AddClone extends Component {
botId: this.state.selectedBot.id,
mode: this.state.mode,
resumeExecution: this.state.resumeExecution,
- runAsTeam: this.state.runAsTeam
+ runAsTeam: this.state.runAsTeam,
+ botType: this.getBotTypeFromKind(this.state.selectedBot.kind)
}
}
@@ -457,6 +458,15 @@ class AddClone extends Component {
})
}
+ getBotTypeFromKind(kind){
+ if(kind === 'TRADER')
+ return 'Trading'
+ else if(kind === 'INDICATOR')
+ return 'Indicator'
+ else if(kind === 'EXTRACTOR')
+ return 'Extraction'
+ }
+
handleNewCloneConfirmationOpen = () => {
this.setState({ isNewCloneConfirmationOpen: true })
};
diff --git a/client/src/views/Clone/List/ListClones.js b/client/src/views/Clone/List/ListClones.js
index 433890125..67535515a 100644
--- a/client/src/views/Clone/List/ListClones.js
+++ b/client/src/views/Clone/List/ListClones.js
@@ -88,7 +88,7 @@ class ListClones extends Component {
Last Execution:
- { (clone.kind === 'TRADER' && isDefined(clone.assetA) ) &&
+ { (clone.botType === 'Trading' && isDefined(clone.assetA) ) &&
Buy Average:
Sell Average:
@@ -100,7 +100,7 @@ class ListClones extends Component {
{isDefined(clone.summaryDate) ? toLocalTime(clone.summaryDate): 'waiting update...'}
- { ((clone.kind === 'INDICATOR' || clone.kind === 'EXTRACTOR') && isDefined(clone.assetA) ) &&
+ { ((clone.botType === 'Indicator' || clone.botType === 'Extraction') && isDefined(clone.assetA) ) &&
{clone.buyAverage}
{clone.sellAverage}
@@ -187,7 +187,7 @@ class ListClones extends Component {
Last clone logs
-
+
Here you can check the clone output directly from the virtual machine.
The last 20 lines are displayed, but you can check the Logs Module for more details.
diff --git a/client/src/views/Clone/List/styles.js b/client/src/views/Clone/List/styles.js
index 418db1c3e..f2c33d236 100644
--- a/client/src/views/Clone/List/styles.js
+++ b/client/src/views/Clone/List/styles.js
@@ -33,9 +33,11 @@ const styles = theme => ({
},
textArea: {
width:'1750px',
- marginLeft: theme.spacing.unit,
- marginRight: theme.spacing.unit,
+ margin: theme.spacing.unit,
whiteSpace: 'pre',
+ width:'98%',
+ overflowY:'hidden',
+ overflowX:'hidden'
},
heading: {
fontSize: theme.typography.pxToRem(18),
@@ -50,13 +52,10 @@ const styles = theme => ({
borderLeft: `2px solid ${theme.palette.divider}`,
padding: `0px ${theme.spacing.unit * 2}px`,
},
- logsDialog:{
- overflowY:'hidden',
- overflowX:'hidden'
- },
logsWrapper:{
+ width:'1750px',
overflowY:'auto',
- overflowX:'auto'
+ overflowX:'hidden'
},
details:{
display:'flex'
diff --git a/server/src/config/clone-deployment.json b/server/src/config/clone-deployment.json
index 770e7bce1..db84d8c18 100644
--- a/server/src/config/clone-deployment.json
+++ b/server/src/config/clone-deployment.json
@@ -14,7 +14,7 @@
"containers": [
{
"name": "aadev",
- "image": "benme/aadev:v3.3",
+ "image": "benme/aadev:v3.4",
"command": ["node", "Run.js"],
"env": [
]
diff --git a/server/src/config/envConfig.js b/server/src/config/envConfig.js
deleted file mode 100644
index a831a6bad..000000000
--- a/server/src/config/envConfig.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import 'dotenv/config'
-
-const env = []
-
-env.push({
- "name": "PLATFORM_ENVIRONMENT",
- "value": process.env.PLATFORM_ENVIRONMENT
-})
-
-env.push({
- "name": "GATEWAY_ENDPOINT",
- "value": process.env.GATEWAY_ENDPOINT
-})
-
-env.push({
- "name": "STORAGE_BASE_URL",
- "value": process.env.STORAGE_BASE_URL
-})
-
-env.push({
- "name": "STORAGE_CONNECTION_STRING",
- "value": process.env.STORAGE_CONNECTION_STRING
-})
-
-env.push({
- "name": "AUTH_URL",
- "value": process.env.AUTH_URL
-})
-
-env.push({
- "name": "AUTH_CLIENT_ID",
- "value": process.env.AUTH_CLIENT_ID
-})
-
-env.push({
- "name": "AUTH_CLIENT_SECRET",
- "value": process.env.AUTH_CLIENT_SECRET
-})
-
-env.push({
- "name": "AUTH_AUDIENCE",
- "value": process.env.AUTH_AUDIENCE
-})
-
-export default env
diff --git a/server/src/config/utils.js b/server/src/config/utils.js
index e9f3b1c12..257881ebc 100644
--- a/server/src/config/utils.js
+++ b/server/src/config/utils.js
@@ -8,8 +8,8 @@ export function isDefined (d) {
return d !== null && typeof d !== 'undefined'
}
-export function getJobNameFromClone (teamSlug, botSlug, mode) {
- return teamSlug + '-' + botSlug + '-' + mode.toLowerCase()
+export function getJobNameFromClone (clone) {
+ return clone.teamSlug + '-' + clone.botSlug + '-' + clone.mode.toLowerCase()
}
export function getSelectedBot(botsByUser, selectedBotId){
diff --git a/server/src/enums/BotTypes.js b/server/src/enums/BotTypes.js
new file mode 100644
index 000000000..62cf49339
--- /dev/null
+++ b/server/src/enums/BotTypes.js
@@ -0,0 +1,11 @@
+export const TRADER = 'Trading'
+export const INDICATOR = 'Indicator'
+export const EXTRACTION = 'Extraction'
+
+export const BotTypesEnum = [
+ TRADER,
+ INDICATOR,
+ EXTRACTION
+]
+
+export default BotTypesEnum
diff --git a/server/src/enums/CloneMode.js b/server/src/enums/CloneMode.js
index 4205db6be..2a3109f26 100644
--- a/server/src/enums/CloneMode.js
+++ b/server/src/enums/CloneMode.js
@@ -1,9 +1,11 @@
export const LIVE = 'Live'
export const BACKTEST = 'Backtest'
export const COMPETITION = 'Competition'
-export const ALL_MONTHS = 'All Months'
-export const ONE_MONTH = 'One Month'
-export const NO_TIME = 'No Time'
+export const ALL_MONTHS = 'allMonths'
+export const ONE_MONTH = 'oneMonth'
+export const NO_TIME = 'noTime'
+export const FIXED_INTERVAL = 'fixedInterval'
+
export const CloneModeEnum = [
LIVE,
@@ -11,7 +13,8 @@ export const CloneModeEnum = [
COMPETITION,
ALL_MONTHS,
ONE_MONTH,
- NO_TIME
+ NO_TIME,
+ FIXED_INTERVAL
]
export default CloneModeEnum
diff --git a/server/src/errors/index.js b/server/src/errors/index.js
index 88ee37eae..b8fb06cf4 100644
--- a/server/src/errors/index.js
+++ b/server/src/errors/index.js
@@ -5,7 +5,6 @@ export class AuthentificationError extends Error {
message = 'Autentification not found, you have to be authentificated to perform this action'
constructor(){
super()
- logger.error(this.stack)
}
}
@@ -14,7 +13,6 @@ export class DatabaseError extends Error {
message = 'Ressource not found : ' + this.message
constructor(message){
super(message)
- logger.error(this.stack)
}
}
@@ -23,7 +21,6 @@ export class WrongArgumentsError extends Error {
message = 'Wrong arguments : ' + this.message
constructor(message){
super(message)
- logger.error(this.stack)
}
}
@@ -32,7 +29,6 @@ export class ServiceUnavailableError extends Error {
message = 'At least one service is unresponding ' + this.message
constructor(message){
super(message)
- logger.error(this.stack)
}
}
@@ -52,13 +48,3 @@ export class KubernateError extends Error {
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/kubernetes/createClone.js b/server/src/kubernetes/createClone.js
index e68654ec3..165b96db4 100644
--- a/server/src/kubernetes/createClone.js
+++ b/server/src/kubernetes/createClone.js
@@ -1,40 +1,66 @@
import logger from '../config/logger'
-import { toPlatformDatetime, getJobNameFromClone, isDefined } from '../config/utils'
-import envConfig from '../config/envConfig'
+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} from '../enums/CloneMode';
-const createClone = async (clone, teamSlug, botSlug) => {
+const createClone = async (clone) => {
try {
- let cloneName = getJobNameFromClone(teamSlug, botSlug, clone.mode)
- logger.info("createClone %s", cloneName)
+ logger.info("createClone %j", clone)
const client = new Client({config: config.fromKubeconfig(), version: '1.9'})
// Make changes to base deployment config
- deploymentManifest.metadata.name = cloneName
+ deploymentManifest.metadata.name = clone.cloneName
logger.debug("createClone Environment and Auth Configuration.")
- let env = envConfig
+ let env = []
+ env.push({
+ "name": "PLATFORM_ENVIRONMENT",
+ "value": process.env.PLATFORM_ENVIRONMENT
+ })
+ env.push({
+ "name": "GATEWAY_ENDPOINT",
+ "value": process.env.GATEWAY_ENDPOINT
+ })
+ env.push({
+ "name": "STORAGE_BASE_URL",
+ "value": process.env.STORAGE_BASE_URL
+ })
+ env.push({
+ "name": "STORAGE_CONNECTION_STRING",
+ "value": process.env.STORAGE_CONNECTION_STRING
+ })
+ env.push({
+ "name": "AUTH_URL",
+ "value": process.env.AUTH_URL
+ })
+ env.push({
+ "name": "AUTH_CLIENT_ID",
+ "value": process.env.AUTH_CLIENT_ID
+ })
+ env.push({
+ "name": "AUTH_CLIENT_SECRET",
+ "value": process.env.AUTH_CLIENT_SECRET
+ })
+ env.push({
+ "name": "AUTH_AUDIENCE",
+ "value": process.env.AUTH_AUDIENCE
+ })
logger.debug("createClone General Financial Being Configuration.")
env.push({
"name": "DEV_TEAM",
- "value": clone.teamId
+ "value": clone.teamSlug
})
env.push({
"name": "BOT",
- "value": clone.botId
+ "value": clone.botSlug
})
env.push({
"name": "TYPE",
- "value": clone.kind
- })
- env.push({
- "name": "PROCESS",
- "value": clone.processName
+ "value": clone.botType
})
env.push({
"name": "CLONE_ID",
@@ -54,7 +80,11 @@ const createClone = async (clone, teamSlug, botSlug) => {
})
logger.debug("createClone Trading Configuration.")
- if(clone.kind === 'Trading'){
+ if(clone.botType === 'Trading'){
+ env.push({
+ "name": "PROCESS",
+ "value": 'Trading-Process'
+ })
env.push({
"name": "RESUME_EXECUTION",
"value": clone.resumeExecution.toString()
@@ -74,7 +104,11 @@ const createClone = async (clone, teamSlug, botSlug) => {
"value": clone.waitTime.toString()
})
}
- } else if(clone.kind === 'Indicator' || clone.kind === 'Extractor'){
+ } else if(clone.botType === 'Indicator' || clone.botType === 'Extraction'){
+ env.push({
+ "name": "PROCESS",
+ "value": clone.processName
+ })
env.push({
"name": "MIN_YEAR",
"value": isDefined(clone.startYear) ? clone.startYear.toString() : ''
@@ -93,12 +127,13 @@ const createClone = async (clone, teamSlug, botSlug) => {
})
}
+ logger.debug("Configuration: %j.", env)
deploymentManifest.spec.template.spec.containers[0].env = env
const create = await client.apis.batch.v1.namespaces('default').jobs.post(
{ body: deploymentManifest })
- logger.info("createClone %s on Kubernates success.", cloneName)
+ logger.info("createClone %s on Kubernates success.", clone.cloneName)
} catch (err) {
throw new KubernateError(err)
}
diff --git a/server/src/kubernetes/removeClone.js b/server/src/kubernetes/removeClone.js
index 2ec404572..d6440626d 100644
--- a/server/src/kubernetes/removeClone.js
+++ b/server/src/kubernetes/removeClone.js
@@ -1,28 +1,26 @@
import logger from '../config/logger'
import { KubernateError } from '../errors'
import { Client, config } from 'kubernetes-client'
-import { getJobNameFromClone } from '../config/utils'
-const removeClone = async (clone, teamSlug, botSlug) => {
+const removeClone = async (clone) => {
try {
- let cloneName = getJobNameFromClone(teamSlug, botSlug, clone.mode)
- logger.info("removeClone %s", cloneName)
+ logger.info("removeClone %s", clone.cloneName)
const client = new Client({config: config.fromKubeconfig(), version: '1.9'})
let query = {
"qs":{
- "labelSelector": "job-name="+cloneName
+ "labelSelector": "job-name=" + clone.cloneName
}
}
const removed = await client.apis.batch.v1.namespaces('default').jobs(
- cloneName).delete()
+ clone.cloneName).delete()
const pod = await client.api.v1.namespaces('default').pods.get(query)
const podRemoved = await client.api.v1.namespaces('default')
.pods(pod.body.items[0].metadata.name).delete()
- logger.info("removeClone %s on kubernates succesful", cloneName)
+ logger.info("removeClone %s on kubernates succesful", clone.cloneName)
} catch (err) {
throw new KubernateError(err)
}
diff --git a/server/src/schema/queries/cloneDetails.js b/server/src/schema/cloneDetails.js
similarity index 50%
rename from server/src/schema/queries/cloneDetails.js
rename to server/src/schema/cloneDetails.js
index 06c3ed99c..447c34ecc 100644
--- a/server/src/schema/queries/cloneDetails.js
+++ b/server/src/schema/cloneDetails.js
@@ -1,19 +1,24 @@
-import logger from '../../config/logger'
-import { getJobNameFromClone, getSelectedBot } from '../../config/utils'
-import teamAvatar from '../../graphQLCalls/teamAvatar'
+import logger from '../config/logger'
+import { getJobNameFromClone, getSelectedBot } from '../config/utils'
+import teamAvatar from '../graphQLCalls/teamAvatar'
+// TODO Refactor this code once financial beings api is ready
const cloneDetails = async(authorization, botsByUser, clone) => {
logger.debug('Retrieving clone details.')
- // TODO Refactor this code once financial beings api is ready
+
let selectedBot = getSelectedBot(botsByUser.data.data.teams_FbByTeamMember, clone.botId)
- clone.cloneName = getJobNameFromClone(botsByUser.data.data.teams_FbByTeamMember.slug, selectedBot.slug, clone.mode)
- clone.kind = selectedBot.kind
+ clone.teamId = botsByUser.data.data.teams_FbByTeamMember.id
+ clone.botId = selectedBot.id
+ clone.teamSlug = botsByUser.data.data.teams_FbByTeamMember.slug
+ clone.botSlug = selectedBot.slug
+ clone.cloneName = getJobNameFromClone(clone)
clone.teamName = botsByUser.data.data.teams_FbByTeamMember.name
clone.botName = selectedBot.name
clone.botAvatar = selectedBot.avatar
-
let team = await teamAvatar(authorization, botsByUser.data.data.teams_FbByTeamMember.id)
clone.teamAvatar = team.data.data.teams_TeamById.profile.avatar
+ clone.userLoggedIn = botsByUser.data.data.teams_FbByTeamMember.members[0].member.alias //TODO change by context info if possible
+
logger.debug('Retrieving clone details complete.')
return clone
};
diff --git a/server/src/schema/mutations/AddCloneMutation.js b/server/src/schema/mutations/AddCloneMutation.js
index 5aceb940b..92511bf3d 100644
--- a/server/src/schema/mutations/AddCloneMutation.js
+++ b/server/src/schema/mutations/AddCloneMutation.js
@@ -2,13 +2,14 @@ import { CloneInputType } from '../types/input';
import { CloneType } from '../types';
import { Clone } from '../../models';
import { CloneModeEnum } from '../../enums/CloneMode';
+import { BotTypesEnum } from '../../enums/BotTypes';
import teams_FbByTeamMember from '../../graphQLCalls/teams_FbByTeamMember';
+import cloneDetails from '../cloneDetails'
import {
AuthentificationError,
WrongArgumentsError,
- OperationsError,
- CustomError
+ OperationsError
} from '../../errors'
import logger from '../../config/logger'
@@ -30,6 +31,10 @@ const resolve = async(parent, { clone }, context) => {
throw new WrongArgumentsError('The mode selected is not valid.')
}
+ if(!Object.values(BotTypesEnum).includes(clone.botType)){
+ throw new WrongArgumentsError('The bot type selected is not valid.')
+ }
+
let existingClone = await Clone.find(
{
authId: context.userId,
@@ -40,25 +45,23 @@ const resolve = async(parent, { clone }, context) => {
logger.debug('addClone -> Checking existing clone %j', existingClone)
if(existingClone.length > 0){
- throw new CustomError('You can only have one active clone by mode. Remove the'
+ throw new OperationsError('You can only have one active clone by mode. Remove the'
+ ' existing clone of type: ' + clone.mode + ' and try again.')
}
try{
// Authorization is handled by the teams module
let botsByUser = await teams_FbByTeamMember(context.authorization)
+ clone = await cloneDetails(context.authorization, botsByUser, clone)
let selectedBot = getSelectedBot(botsByUser.data.data.teams_FbByTeamMember, clone.botId)
- clone.teamId = botsByUser.data.data.teams_FbByTeamMember.id
- clone.botId = selectedBot.id
- clone.userLoggedIn = botsByUser.data.data.teams_FbByTeamMember.members[0].member.alias //TODO change by context info if possible
clone.createDatetime = new Date().valueOf() / 1000|0
clone.active = true
- logger.debug('addClone -> Creating a new clone. %j', clone)
+ logger.debug('addClone -> Creating a new clone.')
let newClone = new Clone(clone)
- newClone.id = newClone._id
newClone.authId = context.userId
- await createKubernetesClone(newClone, botsByUser.data.data.teams_FbByTeamMember.slug, selectedBot.slug )
+ clone.id = newClone._id
+ await createKubernetesClone(clone)
return new Promise((resolve, reject) => {
newClone.save((err) => {
diff --git a/server/src/schema/mutations/RemoveCloneMutation.js b/server/src/schema/mutations/RemoveCloneMutation.js
index dcd51faae..8d1afaf5e 100644
--- a/server/src/schema/mutations/RemoveCloneMutation.js
+++ b/server/src/schema/mutations/RemoveCloneMutation.js
@@ -14,6 +14,7 @@ import logger from '../../config/logger'
import removeKuberneteClone from '../../kubernetes/removeClone'
import teams_FbByTeamMember from '../../graphQLCalls/teams_FbByTeamMember'
import { isDefined, getSelectedBot } from '../../config/utils'
+import cloneDetails from '../cloneDetails'
const args = {
id: { type: new GraphQLNonNull(GraphQLID) }
@@ -33,15 +34,14 @@ const resolve = async (parent, { id }, context) => {
})
if(!isDefined(clone)){
- throw new CustomError('You are not authorized to remove this clone.')
+ throw new OperationsError('You are not authorized to remove this clone.')
}
- // Authorization is also handled by the teams module
let botsByUser = await teams_FbByTeamMember(context.authorization)
- let selectedBot = getSelectedBot(botsByUser.data.data.teams_FbByTeamMember, clone.botId)
+ clone = await cloneDetails(context.authorization, botsByUser, clone)
logger.debug('removeClone -> Removing Clone from Kubernates.')
- await removeKuberneteClone(clone, botsByUser.data.data.teams_FbByTeamMember.slug, selectedBot.slug)
+ await removeKuberneteClone(clone)
const query = {
_id: id,
diff --git a/server/src/schema/queries/HistoryClones.js b/server/src/schema/queries/HistoryClones.js
index 5c20df2bb..467548e27 100644
--- a/server/src/schema/queries/HistoryClones.js
+++ b/server/src/schema/queries/HistoryClones.js
@@ -12,7 +12,7 @@ import logger from '../../config/logger'
import getKuberneteClonePodStatus from '../../kubernetes/getClonePodStatus'
import getKuberneteClonePodLogs from '../../kubernetes/getClonePodLogs'
import teams_FbByTeamMember from '../../graphQLCalls/teams_FbByTeamMember'
-import cloneDetails from './cloneDetails'
+import cloneDetails from '../cloneDetails'
const args = {}
diff --git a/server/src/schema/queries/ListClones.js b/server/src/schema/queries/ListClones.js
index c6cbe9500..c3cc91a67 100644
--- a/server/src/schema/queries/ListClones.js
+++ b/server/src/schema/queries/ListClones.js
@@ -13,7 +13,7 @@ import logger from '../../config/logger'
import getCloneStatus from '../../kubernetes/getClonePodStatus'
import getCloneLogs from '../../kubernetes/getClonePodLogs'
import teams_FbByTeamMember from '../../graphQLCalls/teams_FbByTeamMember'
-import cloneDetails from './cloneDetails'
+import cloneDetails from '../cloneDetails'
const args = {}
diff --git a/server/src/schema/types/CloneType.js b/server/src/schema/types/CloneType.js
index 6c8db2b18..5a653c35e 100644
--- a/server/src/schema/types/CloneType.js
+++ b/server/src/schema/types/CloneType.js
@@ -32,7 +32,7 @@ const CloneType = new GraphQLObjectType({
combinedProfitsB: {type: GraphQLFloat},
assetA: {type: GraphQLString},
assetB: {type: GraphQLString},
- kind: {type: GraphQLString},
+ botType: {type: GraphQLString},
teamName: {type: GraphQLString},
botName: {type: GraphQLString},
botAvatar: {type: GraphQLString},
diff --git a/server/src/schema/types/input/Clone.js b/server/src/schema/types/input/Clone.js
index ef59f0a87..9d20ec6ac 100644
--- a/server/src/schema/types/input/Clone.js
+++ b/server/src/schema/types/input/Clone.js
@@ -22,7 +22,7 @@ const Type = new GraphQLInputObjectType({
month: { type: GraphQLInt },
interval: { type: GraphQLInt },
processName: { type: GraphQLString },
- kind: { type: GraphQLString }
+ botType: { type: new GraphQLNonNull(GraphQLString) }
})
});