summaryrefslogblamecommitdiffstats
path: root/server/lib/external-backends/backends/idoit-backend.js
blob: 1711c9cbb3487fe4acf724fd9600f543f0ccfdbc (plain) (tree)
1
2
3
4
5
6
7
8
9


                                                                                 
                            

                                             


                                                                        
                     
            

                                                                                   

                       



                          

                                                                                  




         



                                                                                              
                                                          
     







                                                                                       
                                                                              


                                                                                              

   

                                                      



                                                                                          

                   

                                                                  








                                                    



                                                                                                                                                
    
                                                                            


                                         
                                              
 
                           
                         
                 
                  

                                      
     


                                                                       
 
                              
                                                                   
 

                                                                                                                                                  

   
                                  



                                                                                
 

                                                                   


                                      




                                                                                                                    
 
                   



                                                                         
                 
   
 
                                                


                                              
 
                   

                                                                                                                    
 
                                                                       



                                                                             
                                                                                                                                                                          

                 

   
                                                           
                                            


                                              
 

                   
                                            

                                                                                                             

                                     
 
                                                                              





                                                                       
     
                 

   



                                       
                                            
              
                                                             



                                                                 


                                              

                         
                           

                                                              
     
                                               
 



                                                                                                          
                        
 

                                                                                                                                                                                                                                                   

                          
                                                                              




                                                                                                                                                                                                  

                                     

                                                                                                                                                                                   
 
                                                                                            
 




                                                                       

                                            



















                                                                                                                                                                                                                           

                               










                                        
                           






                                       
                           



                                    
                                            







                                                         

                                                



                                                                                                                   
            






                                                                                                  
            

         
     
 

                                                                                                

                                        

                                              




                                                                       
                                                                            

                                                         
     
 






































































                                                                                                                      
                               

                                         

                                                                            

                     
                                                                                                      
                                                                                                             

                                                                                             
                        

                                                                      
                          
                        

                                              
                              
                                                                             

                                 
                                                                               
 
                         



                                               

                                  


                            












                                                                                                 
       




                                                                         



                                                                 

                           

                            
                                            
                          
       


                                     

                                                                                          
                                      
 

                                         



                                                                                       
                                                                                                                                        
                                                                 

     



                                                             
                                                                                                                                                  










                                                                                                                                                
                                                                                                                                                    
                                                                                                                                                                                                                 


                                            


                                              
 



                                                                                                
                   

                                                 

                                 
                                                                                                                                                   
 

                                                                                                                                                         
                      





                                     
          


                                                                            

     











                                                                                      

     
                                     
                        







                                                     
          


                                                                             

     

                                                                                                              











                                                                                                                              
                                                               














                                                                                              
         

















                                                                                                 

                                   


                                    

                                                 









                                                   
                                       





                                        
                                                                                        
       
     
 





















                                                                                        
                                   
                     
                     



                                                      
 




                                        
                                  


                                                    

                                                                                                                           
            

                            
                 
                                                                                           


       
                                     
                        
                     

                                          
               









                                                                    
 




                                                


                                   
                                       

                                



                                                   
            

                            
 
                                                                                          
                 

       
 





                                                                           
 
                                             





                                               
                    
                    
                 
                              




                 
                                               






                                                                                                                                       


                                                     



                                                                                                                                       
 

                                                                                
 


                                                                                                                                                    



                                                                                                  
                                                      


                                              
 


                                      
                          
                                                     

                         


                                                                                                                                              
 
                                   







                                                                                                           
          

                                                     
       








                                                                                                      
          



                                                                                                    
 


                                                                                                       

   
















                                                                                     

















                                                                                                                         
                                                                                   















                                                                                               




                                                               






                                                                                 
     
                  

   


                                                             
                       
                       




                        
     
   
 
















                                                                                                                                                                         
     

                  



                             
/* global __appdir */
const path = require('path')
const ExternalBackends = require(path.join(__appdir, 'lib', 'external-backends'))
var axios = require('axios')

class IdoitBackend extends ExternalBackends {
  /*
   * Returns the credential structure / fields, defined in the backends.
   */
  getCredentials () {
    return [
      { type: 'text', id: 1, name: 'API URL', icon: 'link' },
      { type: 'password', id: 2, name: 'API Token', icon: 'vpn_key', show: false },
      {
        type: 'switch',
        id: 3,
        name: 'Login',
        icon: 'lock_open',
        elements: [
          { type: 'text', id: 4, name: 'Username', icon: 'person_outline' },
          { type: 'password', id: 5, name: 'Password', icon: 'lock', show: false }
        ]
      }
    ]
  }

  /*
   * Checks the connection of a given backend.
   * idoIT: Checks if we can get a valid session id. If so the connection must be successfull.
   *
   * return: true / { error: <ERROR>, message: <MESSAGE> }
   */
  async checkConnection (credentials) {
    const c = this.mapCredentials(credentials)
    const body = this.getBody('idoit.version', { 'apikey': c.apikey }, 'version_check')
    const headers = await this.getHeaders(c)
    if (headers.error) return headers

    // Axios request
    const result = await this.axiosRequest(c.url, [body], headers)
    if (result.error) return { error: 'IDOIT_ERROR', message: result.message }
    // Check if it actually returned some legit idoit stuff and not just a valid response.
    if (result.length === 1 && result[0].id === 'version_check') return true
    else return { error: 'IDOIT_ERROR', message: 'The request returned an invalid response!' }
  }

  // Return the list of object types created in iDoIT.
  async getObjectTypes (credentials) {
    const c = this.mapCredentials(credentials)
    const body = this.getBody('cmdb.object_types', { 'apikey': c.apikey }, 'object_types')
    const headers = await this.getHeaders(c)
    if (headers.error) return headers

    var result = {}
    result.types = await this.axiosRequest(c.url, [body], headers)
    result.types = result.types[0].result

    var types = []
    result.types.forEach(type => {
      types.push({ id: type.id, title: type.title })
    })

    return types
  }

  getSyncTypes () {
    return ['None', 'Two-Way', 'Upload Only', 'Upload Then Delete', 'Upload Mirror', 'Download Only', 'Download Then Delete', 'Download Mirror']
  }

  /*
   * Gets all objects and searches for the uuid to get the requested client.
   *
   */
  async getClient (credentials, client) {
    const c = this.mapCredentials(credentials)

    const paramsObjects = {
      'apikey': c.apikey,
      'filter': {
        'type': 10
      },
      'categories': ['C__CATG__MODEL']
    }
    const body = this.getBody('cmdb.objects', paramsObjects, 'objects')
    const headers = await this.getHeaders(c)
    if (headers.error) return headers

    // Get all client objects.
    const clients = await this.axiosRequest(c.url, [body], headers)

    let result = clients[0].result.filter(x => x.categories.C__CATG__MODEL.length > 0 && x.categories.C__CATG__MODEL[0].productid === client.uuid)
    return result.length >= 1 ? result.map(x => x.id) : { error: 'NO_CLIENT_FOUND', msg: 'There is no client matching with the provided uuid.' }
  }

  async getObjects (credentials) {
    const c = this.mapCredentials(credentials)
    const body = this.getBody('cmdb.objects', { 'apikey': c.apikey }, 'objects')
    const headers = await this.getHeaders(c)
    if (headers.error) return headers

    const objects = await this.axiosRequest(c.url, [body], headers)
    return objects[0]
  }

  async getObject (credentials, oid) {
    const c = this.mapCredentials(credentials)
    const body = this.getBody('cmdb.object.read', { 'apikey': c.apikey, 'id': oid }, 'object_read')
    const bodyChilds = this.getBody('cmdb.location_tree', { 'apikey': c.apikey, 'id': oid }, 'object_location_tree')
    const headers = await this.getHeaders(c)
    if (headers.error) return headers

    var result = {}
    result.object = await this.axiosRequest(c.url, [body], headers)
    result.childs = await this.axiosRequest(c.url, [bodyChilds], headers)
    result.object = result.object[0].result
    result.childs = result.childs[0].result
    return result
  }

  async deleteObjects (credentials, objectIds) {
    const c = this.mapCredentials(credentials)
    const headers = await this.getHeaders(c)
    if (headers.error) return headers

    let bodies = []

    bodies.push(this.getBody('cmdb.object.quickpurge', { 'id': objectIds, 'apikey': c.apikey }, 'batch_quickpurge'))

    let deleteRequest = await this.axiosRequest(c.url, bodies, headers)
    const errorRegex = /(?<=\s#)[0-9]+/g

    const failedDeletions = deleteRequest[0].result.message.match(errorRegex)
    let result = []
    if (failedDeletions) for (let failedObjId of failedDeletions) result.push({ error: true, message: `Deletion of object ${failedObjId} was skipped.`, id: failedObjId })

    return result
  }

  // Function to use the same session for multiple requests
  async getDataTree (credentials, objects) {
    const c = this.mapCredentials(credentials)
    const headers = await this.getHeaders(c)
    if (headers.error) return headers

    let bodies = []
    let gids = {}
    // Prepare all the batch request bodies.
    for (let element of objects) {
      bodies.push(this.getBody('cmdb.location_tree', { 'id': element.eid, 'apikey': c.apikey }, element.eid))
      gids[element.eid] = element.gid
    }

    // Send all the batch request and post proccess the result into one array.
    let requestResult = await this.axiosRequest(c.url, bodies, headers)

    // Post process the data.
    let result = []
    for (let res of requestResult) {
      result.push({ gid: gids[res.id], childs: res.result })
    }
    return result
  }

  /*
   * Adds the client to the backend.
   *
   * credentials: <BACKEND_CREDENTIALS>
   * The client parameters are all optional.
   * client: {
   *            title: <CLIENT_TITLE>, parentId: <PARENT_ID>,
   *            network: { mac: <MAC_ADDRESS>, ip: <IP_ADDRESS> }
   *         }
   */
  async addClient (credentials, client) {
    const c = this.mapCredentials(credentials)
    const headers = await this.getHeaders(c)
    if (headers.error) return headers
    let params = {
      'apikey': c.apikey,
      'title': client.name,
      'purpose': client.purpose === 'Pool PC' ? 7 : undefined,
      'categories': {}
    }
    client.parentId = parseInt(client.parentId)

    if (client.type === 'CLIENT') {
      params['type'] = 10
      if (client.parentId) params.categories.C__CATG__LOCATION = { 'data': { 'parent': client.parentId } }
    } else if (client.type === 'SERVER') {
      params['type'] = 5

      if (client.location && client.location.bay === null) params.categories.C__CATG__LOCATION = { 'data': { 'parent': client.parentId, 'option': client.location.assembly, 'insertion': client.location.insertion, 'pos': client.location.slot } }
      if (client.formfactor) params.categories.C__CATG__FORMFACTOR = { 'data': { 'formfactor': client.formfactor.formfactor, 'rackunits': client.formfactor.rackunits } }

      // Rack segmentation
      if (client.location.bay !== undefined && client.location.bay !== null) {
        // Get all assigned objects of the rack (parentid) to check for existing rack segments at slot position.
        const rackobjectsBody = this.getBody('cmdb.category.read', { 'apikey': c.apikey, 'object': client.parentId, 'objID': client.parentId, 'category': 'C__CATG__OBJECT' }, 'get_rack_objects')
        const rackobjects = await this.axiosRequest(c.url, [rackobjectsBody], headers)

        // Get the name of the rack
        console.log('')
        console.log('Get Rack Name:')
        const rackBody = this.getBody('cmdb.category.read', { 'apikey': c.apikey, 'object': client.parentId, 'objID': client.parentId, 'category': 'C__CATG__GLOBAL' }, 'get_rack')
        const rack = await this.axiosRequest(c.url, [rackBody], headers)

        if (!rack[0].result) return { error: 'IDOIT_ERROR', message: rack[0].error.message }

        const rackName = rack[0].result[0].title

        let objectPositionBodies = []

        // For each segmentation object in the rack get the slot number
        console.log('')
        console.log('Get Slot IDs Request:')
        for (let obj in rackobjects[0].result) {
          const object = rackobjects[0].result[obj]
          if (object.assigned_object.type !== 'C__OBJTYPE__RACK_SEGMENT') continue
          objectPositionBodies.push(this.getBody('cmdb.category.read', { 'apikey': c.apikey, 'object': object.objID, 'objID': object.objID, 'category': 'C__CATG__LOCATION' }, 'get_rack_object_position_' + object.objID))
        }
        let objectPositions = await this.axiosRequest(c.url, objectPositionBodies, headers)
        if (objectPositions.length >= 1) objectPositions = objectPositions.filter(x => parseInt(x.result[0].pos.title) === parseInt(client.location.slot))

        // There should only be one segment object for the rack slot if so set it as parent
        var chassisId
        if (objectPositions.length === 1) chassisId = parseInt(objectPositions[0].result[0].objID)
        else {
          // Create a new rack segment
          const createSegmentParamObject = {
            'apikey': c.apikey,
            'type': 92,
            'title': rackName + ' Slot ' + client.location.slot,
            'categories': {
              'C__CATS__CHASSIS': {
                'data': {
                  'front_x': 2,
                  'front_y': 1,
                  'rear_x': 0,
                  'rear_y': 0
                }
              },
              'C__CATS__CHASSIS_SLOT': [
                {
                  'title': 'Bay 1',
                  'insertion': 'front',
                  'from_x': 0,
                  'to_x': 0,
                  'from_y': 0,
                  'to_y': 0
                },
                {
                  'title': 'Bay 2',
                  'insertion': 'front',
                  'from_x': 1,
                  'to_x': 1,
                  'from_y': 0,
                  'to_y': 0
                }
              ],
              'C__CATG__LOCATION': {
                'data': {
                  'parent': client.parentId,
                  'option': client.location.option,
                  'insertion': client.location.insertion,
                  'pos': client.location.slot
                }
              }
            }
          }

          console.log('')
          console.log('Create Segment Request:')
          const createSegmentParam = this.getBody('cmdb.object.create', createSegmentParamObject, 'create_segment')
          const createSegment = await this.axiosRequest(c.url, [createSegmentParam], headers)
          chassisId = createSegment[0].result.id

          /*
          // Set the new rack units height. (Needs an extra request, why? I DONT KNOW... idoit...)
          const setSegmentSizeParams = this.getBody('cmdb.category.save', { 'apikey': c.apikey,
            'object': chassisId,
            'objID': chassisId,
            'category': 'C__CATG__FORMFACTOR',
            'data': { 'rackunits': client.formfactor.rackunits } }, 'set_segment_size')
          await this.axiosRequest(c.url, [setSegmentSizeParams], headers)
          */
        }
      }
    }

    // Add categories to the object
    if (client.uuid) params.categories.C__CATG__MODEL = { 'data': { 'productid': client.uuid } }
    if (client.networks) {
      params.categories.C__CATG__IP = []
      for (let index in client.networks) {
        const network = client.networks[index]
        let networkparams = {}
        if (network.ip) networkparams.ipv4_address = network.ip
        if (network.hostname) networkparams.hostname = network.hostname
        if (network.domain) networkparams.domain = network.domain
        if (network.net) networkparams.net = network.net
        if (network.primary) networkparams.primary = network.primary ? 1 : 0
        params.categories.C__CATG__IP.push(networkparams)
      }
    }

    // Add contact assignment to the object.
    if (client.contacts) {
      // Get the persons ids.
      let readPersonBodies = []
      for (let index in client.contacts) {
        readPersonBodies.push(this.getBody('cmdb.objects.read', {
          'apikey': c.apikey,
          'filter': {
            'type': 'C__OBJTYPE__PERSON',
            'first_name': client.contacts[index].first_name,
            'last_name': client.contacts[index].last_name
          }
        }, 'read_persons_' + index))
      }
      console.log('')
      console.log('Read Person Request:')
      const requestReadPersons = await this.axiosRequest(c.url, readPersonBodies, headers)
      if (requestReadPersons.error) return requestReadPersons
      const error = requestReadPersons.filter(x => x.error)

      if (error.length === 0) {
        const personIds = requestReadPersons.map(x => {
          if (x.result.length === 1) return x.result[0].id
        }).filter(Boolean)
        params.categories.C__CATG__CONTACT = []
        for (let index in personIds) {
          params.categories.C__CATG__CONTACT.push({
            'contact': personIds[index]
          })
        }
      } else console.log(error)
    }

    // Add operating system information.
    if (client.runtime && client.runtime.operating_system) {
      // Get the operating system ids.
      console.log('')
      console.log('Get OS Request:')
      const getOSParam = {
        'apikey': c.apikey,
        'filter': {
          'type': 35, // 35 = Operating System
          'title': client.runtime.operating_system.name
        }
      }
      const getOSBody = this.getBody('cmdb.objects.read', getOSParam, 'get_os')
      const requestGetOS = await this.axiosRequest(c.url, [getOSBody], headers)

      // Extra request for getting the id of the version number
      console.log('')
      console.log('Get OS-Version Request:')
      const getOSVersionParam = {
        'apikey': c.apikey,
        'objID': requestGetOS[0].result[0].id,
        'catgID': 'C__CATG__VERSION'
      }
      const getOSVersionBody = this.getBody('cmdb.category.read', getOSVersionParam, 'get_os_version')
      const requestGetOSVersion = await this.axiosRequest(c.url, [getOSVersionBody], headers)
      const osVersion = requestGetOSVersion[0].result.filter(x => x.title === client.runtime.operating_system.version)

      // Add the result of the OS request (ids) to the create request.
      if (requestGetOS[0].result) {
        params.categories.C__CATG__OPERATING_SYSTEM = {
          'data': {
            'application': requestGetOS[0].result[0].id,
            'assigned_version': osVersion[0].id
          }
        }
      }
    }

    // Send the create request.
    console.log('')
    console.log('Create Client Request:')
    const body = this.getBody('cmdb.object.create', params, 'client_create')
    const requestCreate = await this.axiosRequest(c.url, [body], headers)

    // Error handling
    if (requestCreate.error) return { error: requestCreate.errno, message: 'Connection was refused.' }
    else if (requestCreate[0].error) return { error: 'IDOIT_ERROR', message: requestCreate[0].error.message }

    // Add mac address: Network port is a subcategory of network so it need an extra request.
    let macRequests = []
    const hostnameIds = requestCreate[0].result.categories.C__CATG__IP

    if (client.networks) {
      let macBodies = []
      for (let index in client.networks) {
        const network = client.networks[index]
        // For the ip adresses
        // network.id = requestCreate[0].result.categories.C__CATG__IP[index]
        let addresses = []
        // Push the ids as string
        if (hostnameIds.length > index) addresses.push('' + hostnameIds[index])

        let paramsMac = {
          'object': requestCreate[0].result.id,
          'objID': requestCreate[0].result.id,
          'category': 'C__CATG__NETWORK_PORT',
          'data': {
            'mac': network.mac,
            'addresses': addresses
          },
          'apikey': c.apikey
        }

        if (network.device) {
          if (network.device.speed) {
            paramsMac.data.speed = parseFloat(network.device.speed)
            // MB/s GB/s ... etc not supported?! Only Mbit/s Gbit/s ...
            paramsMac.data.speed_type = null
          }

          if (network.device.name) paramsMac.data.title = network.device.name
          if (network.device.type) paramsMac.data.port_type = network.device.type
        }

        macBodies.push(this.getBody('cmdb.category.save', paramsMac, 'add_mac_address_' + index))
      }

      console.log('')
      console.log('Add MAC Request:')
      const response = await this.axiosRequest(c.url, macBodies, headers)
      macRequests.push(response)
    }

    // If chassis id is set, assign the object to the chassis bay
    if (chassisId) {
      // Read bay ids
      const paramsSlots = {
        'object': chassisId,
        'objID': chassisId,
        'category': 'C__CATS__CHASSIS_SLOT',
        'apikey': c.apikey
      }

      console.log('')
      console.log('Read Rack Slots:')
      const readSlotsParam = this.getBody('cmdb.category.read', paramsSlots, 'read_slots')
      const readSlots = await this.axiosRequest(c.url, [readSlotsParam], headers)
      const bays = readSlots[0].result

      console.log('')
      console.log('Assign to Rack Slot:')
      const assignToSlotBody = this.getBody('cmdb.category.save', { 'apikey': c.apikey,
        'objID': chassisId,
        'object': chassisId,
        'category': 'C__CATS__CHASSIS_DEVICES',
        'data': { 'assigned_device': requestCreate[0].result.id, 'assigned_slots': [bays[client.location.bay].id] } }, 'assign_to_slot')
      await this.axiosRequest(c.url, [assignToSlotBody], headers)
    }

    // Purpose for Clients:
    // 1 = Production         |  5 = PVS
    // 2 = Test               |  7 = Pool PC
    // 3 = Quality Assurance  |  8 = Mitarbeiter Arbeitsplatz
    return { succes: true, id: requestCreate[0].result.id, type: params.type, message: requestCreate[0].result.message, macRequests: macRequests }
  }

  /*
   * Updates the client information in the backend.
   *
   * credentials: <BACKEND_CREDENTIALS>
   * The client parameters are all optional.
   * client: {
   *            id: <CLIENT_ID>, title: <CLIENT_TITLE>, parentId: <PARENT_ID>,
   *            system: { model: <SYSTEM_MODEL>, manufacturer: <SYSTEM_MANUFACTURER>,  serialnumber: <SYSTEM_SERIALNUMBER> },
   *            cpu: { model: <CPU_MODEL>, manufacturer: <CPU_MANUFACTURER>, type: <CPU_TYPE>, frequency: <CPU_FREQUENCY>, cores: <CPU_CORES> },
   *            ram: [{ title: <RAM_TITLE>, manufacturer: <RAM_MANUFACTURER>, type: <RAM_TYPE>,  capacity: <RAM_CAPACITY>, unit: <RAM_UNIT> }, ...],
   *            drives: [{model: <DRIVE_MODEL>,}, serial: <DRIVE_SERIAL>, capacity: <DRIVE_CAPACITY>, unit: <DRIVE_UNIT>, type: <DRIVE_TYPE>, formfactor: <DRIVE_FORMFACTOR>, connection: <DRIVE_CONNECTION> ...]
   *         }
   */
  async updateClient (credentials, client) {
    const c = this.mapCredentials(credentials)
    const headers = await this.getHeaders(c)
    if (headers.error) return headers

    // Parse needed because db values are strings. (Other backends need strings as external ids)
    client.id = parseInt(client.id)
    client.parentId = parseInt(client.parentId)

    let bodies = []
    // workaround for the fucking idoit shit. -.-
    let requestResults = []

    // Update title of the object
    if (client.name) bodies.push(this.getBody('cmdb.object.update', { 'id': client.id, 'title': client.name, 'apikey': c.apikey }, 'update_title'))

    // 'object' should be 'objID' but there is a fucking bug in the idoit api. Soo let's add both parameters because else it will break when they fix it.
    // Update the productid to the uuid.
    if (client.uuid) {
      let params = {
        'object': client.id,
        'objID': client.id,
        'category': 'C__CATG__MODEL',
        'data': {
          'productid': client.uuid
        },
        'apikey': c.apikey
      }
      bodies.push(this.getBody('cmdb.category.save', params, 'update_uuid'))
    }

    if (client.parentId) {
      // Update the object. Location
      let paramsLocation = {
        'object': client.id,
        'objID': client.id,
        'category': 'C__CATG__LOCATION',
        'data': {
          'parent': client.parentId
        },
        'apikey': c.apikey
      }
      bodies.push(this.getBody('cmdb.category.save', paramsLocation, 'update_parent'))
    }

    // Update the object. Model data.
    if (client.system) {
      let params = {
        'object': client.id,
        'objID': client.id,
        'category': 'C__CATG__MODEL',
        'data': {
          'manufacturer': client.system.manufacturer,
          'title': client.system.model,
          'serial': client.system.serialnumber
        },
        'apikey': c.apikey
      }
      bodies.push(this.getBody('cmdb.category.save', params, 'update_model'))
    }

    // Update networks & Check if there is at least one network which will get updated.
    if (client.networks && client.networks.filter(network => Object.keys(network).length >= 4).length !== 0) {
      let parmReadNetwork = {
        'object': client.id,
        'objID': client.id,
        'category': 'C__CATG__IP',
        'apikey': c.apikey
      }
      const ips = await this.axiosRequest(c.url, [this.getBody('cmdb.category.read', parmReadNetwork, 'read_ips')], headers)
      parmReadNetwork.category = 'C__CATG__NETWORK_PORT'
      const macs = await this.axiosRequest(c.url, [this.getBody('cmdb.category.read', parmReadNetwork, 'read_macs')], headers)

      for (let index in client.networks) {
        const network = client.networks[index]
        if (network.ip && network.hostname && network.domain) {
          // Update ip addresses
          let paramsIp = {
            'object': client.id,
            'objID': client.id,
            'category': 'C__CATG__IP',
            'data': {
              'ipv4_address': network.ip,
              'hostname': network.hostname,
              'domain': network.domain,
              'net': network.net
            },
            'apikey': c.apikey
          }
          if (ips[0].result.length > index) paramsIp.entry = parseInt(ips[0].result[index].id)
          bodies.push(this.getBody('cmdb.category.save', paramsIp, 'update_ip'))
        }

        // Update mac addresses
        if (network.mac) {
          let paramsMac = {
            'object': client.id,
            'objID': client.id,
            'category': 'C__CATG__NETWORK_PORT',
            'data': {
              'mac': network.mac
            },
            'apikey': c.apikey
          }
          if (macs[0].result.length > index) paramsMac.entry = parseInt(macs[0].result[index].id)
          bodies.push(this.getBody('cmdb.category.save', paramsMac, 'update_mac'))
        }
      }
    }

    // Update the object. CPU data.
    // TODO: Delete cpu if exists?
    if (client.cpus) {
      let counter = 1
      for (let cpu of client.cpus) {
        if (cpu.unit === 'MHz') cpu.unit = 2
        else if (cpu.unit === 'GHz') cpu.unit = 3

        let params = {
          'object': client.id,
          'objID': client.id,
          'category': 'C__CATG__CPU',
          'data': {
            'manufacturer': cpu.manufacturer,
            'title': cpu.model,
            'type': cpu.type,
            'frequency': parseFloat(cpu.frequency),
            'frequency_unit': cpu.unit,
            'cores': parseInt(cpu.cores)
          },
          'apikey': c.apikey
        }

        counter++
        bodies.push(this.getBody('cmdb.category.save', params, 'create_cpu_' + counter))
      }
    }

    // GPUS
    if (client.gpus) {
      let counter = 1
      for (let gpu of client.gpus) {
        let params = {
          'object': client.id,
          'objID': client.id,
          'category': 'C__CATG__GRAPHIC',
          'data': {
            'manufacturer': gpu.manufacturer,
            'title': gpu.model,
            'memory': gpu.memory,
            'unit': gpu.unit
          },
          'apikey': c.apikey
        }

        counter++
        bodies.push(this.getBody('cmdb.category.save', params, 'create_gpu_' + counter))
      }
    }

    // Update the object. Ram data.
    if (client.ram) {
      let counter = 1
      for (let module of client.ram.modules) {
        // Add KB and TB
        if (module.unit === 'MB') module.unit = 2
        else if (module.unit === 'GB') module.unit = 3

        let params = {
          'object': client.id,
          'objID': client.id,
          'category': 'C__CATG__MEMORY',
          'data': {
            'title': module.model,
            'manufacturer': module.manufacturer,
            'type': module.type,
            'capacity': parseFloat(module.capacity),
            'unit': module.unit,
            'description': `Serialnumber: ${module.serialnumber}\nFormfactor: ${module.formfactor}\nSpeed: ${module.speed}`
          },
          'apikey': c.apikey
        }
        counter++
        bodies.push(this.getBody('cmdb.category.save', params, 'create_memory_' + counter))
      }
    }

    // Update the object. Drive data.
    if (client.drives) {
      let counter = 1
      for (let index in client.drives) {
        const drive = client.drives[index]
        // UNIT
        if (drive.unit === 'GB') drive.unit = 3
        else if (drive.unit === 'MB') drive.unit = 2
        else if (drive.unit === 'TB') drive.unit = 4
        else if (drive.unit === 'KB') drive.unit = 1
        else if (drive.unit === 'B') drive.unit = 0

        if (drive.type === 'Solid State Device') drive.type = 'SSD'
        else if (drive.type === '5400 rpm') drive.type = 'Hard disk'
        else if (drive.type === '7200 rpm') drive.type = 'Hard disk'
        else if (drive.type === '') drive.type = 'Hard disk'

        let params = {
          'object': client.id,
          'objID': client.id,
          'category': 'C__CATG__STORAGE_DEVICE',
          'data': {
            'category_id': counter,
            'title': drive.model,
            'type': drive.type,
            'firmware': drive.firmware,
            // 'manufacturer': ,
            // 'model': ,
            'capacity': parseFloat(drive.capacity),
            'unit': drive.unit,
            'serial': drive.serial,
            'connected': drive.connection
          },
          'apikey': c.apikey
        }

        bodies.push(this.getBody('cmdb.category.save', params, 'create_drive_' + counter))
        counter++
      }
    }

    if (bodies.length > 0) {
      const requestUpdate = await this.axiosRequest(c.url, bodies, headers)
      requestResults.push(requestUpdate)

      if (requestUpdate.error) return requestUpdate
    }

    // 10 is the idoit object id for clients.
    // 5 is the idoit object id for servers.
    let type = 0
    if (client.type === 'CLIENT') type = 10
    else if (client.type === 'SERVER') type = 5

    const result = {
      success: true,
      id: client.id,
      type: type,
      response: requestResults
    }

    return result
  }

  async getFileList (credentials, externalId) {
    const c = this.mapCredentials(credentials)
    const body = this.getBody('cmdb.category.read', { 'objID': externalId, 'category': 'C__CATG__FILE', apikey: c.apikey }, 'filelist')
    const headers = await this.getHeaders(c)
    if (headers.error) return headers

    const result = await this.axiosRequest(c.url, [body], headers)
    return result[0].result
  }

  async getFile (credentials, externalId, filename) {
    const c = this.mapCredentials(credentials)
    const body = this.getBody('cmdb.category.read', { 'objID': externalId, 'category': 'C__CATG__FILE', apikey: c.apikey }, 'filelist')
    const headers = await this.getHeaders(c)
    if (headers.error) return headers

    const files = await this.axiosRequest(c.url, [body], headers)
    let fileObjId = files[0].result.find(x => x.file.title === filename).file.id

    const body2 = this.getBody('cmdb.category.read', { 'objID': fileObjId, 'category': 'C__CMDB__SUBCAT__FILE_VERSIONS', apikey: c.apikey }, 'file')
    let result = await this.axiosRequest(c.url, [body2], headers)
    result = result[0].result[0]

    return { filename: result.file_title, value: result.file_content.value, md5: result.md5_hash }
  }

  async uploadFiles (credentials, externalId, files) {
    const c = this.mapCredentials(credentials)
    const headers = await this.getHeaders(c)
    if (headers.error) return headers

    let result = []
    for (var key in files) {
      const filename = files[key].name
      /* eslint-disable */
      const buffer = new Buffer.from(files[key].data)
      /* eslint-enable */

      // Create the fileObject where the file gets uplaoded to.
      const body = this.getBody('cmdb.object.create', { 'type': 29, 'title': files[key].name, 'apikey': c.apikey }, 'createFileObject_' + key)
      const fileObject = await this.axiosRequest(c.url, [body], headers)

      // Upload file to fileobject.
      const paramsUploadFile = {
        'object': fileObject[0].result.id, // <-- workaround for the idoit api bug (wrong mapping of objID)
        'objID': fileObject[0].result.id,
        'data': {
          'file_content': buffer.toString('base64'),
          'file_physical': filename,
          'file_title': filename,
          'version_description': key
        },
        'category': 'C__CMDB__SUBCAT__FILE_VERSIONS',
        'apikey': c.apikey
      }
      const bodyUploadFile = this.getBody('cmdb.category.save', paramsUploadFile, 'uploadFile_' + key)
      const uploadFile = await this.axiosRequest(c.url, [bodyUploadFile], headers)

      // Connect the file with the client (object).
      const paramsConnectFile = {
        'object': externalId,
        'objID': externalId,
        'data': {
          'file': fileObject[0].result.id
        },
        'category': 'C__CATG__FILE',
        'apikey': c.apikey }
      const bodyConnect = this.getBody('cmdb.category.save', paramsConnectFile, 'uploadFile_' + key)
      const connectFile = await this.axiosRequest(c.url, [bodyConnect], headers)

      result.push({ createObject: fileObject, uploadFile: uploadFile, connectObjectFile: connectFile })
    }
    return result
  }

  // Helper function, to map the array of credential objects into a single js object.
  mapCredentials (credentials) {
    const c = JSON.parse(credentials)
    const login = c.find(x => x.id === 3)
    var mapped = {
      url: c.find(x => x.id === 1).value,
      apikey: c.find(x => x.id === 2).value,
      login: login.value
    }

    if (mapped.login) {
      mapped.username = login.elements.find(x => x.id === 4).value
      mapped.password = login.elements.find(x => x.id === 5).value
    }
    return mapped
  }

  // Method for making the axios request and error handling.
  async axiosRequest (url, bodies, headers, batchRequestSize = 400) {
    let config = { timeout: 180000, headers: headers }

    // Split batchrequest in multiple request
    let batchRequests = []
    // For spliceing the array a while loop seems to be the most efficient:
    // https://ourcodeworld.com/articles/read/278/how-to-split-an-array-into-chunks-of-the-same-size-easily-in-javascript
    while (bodies.length) {
      batchRequests = [...batchRequests, bodies.splice(0, batchRequestSize)]
    }
    // Make all batch request and return the result of all.
    let results = []
    let requestCounter = 1

    for (let i = 0; i < batchRequests.length; i++) {
      // Axios error handling
      try {
        console.log(requestCounter + '/' + batchRequests.length + ' requests sent')
        requestCounter++
        const responses = await axios.post(url, batchRequests[i], config)
        if (Array.isArray(responses.data)) results = [...results, ...responses.data]
        else results = [...results, responses.data]
      } catch (error) {
        let errorResponse = { error: true }
        if (error.response) {
          // The request was made and the server responded with a status code
          // that falls out of the range of 2xx
          errorResponse['data'] = error.response.data
          errorResponse['status'] = error.response.status
          errorResponse['headers'] = error.response.headers
        } else if (error.request) {
          // The request was made but no response was received
          // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
          // http.ClientRequest in node.js
          let errorMsg = ''
          if (error.code) errorMsg += '[' + error.code + '] '
          if (error.errno) errorMsg += '[' + error.errno + '] '
          if (error.message) errorMsg += error.message
          errorResponse['message'] = errorMsg
        } else {
          // Something happened in setting up the request that triggered an Error
          errorResponse['message'] = error.message
        }

        return errorResponse
      }
    }
    return results
  }

  // Body wrapper for the requests to remove duplicated code.
  getBody (method, params, id) {
    return {
      'version': '2.0',
      'method': method,
      'params': {
        ...params,
        'language': 'en'
      },
      'id': id
    }
  }

  // Returns the header. Sets the session id if needed.
  async getHeaders (credentials = {}) {
    let headers = { 'Content-Type': 'application/json' }

    // Get a session id for the header
    if (credentials.login && credentials.username && credentials.password) {
      const header = {
        ...headers,
        'X-RPC-Auth-Username': credentials.username,
        'X-RPC-Auth-Password': credentials.password
      }
      const sessionRequest = await this.axiosRequest(credentials.url, [this.getBody('idoit.login', { 'apikey': credentials.apikey, 'language': 'en' }, 'login')], header)
      // Axios errors
      if (sessionRequest.error) return sessionRequest
      // Idoit error
      if (sessionRequest[0].error) return { error: 'IDOIT_ERROR', message: sessionRequest[0].error.message }
      headers = { ...headers, 'X-RPC-Auth-Session': sessionRequest[0].result['session-id'] }
    }

    return headers
  }
}

module.exports = IdoitBackend