changes for system service offering handling

Signed-off-by: Abhishek Kumar <af012c8507429d6aba5b82a315e3a191d146c0de@gmail.com>
Signed-off-by: Rohit Yadav <902b9eb5dd62819678163bd5e00fbf0cbc7c264b@shapeblue.com>

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

<template>
  <div class="form-layout">
    <a-spin :spinning="loading">
      <a-form
        :form="form"
        @submit="handleSubmit"
        layout="vertical">
        <a-form-item :label="$t('name')">
          <a-input
            v-decorator="['name', {
              rules: [{ required: true, message: 'Please enter name' }]
            }]"
            :placeholder="this.$t('Name')"/>
        </a-form-item>
        <a-form-item :label="$t('displaytext')">
          <a-input
            v-decorator="['displaytext', {
              rules: [{ required: true, message: 'Please enter description' }]
            }]"
            :placeholder="this.$t('displaytext  ')"/>
        </a-form-item>
        <a-form-item :label="$t('systemvmtype')" v-if="this.isSystem">
          <a-select
            v-decorator="['systemvmtype', {
              initialValue: 'domainrouter'
            }]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :placeholder="this.$t('systemvmtype')">
            <a-select-option key="domainrouter">{{ $t('domain.router') }}</a-select-option>
            <a-select-option key="consoleproxy">{{ $t('console.proxy') }}</a-select-option>
            <a-select-option key="secondarystoragevm">{{ $t('secondary.storage.vm') }}</a-select-option>
          </a-select>
        </a-form-item>
        <a-form-item :label="$t('storagetype')">
          <a-radio-group
            v-decorator="['storagetype', {
              initialValue: this.storageType
            }]"
            buttonStyle="solid"
            @change="selected => { this.handleStorageTypeChange(selected.target.value) }">
            <a-radio-button value="shared">
              {{ $t('label.shared') }}
            </a-radio-button>
            <a-radio-button value="local">
              {{ $t('label.local') }}
            </a-radio-button>
          </a-radio-group>
        </a-form-item>
        <a-form-item :label="$t('provisioningtype')">
          <a-radio-group
            v-decorator="['provisioningtype', {
              initialValue: this.provisioningType
            }]"
            buttonStyle="solid"
            @change="selected => { this.handleProvisioningTypeChange(selected.target.value) }">
            <a-radio-button value="thin">
              {{ $t('label.thin') }}
            </a-radio-button>
            <a-radio-button value="sparse">
              {{ $t('label.sparse') }}
            </a-radio-button>
            <a-radio-button value="fat">
              {{ $t('label.fat') }}
            </a-radio-button>
          </a-radio-group>
        </a-form-item>
        <a-form-item :label="$t('offeringtype')" v-show="!this.isSystem">
          <a-radio-group
            v-decorator="['offeringtype', {
              initialValue: this.offeringType
            }]"
            buttonStyle="solid"
            @change="selected => { this.handleComputeOfferingTypeChange(selected.target.value) }">
            <a-radio-button value="fixed">
              {{ $t('label.fixed') }}
            </a-radio-button>
            <a-radio-button value="customconstrained">
              {{ $t('label.customconstrained') }}
            </a-radio-button>
            <a-radio-button value="customunconstrained">
              {{ $t('label.customunconstrained') }}
            </a-radio-button>
          </a-radio-group>
        </a-form-item>
        <a-form-item :label="$t('cpunumber')" v-if="this.offeringType === 'fixed'">
          <a-input
            v-decorator="['cpunumber', {
              rules: [{ required: true, message: 'Please enter value' },
                      {
                        validator: (rule, value, callback) => {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback('Please enter a valid number')
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('cpunumber')"/>
        </a-form-item>
        <a-form-item :label="$t('cpuspeed')" v-if="this.offeringType !== 'customunconstrained'">
          <a-input
            v-decorator="['cpuspeed', {
              rules: [{ required: true, message: 'Please enter value' },
                      {
                        validator: (rule, value, callback) => {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback('Please enter a valid number')
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('cpuspeed')"/>
        </a-form-item>
        <a-form-item :label="$t('mincpunumber')" v-if="this.offeringType === 'customconstrained'">
          <a-input
            v-decorator="['mincpunumber', {
              rules: [{ required: true, message: 'Please enter value' },
                      {
                        validator: (rule, value, callback) => {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback('Please enter a valid number')
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('mincpunumber')"/>
        </a-form-item>
        <a-form-item :label="$t('maxcpunumber')" v-if="this.offeringType === 'customconstrained'">
          <a-input
            v-decorator="['maxcpunumber', {
              rules: [{ required: true, message: 'Please enter value' },
                      {
                        validator: (rule, value, callback) => {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback('Please enter a valid number')
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('maxcpunumber')"/>
        </a-form-item>
        <a-form-item :label="$t('memory')" v-if="this.offeringType === 'fixed'">
          <a-input
            v-decorator="['memory', {
              rules: [{ required: true, message: 'Please enter value' },
                      {
                        validator: (rule, value, callback) => {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback('Please enter a valid number')
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('memory')"/>
        </a-form-item>
        <a-form-item :label="$t('minmemory')" v-if="this.offeringType === 'customconstrained'">
          <a-input
            v-decorator="['minmemory', {
              rules: [{ required: true, message: 'Please enter value' },
                      {
                        validator: (rule, value, callback) => {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback('Please enter a valid number')
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('minmemory')"/>
        </a-form-item>
        <a-form-item :label="$t('maxmemory')" v-if="this.offeringType === 'customconstrained'">
          <a-input
            v-decorator="['maxmemory', {
              rules: [{ required: true, message: 'Please enter value' },
                      {
                        validator: (rule, value, callback) => {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback('Please enter a valid number')
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('maxmemory')"/>
        </a-form-item>
        <a-form-item :label="$t('networkrate')">
          <a-input
            v-decorator="['networkrate', {
              rules: [
                {
                  validator: (rule, value, callback) => {
                    if (value && (isNaN(value) || value <= 0)) {
                      callback('Please enter a valid number')
                    }
                    callback()
                  }
                }
              ]
            }]"
            :placeholder="this.$t('networkrate')"/>
        </a-form-item>
        <a-form-item :label="$t('qostype')">
          <a-radio-group
            v-decorator="['qostype', {
              initialValue: this.qosType
            }]"
            buttonStyle="solid"
            @change="selected => { this.handleQosTypeChange(selected.target.value) }">
            <a-radio-button value="">
              {{ $t('label.none') }}
            </a-radio-button>
            <a-radio-button value="hypervisor">
              {{ $t('label.hypervisor') }}
            </a-radio-button>
            <a-radio-button value="storage">
              {{ $t('label.storage') }}
            </a-radio-button>
          </a-radio-group>
        </a-form-item>
        <a-form-item :label="$t('diskbytesreadrate')" v-if="this.qosType === 'hypervisor'">
          <a-input
            v-decorator="['diskbytesreadrate', {
              rules: [{
                validator: (rule, value, callback) => {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback('Please enter a valid number')
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('diskbytesreadrate')"/>
        </a-form-item>
        <a-form-item :label="$t('diskbyteswriterate')" v-if="this.qosType === 'hypervisor'">
          <a-input
            v-decorator="['diskbyteswriterate', {
              rules: [{
                validator: (rule, value, callback) => {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback('Please enter a valid number')
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('diskbyteswriterate')"/>
        </a-form-item>
        <a-form-item :label="$t('diskiopsreadrate')" v-if="this.qosType === 'hypervisor'">
          <a-input
            v-decorator="['diskiopsreadrate', {
              rules: [{
                validator: (rule, value, callback) => {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback('Please enter a valid number')
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('diskiopsreadrate')"/>
        </a-form-item>
        <a-form-item :label="$t('diskiopswriterate')" v-if="this.qosType === 'hypervisor'">
          <a-input
            v-decorator="['diskiopswriterate', {
              rules: [{
                validator: (rule, value, callback) => {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback('Please enter a valid number')
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('diskiopswriterate')"/>
        </a-form-item>
        <a-form-item :label="$t('iscustomizeddiskiops')" v-if="!this.isSystem && this.qosType === 'storage'">
          <a-switch v-decorator="['iscustomizeddiskiops', {initialValue: this.isCustomizedDiskIops}]" :defaultChecked="this.isCustomizedDiskIops" @change="val => { this.isCustomizedDiskIops = val }" />
        </a-form-item>
        <a-form-item :label="$t('diskiopsmin')" v-if="this.qosType === 'storage' && !this.isCustomizedDiskIops">
          <a-input
            v-decorator="['diskiopsmin', {
              rules: [{
                validator: (rule, value, callback) => {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback('Please enter a valid number')
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('diskiopsmin')"/>
        </a-form-item>
        <a-form-item :label="$t('diskiopsmax')" v-if="this.qosType === 'storage' && !this.isCustomizedDiskIops">
          <a-input
            v-decorator="['diskiopsmax', {
              rules: [{
                validator: (rule, value, callback) => {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback('Please enter a valid number')
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('diskiopsmax')"/>
        </a-form-item>
        <a-form-item :label="$t('hypervisorsnapshotreserve')" v-if="!this.isSystem && this.qosType === 'storage'">
          <a-input
            v-decorator="['hypervisorsnapshotreserve', {
              rules: [{
                validator: (rule, value, callback) => {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback('Please enter a valid number')
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('hypervisorsnapshotreserve')"/>
        </a-form-item>
        <a-form-item :label="$t('offerha')">
          <a-switch v-decorator="['offerha', {initialValue: false}]" />
        </a-form-item>
        <a-form-item :label="$t('hosttags')" v-if="this.isAdmin()">
          <a-input
            v-decorator="['hosttags', {}]"
            :placeholder="this.$t('hosttags')"/>
        </a-form-item>
        <a-form-item :label="$t('tags')" v-if="this.isAdmin()">
          <a-select
            mode="tags"
            v-decorator="['storagetags', {}]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :loading="storageTagLoading"
            :placeholder="this.$t('tags')"
            v-if="this.isAdmin()">
            <a-select-option v-for="(opt) in this.storageTags" :key="opt.name">
              {{ opt.name || opt.description }}
            </a-select-option>
          </a-select>
        </a-form-item>
        <a-form-item :label="$t('limitcpuuse')">
          <a-switch v-decorator="['limitcpuuse', {initialValue: false}]" />
        </a-form-item>
        <a-form-item :label="$t('isvolatile')" v-if="!this.isSystem">
          <a-switch v-decorator="['isvolatile', {initialValue: false}]" />
        </a-form-item>
        <a-form-item :label="$t('deploymentplanner')" v-if="!this.isSystem && this.isAdmin()">
          <a-select
            v-decorator="['deploymentplanner', {
              initialValue: this.deploymentPlanners.length > 0 ? this.deploymentPlanners[0].name : ''
            }]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :loading="deploymentPlannerLoading"
            :placeholder="this.$t('deploymentplanner')"
            @change="val => { this.handleDeploymentPlannerChange(val) }">
            <a-select-option v-for="(opt) in this.deploymentPlanners" :key="opt.name">
              {{ opt.name || opt.description }}
            </a-select-option>
          </a-select>
        </a-form-item>
        <a-form-item :label="$t('plannermode')" v-if="this.plannerModeVisible">
          <a-radio-group
            v-decorator="['plannermode', {
              initialValue: this.plannerMode
            }]"
            buttonStyle="solid"
            @change="selected => { this.handlePlannerModeChange(selected.target.value) }">
            <a-radio-button value="">
              {{ $t('label.none') }}
            </a-radio-button>
            <a-radio-button value="strict">
              {{ $t('label.strict') }}
            </a-radio-button>
            <a-radio-button value="preferred">
              {{ $t('label.preferred') }}
            </a-radio-button>
          </a-radio-group>
        </a-form-item>
        <a-form-item :label="$t('pcidevice')" v-if="!this.isSystem">
          <a-radio-group
            v-decorator="['pcidevice', {
              initialValue: this.selectedGpu
            }]"
            buttonStyle="solid"
            @change="selected => { this.handleGpuChange(selected.target.value) }">
            <a-radio-button v-for="(opt, optIndex) in this.gpuTypes" :key="optIndex" :value="opt.value">
              {{ opt.title }}
            </a-radio-button>
          </a-radio-group>
        </a-form-item>
        <a-form-item :label="$t('vgputype')" v-if="this.vGpuVisible">
          <a-select
            v-decorator="['vgputype', {}]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :placeholder="this.$t('vgputype')">
            <a-select-option v-for="(opt, optIndex) in this.vGpuTypes" :key="optIndex">
              {{ opt }}
            </a-select-option>
          </a-select>
        </a-form-item>
        <a-form-item :label="$t('ispublic')" v-show="this.isAdmin()">
          <a-switch v-decorator="['ispublic', {initialValue: this.isPublic}]" :checked="this.isPublic" @change="val => { this.isPublic = val }" />
        </a-form-item>
        <a-form-item :label="$t('domainid')" v-if="!this.isPublic">
          <a-select
            mode="multiple"
            v-decorator="['domainid', {
              rules: [
                {
                  required: true,
                  message: 'Please select option'
                }
              ]
            }]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :loading="domainLoading"
            :placeholder="this.$t('domainid')">
            <a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex">
              {{ opt.name || opt.description }}
            </a-select-option>
          </a-select>
        </a-form-item>
        <a-form-item :label="$t('zoneid')" v-if="!this.isSystem">
          <a-select
            id="zone-selection"
            mode="multiple"
            v-decorator="['zoneid', {
              rules: [
                {
                  validator: (rule, value, callback) => {
                    if (value && value.length > 1 && value.indexOf(0) !== -1) {
                      callback('All Zones cannot be combined with any other zone')
                    }
                    callback()
                  }
                }
              ]
            }]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :loading="zoneLoading"
            :placeholder="this.$t('zoneid')">
            <a-select-option v-for="(opt, optIndex) in this.zones" :key="optIndex">
              {{ opt.name || opt.description }}
            </a-select-option>
          </a-select>
        </a-form-item>
      </a-form>
      <div :span="24" class="action-button">
        <a-button @click="closeAction">{{ this.$t('Cancel') }}</a-button>
        <a-button :loading="loading" type="primary" @click="handleSubmit">{{ this.$t('OK') }}</a-button>
      </div>
    </a-spin>
  </div>
</template>

<script>
import { api } from '@/api'

export default {
  name: 'AddServiceOffering',
  props: {
  },
  components: {
  },
  data () {
    return {
      isSystem: false,
      storageType: 'shared',
      provisioningType: 'thin',
      offeringType: 'fixed',
      qosType: '',
      isCustomizedDiskIops: false,
      isPublic: true,
      selectedDomains: [],
      domains: [],
      domainLoading: false,
      selectedZones: [],
      zones: [],
      zoneLoading: false,
      selectedDeployementPlanner: null,
      storageTags: [],
      storageTagLoading: false,
      deploymentPlanners: [],
      deploymentPlannerLoading: false,
      plannerModeVisible: false,
      plannerMode: '',
      selectedGpu: '',
      gpuTypes: [
        {
          value: '',
          title: 'None',
          vgpu: []
        },
        {
          value: 'Group of NVIDIA Corporation GK107GL [GRID K1] GPUs',
          title: 'NVIDIA GRID K1',
          vgpu: ['', 'passthrough', 'GRID K100', 'GRID K120Q', 'GRID K140Q', 'GRID K160Q', 'GRID K180Q']
        },
        {
          value: 'Group of NVIDIA Corporation GK104GL [GRID K2] GPUs',
          title: 'NVIDIA GRID K2',
          vgpu: ['', 'passthrough', 'GRID K200', 'GRID K220Q', 'GRID K240Q', 'GRID K260Q', 'GRID K280Q']
        }
      ],
      vGpuVisible: false,
      vGpuTypes: [],
      loading: false
    }
  },
  beforeCreate () {
    this.form = this.$form.createForm(this)
  },
  created () {
    this.zones = [
      {
        id: 'all',
        name: this.$t('label.all.zone')
      }
    ]
  },
  mounted () {
    if (this.$route.meta.name === 'systemoffering') {
      this.isSystem = true
    }
    this.fetchData()
  },
  methods: {
    fetchData () {
      this.fetchDomainData()
      this.fetchZoneData()
      if (this.isAdmin()) {
        this.fetchStorageTagData()
        this.fetchDeploymentPlannerData()
      }
    },
    isAdmin () {
      return ['Admin'].includes(this.$store.getters.userInfo.roletype)
    },
    arrayHasItems (array) {
      return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
    },
    fetchDomainData () {
      const params = {}
      params.listAll = true
      params.details = 'min'
      this.domainLoading = true
      api('listDomains', params).then(json => {
        const listDomains = json.listdomainsresponse.domain
        this.domains = this.domains.concat(listDomains)
      }).finally(() => {
        this.domainLoading = false
      })
    },
    fetchZoneData () {
      const params = {}
      params.listAll = true
      this.zoneLoading = true
      api('listZones', params).then(json => {
        const listZones = json.listzonesresponse.zone
        this.zones = this.zones.concat(listZones)
      }).finally(() => {
        this.zoneLoading = false
      })
    },
    fetchStorageTagData () {
      const params = {}
      params.listAll = true
      this.storageTagLoading = true
      api('listStorageTags', params).then(json => {
        const tags = json.liststoragetagsresponse.storagetag
        if (this.arrayHasItems(tags)) {
          for (var i in tags) {
            var tag = {}
            tag.id = tags[i].name
            tag.name = tags[i].name
            this.storageTags.push(tag)
          }
        }
      }).finally(() => {
        this.storageTagLoading = false
      })
    },
    fetchDeploymentPlannerData () {
      const params = {}
      params.listAll = true
      this.deploymentPlannerLoading = true
      api('listDeploymentPlanners', params).then(json => {
        const planners = json.listdeploymentplannersresponse.deploymentPlanner
        this.deploymentPlanners = this.deploymentPlanners.concat(planners)
        this.deploymentPlanners.unshift({ name: '' })
      }).finally(() => {
        this.deploymentPlannerLoading = false
      })
    },
    handleStorageTypeChange (val) {
      this.storageType = val
    },
    handleProvisioningTypeChange (val) {
      this.provisioningType = val
    },
    handleComputeOfferingTypeChange (val) {
      this.offeringType = val
    },
    handleQosTypeChange (val) {
      this.qosType = val
    },
    handleDeploymentPlannerChange (planner) {
      this.selectedDeployementPlanner = planner
      this.plannerModeVisible = false
      if (this.selectedDeployementPlanner === 'ImplicitDedicationPlanner') {
        this.plannerModeVisible = this.isAdmin()
      }
    },
    handlePlannerModeChange (val) {
      this.plannerMode = val
    },
    handleGpuChange (val) {
      this.vGpuTypes = []
      for (var i in this.gpuTypes) {
        if (this.gpuTypes[i].value === val) {
          this.vGpuTypes = this.gpuTypes[i].vgpu
          break
        }
      }
      this.vGpuVisible = true
      if (!this.arrayHasItems(this.vGpuTypes)) {
        this.vGpuVisible = false
      }
    },
    handleSubmit (e) {
      e.preventDefault()
      this.form.validateFields((err, values) => {
        if (err) {
          return
        }
        var params = {
          issystem: this.isSystem,
          name: values.name,
          displaytext: values.displaytext,
          storagetype: values.storageType,
          provisioningtype: values.provisioningtype,
          customized: values.offeringtype !== 'fixed',
          offerha: values.offerha === true,
          limitcpuuse: values.limitcpuuse === true
        }

        // custom fields (begin)
        if (values.offeringtype === 'fixed') {
          params.cpunumber = values.cpunumber
          params.cpuspeed = values.cpuspeed
          params.memory = values.memory
        } else {
          if (values.cpuspeed != null &&
              values.mincpunumber != null &&
              values.maxcpunumber != null &&
              values.minmemory != null &&
              values.maxmemory != null) {
            params.cpuspeed = values.cpuspeed
            params.mincpunumber = values.mincpunumber
            params.maxcpunumber = values.maxcpunumber
            params.minmemory = values.minmemory
            params.maxmemory = values.maxmemory
          }
        }
        // custom fields (end)

        if (values.networkRate != null && values.networkRate.length > 0) {
          params.networkrate = values.networkrate
        }

        if (values.qostype === 'storage') {
          var customIops = values.iscustomizeddiskiops === true
          params.customizediops = customIops
          if (!customIops) {
            if (values.diskiopsmin != null && values.diskiopsmin.length > 0) {
              params.miniops = values.diskiopsmin
            }
            if (values.diskiopsmax != null && values.diskiopsmax.length > 0) {
              params.maxiops = values.diskiopsmax
            }
            if (values.hypervisorsnapshotreserve !== undefined &&
              values.hypervisorsnapshotreserve != null && values.hypervisorsnapshotreserve.length > 0) {
              params.hypervisorsnapshotreserve = values.hypervisorsnapshotreserve
            }
          }
        } else if (values.qostype === 'hypervisor') {
          if (values.diskbytesreadrate != null && values.diskbytesreadrate.length > 0) {
            params.bytesreadrate = values.diskbytesreadrate
          }
          if (values.diskbyteswriterate != null && values.diskbyteswriterate.length > 0) {
            params.byteswriterate = values.diskbyteswriterate
          }
          if (values.diskiopsreadrate != null && values.diskiopsreadrate.length > 0) {
            params.iopsreadrate = values.diskiopsreadrate
          }
          if (values.diskiopswriterate != null && values.diskiopswriterate.length > 0) {
            params.iopswriterate = values.diskiopswriterate
          }
        }
        if (values.storagetags != null && values.storagetags.length > 0) {
          var tags = values.storagetags.join(',')
          params.tags = tags
        }
        if (values.hosttags != null && values.hosttags.length > 0) {
          params.hosttags = values.hosttags
        }
        if ('deploymentplanner' in values &&
          values.deploymentplanner !== undefined &&
          values.deploymentplanner != null && values.deploymentplanner.length > 0) {
          params.deploymentplanner = values.deploymentplanner
        }
        if ('deploymentplanner' in values &&
          values.deploymentplanner !== undefined &&
          values.deploymentplanner === 'ImplicitDedicationPlanner' &&
          values.plannermode !== undefined &&
          values.plannermode !== '') {
          params['serviceofferingdetails[0].key'] = 'ImplicitDedicationMode'
          params['serviceofferingdetails[0].value'] = values.plannermode
        }
        if ('pcidevice' in values &&
          values.pcidevice !== undefined && values.pcidevice !== '') {
          params['serviceofferingdetails[1].key'] = 'pciDevice'
          params['serviceofferingdetails[1].value'] = values.pcidevice
        }
        if ('vgputype' in values &&
          this.vGpuTypes != null && this.vGpuTypes !== undefined &&
          values.vgputype > this.vGpuTypes.length) {
          params['serviceofferingdetails[2].key'] = 'vgpuType'
          params['serviceofferingdetails[2].value'] = this.vGpuTypes[values.vgputype]
        }
        if ('isvolatile' in values && values.isvolatile !== undefined) {
          params.isvolatile = values.isvolatile === true
        }
        if (values.ispublic !== true) {
          var domainIndexes = values.domainid
          var domainId = null
          if (domainIndexes && domainIndexes.length > 0) {
            var domainIds = []
            for (var i = 0; i < domainIndexes.length; i++) {
              domainIds = domainIds.concat(this.domains[domainIndexes[i]].id)
            }
            domainId = domainIds.join(',')
          }
          if (domainId) {
            params.domainid = domainId
          }
        }
        var zoneIndexes = values.zoneid
        var zoneId = null
        if (zoneIndexes && zoneIndexes.length > 0) {
          var zoneIds = []
          for (var j = 0; j < zoneIndexes.length; j++) {
            zoneIds = zoneIds.concat(this.zones[zoneIndexes[j]].id)
          }
          zoneId = zoneIds.join(',')
        }
        if (zoneId) {
          params.zoneid = zoneId
        }
        api('createServiceOffering', params).then(json => {
          this.$notification.success({
            message: this.isSystem ? this.$t('message.add.system.service.offering') : this.$t('message.add.service.offering'),
            description: this.isSystem ? this.$t('message.add.system.service.offering') : this.$t('message.add.service.offering')
          })
        }).catch(error => {
          this.$notification.error({
            message: 'Request Failed',
            description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
          })
        }).finally(() => {
          this.loading = false
          this.closeAction()
        })
      })
    },
    closeAction () {
      this.$emit('close-action')
    }
  }
}
</script>

<style scoped lang="scss">
  .action-button {
    text-align: right;

    button {
      margin-right: 5px;
    }
  }
</style>

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

<template>
  <div class="form-layout">
    <a-spin :spinning="loading">
      <a-form
        :form="form"
        @submit="handleSubmit"
        layout="vertical">
        <a-form-item :label="$t('name')">
          <a-input
            v-decorator="['name', {
              rules: [{ required: true, message: 'Please enter name' }]
            }]"
            :placeholder="this.$t('Name')"/>
        </a-form-item>
        <a-form-item :label="$t('displaytext')">
          <a-input
            v-decorator="['displaytext', {
              rules: [{ required: true, message: 'Please enter description' }]
            }]"
            :placeholder="this.$t('displaytext  ')"/>
        </a-form-item>
        <a-form-item :label="$t('storagetype')">
          <a-radio-group
            v-decorator="['storagetype', {
              initialValue: this.storageType
            }]"
            buttonStyle="solid"
            @change="selected => { this.handleStorageTypeChange(selected.target.value) }">
            <a-radio-button value="shared">
              {{ $t('label.shared') }}
            </a-radio-button>
            <a-radio-button value="local">
              {{ $t('label.local') }}
            </a-radio-button>
          </a-radio-group>
        </a-form-item>
        <a-form-item :label="$t('provisioningtype')">
          <a-radio-group
            v-decorator="['provisioningtype', {
              initialValue: this.provisioningType
            }]"
            buttonStyle="solid"
            @change="selected => { this.handleProvisioningTypeChange(selected.target.value) }">
            <a-radio-button value="thin">
              {{ $t('label.thin') }}
            </a-radio-button>
            <a-radio-button value="sparse">
              {{ $t('label.sparse') }}
            </a-radio-button>
            <a-radio-button value="fat">
              {{ $t('label.fat') }}
            </a-radio-button>
          </a-radio-group>
        </a-form-item>
        <a-form-item :label="$t('offeringtype')">
          <a-radio-group
            v-decorator="['offeringtype', {
              initialValue: this.offeringType
            }]"
            buttonStyle="solid"
            @change="selected => { this.handleComputeOfferingTypeChange(selected.target.value) }">
            <a-radio-button value="fixed">
              {{ $t('label.fixed') }}
            </a-radio-button>
            <a-radio-button value="customconstrained">
              {{ $t('label.customconstrained') }}
            </a-radio-button>
            <a-radio-button value="customunconstrained">
              {{ $t('label.customunconstrained') }}
            </a-radio-button>
          </a-radio-group>
        </a-form-item>
        <a-form-item :label="$t('cpunumber')" v-if="this.offeringType === 'fixed'">
          <a-input
            v-decorator="['cpunumber', {
              rules: [{ required: true, message: 'Please enter value' },
                      {
                        validator: (rule, value, callback) => {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback('Please enter a valid number')
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('cpunumber')"/>
        </a-form-item>
        <a-form-item :label="$t('cpuspeed')" v-if="this.offeringType !== 'customunconstrained'">
          <a-input
            v-decorator="['cpuspeed', {
              rules: [{ required: true, message: 'Please enter value' },
                      {
                        validator: (rule, value, callback) => {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback('Please enter a valid number')
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('cpuspeed')"/>
        </a-form-item>
        <a-form-item :label="$t('mincpunumber')" v-if="this.offeringType === 'customconstrained'">
          <a-input
            v-decorator="['mincpunumber', {
              rules: [{ required: true, message: 'Please enter value' },
                      {
                        validator: (rule, value, callback) => {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback('Please enter a valid number')
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('mincpunumber')"/>
        </a-form-item>
        <a-form-item :label="$t('maxcpunumber')" v-if="this.offeringType === 'customconstrained'">
          <a-input
            v-decorator="['maxcpunumber', {
              rules: [{ required: true, message: 'Please enter value' },
                      {
                        validator: (rule, value, callback) => {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback('Please enter a valid number')
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('maxcpunumber')"/>
        </a-form-item>
        <a-form-item :label="$t('memory')" v-if="this.offeringType === 'fixed'">
          <a-input
            v-decorator="['memory', {
              rules: [{ required: true, message: 'Please enter value' },
                      {
                        validator: (rule, value, callback) => {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback('Please enter a valid number')
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('memory')"/>
        </a-form-item>
        <a-form-item :label="$t('minmemory')" v-if="this.offeringType === 'customconstrained'">
          <a-input
            v-decorator="['minmemory', {
              rules: [{ required: true, message: 'Please enter value' },
                      {
                        validator: (rule, value, callback) => {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback('Please enter a valid number')
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('minmemory')"/>
        </a-form-item>
        <a-form-item :label="$t('maxmemory')" v-if="this.offeringType === 'customconstrained'">
          <a-input
            v-decorator="['maxmemory', {
              rules: [{ required: true, message: 'Please enter value' },
                      {
                        validator: (rule, value, callback) => {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback('Please enter a valid number')
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('maxmemory')"/>
        </a-form-item>
        <a-form-item :label="$t('qostype')">
          <a-radio-group
            v-decorator="['qostype', {
              initialValue: this.qosType
            }]"
            buttonStyle="solid"
            @change="selected => { this.handleQosTypeChange(selected.target.value) }">
            <a-radio-button value="">
              {{ $t('label.none') }}
            </a-radio-button>
            <a-radio-button value="hypervisor">
              {{ $t('label.hypervisor') }}
            </a-radio-button>
            <a-radio-button value="storage">
              {{ $t('label.storage') }}
            </a-radio-button>
          </a-radio-group>
        </a-form-item>
        <a-form-item :label="$t('diskbytesreadrate')" v-if="this.qosType === 'hypervisor'">
          <a-input
            v-decorator="['diskbytesreadrate', {
              rules: [{
                validator: (rule, value, callback) => {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback('Please enter a valid number')
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('diskbytesreadrate')"/>
        </a-form-item>
        <a-form-item :label="$t('diskbyteswriterate')" v-if="this.qosType === 'hypervisor'">
          <a-input
            v-decorator="['diskbyteswriterate', {
              rules: [{
                validator: (rule, value, callback) => {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback('Please enter a valid number')
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('diskbyteswriterate')"/>
        </a-form-item>
        <a-form-item :label="$t('diskiopsreadrate')" v-if="this.qosType === 'hypervisor'">
          <a-input
            v-decorator="['diskiopsreadrate', {
              rules: [{
                validator: (rule, value, callback) => {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback('Please enter a valid number')
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('diskiopsreadrate')"/>
        </a-form-item>
        <a-form-item :label="$t('diskiopswriterate')" v-if="this.qosType === 'hypervisor'">
          <a-input
            v-decorator="['diskiopswriterate', {
              rules: [{
                validator: (rule, value, callback) => {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback('Please enter a valid number')
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('diskiopswriterate')"/>
        </a-form-item>
        <a-form-item :label="$t('iscustomizeddiskiops')" v-if="this.qosType === 'storage'">
          <a-switch v-decorator="['iscustomizeddiskiops', {initialValue: this.isCustomizedDiskIops}]" :defaultChecked="this.isCustomizedDiskIops" @change="val => { this.isCustomizedDiskIops = val }" />
        </a-form-item>
        <a-form-item :label="$t('diskiopsmin')" v-if="this.qosType === 'storage' && !this.isCustomizedDiskIops">
          <a-input
            v-decorator="['diskiopsmin', {
              rules: [{
                validator: (rule, value, callback) => {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback('Please enter a valid number')
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('diskiopsmin')"/>
        </a-form-item>
        <a-form-item :label="$t('diskiopsmax')" v-if="this.qosType === 'storage' && !this.isCustomizedDiskIops">
          <a-input
            v-decorator="['diskiopsmax', {
              rules: [{
                validator: (rule, value, callback) => {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback('Please enter a valid number')
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('diskiopsmax')"/>
        </a-form-item>
        <a-form-item :label="$t('hypervisorsnapshotreserve')" v-if="this.qosType === 'storage'">
          <a-input
            v-decorator="['hypervisorsnapshotreserve', {
              rules: [{
                validator: (rule, value, callback) => {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback('Please enter a valid number')
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('hypervisorsnapshotreserve')"/>
        </a-form-item>
        <a-form-item :label="$t('offerha')">
          <a-switch v-decorator="['offerha', {initialValue: false}]" />
        </a-form-item>
        <a-form-item :label="$t('hosttags')" v-if="this.isAdmin()">
          <a-input
            v-decorator="['hosttags', {}]"
            :placeholder="this.$t('hosttags')"/>
        </a-form-item>
        <a-form-item :label="$t('tags')" v-if="this.isAdmin()">
          <a-select
            mode="tags"
            v-decorator="['storagetags', {}]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :loading="storageTagLoading"
            :placeholder="this.$t('tags')"
            v-if="this.isAdmin()">
            <a-select-option v-for="(opt) in this.storageTags" :key="opt.name">
              {{ opt.name || opt.description }}
            </a-select-option>
          </a-select>
        </a-form-item>
        <a-form-item :label="$t('limitcpuuse')">
          <a-switch v-decorator="['limitcpuuse', {initialValue: false}]" />
        </a-form-item>
        <a-form-item :label="$t('isvolatile')">
          <a-switch v-decorator="['isvolatile', {initialValue: false}]" />
        </a-form-item>
        <a-form-item :label="$t('deploymentplanner')" v-if="this.isAdmin()">
          <a-select
            v-decorator="['deploymentplanner', {
              initialValue: this.deploymentPlanners.length > 0 ? this.deploymentPlanners[0].name : ''
            }]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :loading="deploymentPlannerLoading"
            :placeholder="this.$t('deploymentplanner')"
            @change="val => { this.handleDeploymentPlannerChange(val) }">
            <a-select-option v-for="(opt) in this.deploymentPlanners" :key="opt.name">
              {{ opt.name || opt.description }}
            </a-select-option>
          </a-select>
        </a-form-item>
        <a-form-item :label="$t('plannermode')" v-if="this.plannerModeVisible">
          <a-radio-group
            v-decorator="['plannermode', {
              initialValue: this.plannerMode
            }]"
            buttonStyle="solid"
            @change="selected => { this.handlePlannerModeChange(selected.target.value) }">
            <a-radio-button value="">
              {{ $t('label.none') }}
            </a-radio-button>
            <a-radio-button value="strict">
              {{ $t('label.strict') }}
            </a-radio-button>
            <a-radio-button value="preferred">
              {{ $t('label.preferred') }}
            </a-radio-button>
          </a-radio-group>
        </a-form-item>
        <a-form-item :label="$t('pcidevice')">
          <a-radio-group
            v-decorator="['pcidevice', {
              initialValue: this.selectedGpu
            }]"
            buttonStyle="solid"
            @change="selected => { this.handleGpuChange(selected.target.value) }">
            <a-radio-button v-for="(opt, optIndex) in this.gpuTypes" :key="optIndex" :value="opt.value">
              {{ opt.title }}
            </a-radio-button>
          </a-radio-group>
        </a-form-item>
        <a-form-item :label="$t('vgputype')" v-if="this.vGpuVisible">
          <a-select
            v-decorator="['vgputype', {}]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :placeholder="this.$t('vgputype')">
            <a-select-option v-for="(opt, optIndex) in this.vGpuTypes" :key="optIndex">
              {{ opt }}
            </a-select-option>
          </a-select>
        </a-form-item>
        <a-form-item :label="$t('ispublic')" v-show="this.isAdmin()">
          <a-switch v-decorator="['ispublic', {initialValue: this.isPublic}]" :checked="this.isPublic" @change="val => { this.isPublic = val }" />
        </a-form-item>
        <a-form-item :label="$t('domainid')" v-if="!this.isPublic">
          <a-select
            mode="multiple"
            v-decorator="['domainid', {
              rules: [
                {
                  required: true,
                  message: 'Please select option'
                }
              ]
            }]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :loading="domainLoading"
            :placeholder="this.$t('domainid')">
            <a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex">
              {{ opt.name || opt.description }}
            </a-select-option>
          </a-select>
        </a-form-item>
        <a-form-item :label="$t('zoneid')">
          <a-select
            id="zone-selection"
            mode="multiple"
            v-decorator="['zoneid', {
              rules: [
                {
                  validator: (rule, value, callback) => {
                    if (value && value.length > 1 && value.indexOf(0) !== -1) {
                      callback('All Zones cannot be combined with any other zone')
                    }
                    callback()
                  }
                }
              ]
            }]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :loading="zoneLoading"
            :placeholder="this.$t('zoneid')">
            <a-select-option v-for="(opt, optIndex) in this.zones" :key="optIndex">
              {{ opt.name || opt.description }}
            </a-select-option>
          </a-select>
        </a-form-item>
      </a-form>
      <div :span="24" class="action-button">
        <a-button @click="closeAction">{{ this.$t('Cancel') }}</a-button>
        <a-button :loading="loading" type="primary" @click="handleSubmit">{{ this.$t('OK') }}</a-button>
      </div>
    </a-spin>
  </div>
</template>

<script>
import { api } from '@/api'

export default {
  name: 'AddServiceOffering',
  props: {
  },
  components: {
  },
  data () {
    return {
      storageType: 'shared',
      provisioningType: 'thin',
      offeringType: 'fixed',
      qosType: '',
      isCustomizedDiskIops: false,
      isPublic: true,
      selectedDomains: [],
      domains: [],
      domainLoading: false,
      selectedZones: [],
      zones: [],
      zoneLoading: false,
      selectedDeployementPlanner: null,
      storageTags: [],
      storageTagLoading: false,
      deploymentPlanners: [],
      deploymentPlannerLoading: false,
      plannerModeVisible: false,
      plannerMode: '',
      selectedGpu: '',
      gpuTypes: [
        {
          value: '',
          title: 'None',
          vgpu: []
        },
        {
          value: 'Group of NVIDIA Corporation GK107GL [GRID K1] GPUs',
          title: 'NVIDIA GRID K1',
          vgpu: ['', 'passthrough', 'GRID K100', 'GRID K120Q', 'GRID K140Q', 'GRID K160Q', 'GRID K180Q']
        },
        {
          value: 'Group of NVIDIA Corporation GK104GL [GRID K2] GPUs',
          title: 'NVIDIA GRID K2',
          vgpu: ['', 'passthrough', 'GRID K200', 'GRID K220Q', 'GRID K240Q', 'GRID K260Q', 'GRID K280Q']
        }
      ],
      vGpuVisible: false,
      vGpuTypes: [],
      loading: false
    }
  },
  beforeCreate () {
    this.form = this.$form.createForm(this)
  },
  created () {
    this.zones = [
      {
        id: 'all',
        name: this.$t('label.all.zone')
      }
    ]
  },
  mounted () {
    this.fetchData()
  },
  methods: {
    fetchData () {
      this.fetchDomainData()
      this.fetchZoneData()
      if (this.isAdmin()) {
        this.fetchStorageTagData()
        this.fetchDeploymentPlannerData()
      }
    },
    isAdmin () {
      return ['Admin'].includes(this.$store.getters.userInfo.roletype)
    },
    arrayHasItems (array) {
      return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
    },
    fetchDomainData () {
      const params = {}
      params.listAll = true
      params.details = 'min'
      this.domainLoading = true
      api('listDomains', params).then(json => {
        const listDomains = json.listdomainsresponse.domain
        this.domains = this.domains.concat(listDomains)
      }).finally(() => {
        this.domainLoading = false
      })
    },
    fetchZoneData () {
      const params = {}
      params.listAll = true
      this.zoneLoading = true
      api('listZones', params).then(json => {
        const listZones = json.listzonesresponse.zone
        this.zones = this.zones.concat(listZones)
      }).finally(() => {
        this.zoneLoading = false
      })
    },
    fetchStorageTagData () {
      const params = {}
      params.listAll = true
      this.storageTagLoading = true
      api('listStorageTags', params).then(json => {
        const tags = json.liststoragetagsresponse.storagetag
        if (this.arrayHasItems(tags)) {
          for (var i in tags) {
            var tag = {}
            tag.id = tags[i].name
            tag.name = tags[i].name
            this.storageTags.push(tag)
          }
        }
      }).finally(() => {
        this.storageTagLoading = false
      })
    },
    fetchDeploymentPlannerData () {
      const params = {}
      params.listAll = true
      this.deploymentPlannerLoading = true
      api('listDeploymentPlanners', params).then(json => {
        const planners = json.listdeploymentplannersresponse.deploymentPlanner
        this.deploymentPlanners = this.deploymentPlanners.concat(planners)
        this.deploymentPlanners.unshift({ name: '' })
      }).finally(() => {
        this.deploymentPlannerLoading = false
      })
    },
    handleStorageTypeChange (val) {
      this.storageType = val
    },
    handleProvisioningTypeChange (val) {
      this.provisioningType = val
    },
    handleComputeOfferingTypeChange (val) {
      this.offeringType = val
    },
    handleQosTypeChange (val) {
      this.qosType = val
    },
    handleDeploymentPlannerChange (planner) {
      this.selectedDeployementPlanner = planner
      this.plannerModeVisible = false
      if (this.selectedDeployementPlanner === 'ImplicitDedicationPlanner') {
        this.plannerModeVisible = this.isAdmin()
      }
    },
    handlePlannerModeChange (val) {
      this.plannerMode = val
    },
    handleGpuChange (val) {
      this.vGpuTypes = []
      for (var i in this.gpuTypes) {
        if (this.gpuTypes[i].value === val) {
          this.vGpuTypes = this.gpuTypes[i].vgpu
          break
        }
      }
      this.vGpuVisible = true
      if (!this.arrayHasItems(this.vGpuTypes)) {
        this.vGpuVisible = false
      }
    },
    handleSubmit (e) {
      e.preventDefault()
      this.form.validateFields((err, values) => {
        if (err) {
          return
        }
        var params = {
          issystem: false,
          name: values.name,
          displaytext: values.displaytext,
          storagetype: values.storageType,
          provisioningtype: values.provisioningtype,
          customized: values.offeringtype !== 'fixed',
          offerha: values.offerha === true,
          limitcpuuse: values.limitcpuuse === true,
          isvolatile: values.isvolatile === true
        }

        // custom fields (begin)
        if (values.offeringtype === 'fixed') {
          params.cpunumber = values.cpunumber
          params.cpuspeed = values.cpuspeed
          params.memory = values.memory
        } else {
          if (values.cpuspeed != null &&
              values.mincpunumber != null &&
              values.maxcpunumber != null &&
              values.minmemory != null &&
              values.maxmemory != null) {
            params.cpuspeed = values.cpuspeed
            params.mincpunumber = values.mincpunumber
            params.maxcpunumber = values.maxcpunumber
            params.minmemory = values.minmemory
            params.maxmemory = values.maxmemory
          }
        }
        // custom fields (end)

        if (values.qostype === 'storage') {
          var customIops = values.iscustomizeddiskiops === true
          params.customizediops = customIops
          if (!customIops) {
            if (values.diskiopsmin != null && values.diskiopsmin.length > 0) {
              params.miniops = values.diskiopsmin
            }
            if (values.diskiopsmax != null && values.diskiopsmax.length > 0) {
              params.maxiops = values.diskiopsmax
            }
            if (values.hypervisorsnapshotreserve != null && values.hypervisorsnapshotreserve.length > 0) {
              params.hypervisorsnapshotreserve = values.hypervisorsnapshotreserve
            }
          }
        } else if (values.qostype === 'hypervisor') {
          if (values.diskbytesreadrate != null && values.diskbytesreadrate.length > 0) {
            params.bytesreadrate = values.diskbytesreadrate
          }
          if (values.diskbyteswriterate != null && values.diskbyteswriterate.length > 0) {
            params.byteswriterate = values.diskbyteswriterate
          }
          if (values.diskiopsreadrate != null && values.diskiopsreadrate.length > 0) {
            params.iopsreadrate = values.diskiopsreadrate
          }
          if (values.diskiopswriterate != null && values.diskiopswriterate.length > 0) {
            params.iopswriterate = values.diskiopswriterate
          }
        }
        if (values.storagetags != null && values.storagetags.length > 0) {
          var tags = values.storagetags.join(',')
          params.tags = tags
        }
        if (values.hosttags != null && values.hosttags.length > 0) {
          params.hosttags = values.hosttags
        }
        if (values.deploymentplanner != null && values.deploymentplanner.length > 0) {
          params.deploymentplanner = values.deploymentplanner
        }
        if (values.deploymentplanner === 'ImplicitDedicationPlanner' && values.plannermode !== '') {
          params['serviceofferingdetails[0].key'] = 'ImplicitDedicationMode'
          params['serviceofferingdetails[0].value'] = values.plannermode
        }
        if (values.pcidevice !== '') {
          params['serviceofferingdetails[1].key'] = 'pciDevice'
          params['serviceofferingdetails[1].value'] = values.pcidevice
        }
        if ('vgputype' in values &&
          this.vGpuTypes != null && this.vGpuTypes !== undefined &&
          values.vgputype > this.vGpuTypes.length) {
          params['serviceofferingdetails[2].key'] = 'vgpuType'
          params['serviceofferingdetails[2].value'] = this.vGpuTypes[values.vgputype]
        }
        if (values.ispublic !== true) {
          var domainIndexes = values.domainid
          var domainId = null
          if (domainIndexes && domainIndexes.length > 0) {
            var domainIds = []
            for (var i = 0; i < domainIndexes.length; i++) {
              domainIds = domainIds.concat(this.domains[domainIndexes[i]].id)
            }
            domainId = domainIds.join(',')
          }
          if (domainId) {
            params.domainid = domainId
          }
        }
        var zoneIndexes = values.zoneid
        var zoneId = null
        if (zoneIndexes && zoneIndexes.length > 0) {
          var zoneIds = []
          for (var j = 0; j < zoneIndexes.length; j++) {
            zoneIds = zoneIds.concat(this.zones[zoneIndexes[j]].id)
          }
          zoneId = zoneIds.join(',')
        }
        if (zoneId) {
          params.zoneid = zoneId
        }
        api('createServiceOffering', params).then(json => {
          this.$notification.success({
            message: this.$t('message.add.service.offering'),
            description: this.$t('message.add.service.offering')
          })
        }).catch(error => {
          this.$notification.error({
            message: 'Request Failed',
            description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
          })
        }).finally(() => {
          this.loading = false
          this.closeAction()
        })
      })
    },
    closeAction () {
      this.$emit('close-action')
    }
  }
}
</script>

<style scoped lang="scss">
  .action-button {
    text-align: right;

    button {
      margin-right: 5px;
    }
  }
</style>


Add rootdisk size on compute offering (#770)

* Add rootdisk size oncompute offering

* Change "leftover" apiParams.provisioningtype to apiParams.rootdisk

Signed-off-by: Rohit Yadav <902b9eb5dd62819678163bd5e00fbf0cbc7c264b@shapeblue.com>

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

<template>

            {{ $t('label.name') }}

            {{ $t('label.displaytext') }}

            {{ $t('label.systemvmtype') }}

           {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :placeholder="this.$t('label.systemvmtype')">
            {{ $t('label.domain.router') }}
            {{ $t('label.console.proxy') }}
            {{ $t('label.secondary.storage.vm') }}

            {{ $t('label.storagetype') }}

           { this.handleStorageTypeChange(selected.target.value) }">

              {{ $t('label.shared') }}

              {{ $t('label.local') }}

            {{ $t('label.provisioningtype') }}

           { this.handleProvisioningTypeChange(selected.target.value) }">

              {{ $t('label.provisioningtype.thin') }}

              {{ $t('label.provisioningtype.sparse') }}

              {{ $t('label.provisioningtype.fat') }}

            {{ $t('label.cachemode') }}

           { this.handleCacheModeChange(selected.target.value) }">

              {{ $t('label.nodiskcache') }}

              {{ $t('label.writeback') }}

              {{ $t('label.writethrough') }}

           { this.handleComputeOfferingTypeChange(selected.target.value) }">

              {{ $t('label.fixed') }}

              {{ $t('label.customconstrained') }}

              {{ $t('label.customunconstrained') }}

            {{ $t('label.cpunumber') }}

           {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback(this.$t('message.error.number'))
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('label.cpunumber')"/>

            {{ $t('label.cpuspeed') }}

           {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback(this.$t('message.error.number'))
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('label.cpuspeed')"/>

            {{ $t('label.mincpunumber') }}

           {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback(this.$t('message.error.number'))
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('label.mincpunumber')"/>

            {{ $t('label.maxcpunumber') }}

           {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback(this.$t('message.error.number'))
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('label.maxcpunumber')"/>

            {{ $t('label.memory.mb') }}

           {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback(this.$t('message.error.number'))
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('label.memory')"/>

            {{ $t('label.minmemory') }}

           {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback(this.$t('message.error.number'))
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('label.minmemory')"/>

            {{ $t('label.maxmemory') }}

           {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback(this.$t('message.error.number'))
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('label.maxmemory')"/>

            {{ $t('label.networkrate') }}

           {
                    if (value && (isNaN(value) || value <= 0)) {
                      callback(this.$t('message.error.number'))
                    }
                    callback()
                  }
                }
              ]
            }]"
            :placeholder="this.$t('label.networkrate')"/>

            {{ $t('label.root.disk.size') }}

           {
                    if (value && (isNaN(value) || value <= 0)) {
                      callback(this.$t('message.error.number'))
                    }
                    callback()
                  }
                }
              ]
            }]"
            :placeholder="this.$t('label.root.disk.size')"/>

           { this.handleQosTypeChange(selected.target.value) }">

              {{ $t('label.none') }}

              {{ $t('label.hypervisor') }}

              {{ $t('label.storage') }}

            {{ $t('label.diskbytesreadrate') }}

           {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback(this.$t('message.error.number'))
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('label.diskbytesreadrate')"/>

            {{ $t('label.diskbyteswriterate') }}

           {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback(this.$t('message.error.number'))
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('label.diskbyteswriterate')"/>

            {{ $t('label.diskiopsreadrate') }}

           {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback(this.$t('message.error.number'))
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('label.diskiopsreadrate')"/>

            {{ $t('label.diskiopswriterate') }}

           {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback(this.$t('message.error.number'))
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('label.diskiopswriterate')"/>

            {{ $t('label.iscustomizeddiskiops') }}

           { this.isCustomizedDiskIops = val }" />

            {{ $t('label.diskiopsmin') }}

           {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback(this.$t('message.error.number'))
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('label.diskiopsmin')"/>

            {{ $t('label.diskiopsmax') }}

           {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback(this.$t('message.error.number'))
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('label.diskiopsmax')"/>

            {{ $t('label.hypervisorsnapshotreserve') }}

           {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback(this.$t('message.error.number'))
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('label.hypervisorsnapshotreserve')"/>

            {{ $t('label.offerha') }}

            {{ $t('label.hosttags') }}

            {{ $t('label.storagetags') }}

           {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :loading="storageTagLoading"
            :placeholder="$t('label.storagetags')"
            v-if="this.isAdmin()">

              {{ opt }}

            {{ $t('label.limitcpuuse') }}

            {{ $t('label.isvolatile') }}

            {{ $t('label.deploymentplanner') }}

           0 ? this.deploymentPlanners[0].name : ''
            }]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :loading="deploymentPlannerLoading"
            :placeholder="this.$t('label.deploymentplanner')"
            @change="val => { this.handleDeploymentPlannerChange(val) }">

              {{ opt.name || opt.description }}

           { this.handlePlannerModeChange(selected.target.value) }">

              {{ $t('label.none') }}

              {{ $t('label.strict') }}

              {{ $t('label.preferred') }}

           { this.handleGpuChange(selected.target.value) }">

              {{ opt.title }}

           {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :placeholder="this.$t('label.vgputype')">

              {{ opt }}

           { this.isPublic = val }" />

            {{ $t('label.domain') }}

           {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :loading="domainLoading"
            :placeholder="this.$t('label.domainid')">

              {{ opt.name || opt.description }}

            {{ $t('label.zoneid') }}

           {
                    if (value && value.length > 1 && value.indexOf(0) !== -1) {
                      callback(this.$t('message.error.zone.combined'))
                    }
                    callback()
                  }
                }
              ]
            }]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            @select="val => fetchvSphereStoragePolicies(val)"
            :loading="zoneLoading"
            :placeholder="this.$t('label.zoneid')">

              {{ opt.name || opt.description }}

            {{ $t('label.vmware.storage.policy') }}

              {{ policy.name || policy.id }}

        {{ this.$t('label.cancel') }}
        {{ this.$t('label.ok') }}

</template>

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

<template>

            {{ $t('label.name') }}

            {{ $t('label.displaytext') }}

            {{ $t('label.systemvmtype') }}

           {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :placeholder="this.$t('label.systemvmtype')">
            {{ $t('label.domain.router') }}
            {{ $t('label.console.proxy') }}
            {{ $t('label.secondary.storage.vm') }}

            {{ $t('label.storagetype') }}

           { this.handleStorageTypeChange(selected.target.value) }">

              {{ $t('label.shared') }}

              {{ $t('label.local') }}

            {{ $t('label.provisioningtype') }}

           { this.handleProvisioningTypeChange(selected.target.value) }">

              {{ $t('label.provisioningtype.thin') }}

              {{ $t('label.provisioningtype.sparse') }}

              {{ $t('label.provisioningtype.fat') }}

            {{ $t('label.cachemode') }}

           { this.handleCacheModeChange(selected.target.value) }">

              {{ $t('label.nodiskcache') }}

              {{ $t('label.writeback') }}

              {{ $t('label.writethrough') }}

           { this.handleComputeOfferingTypeChange(selected.target.value) }">

              {{ $t('label.fixed') }}

              {{ $t('label.customconstrained') }}

              {{ $t('label.customunconstrained') }}

            {{ $t('label.cpunumber') }}

           {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback(this.$t('message.error.number'))
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('label.cpunumber')"/>

            {{ $t('label.cpuspeed') }}

           {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback(this.$t('message.error.number'))
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('label.cpuspeed')"/>

            {{ $t('label.mincpunumber') }}

           {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback(this.$t('message.error.number'))
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('label.mincpunumber')"/>

            {{ $t('label.maxcpunumber') }}

           {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback(this.$t('message.error.number'))
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('label.maxcpunumber')"/>

            {{ $t('label.memory.mb') }}

           {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback(this.$t('message.error.number'))
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('label.memory')"/>

            {{ $t('label.minmemory') }}

           {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback(this.$t('message.error.number'))
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('label.minmemory')"/>

            {{ $t('label.maxmemory') }}

           {
                          if (value && (isNaN(value) || value <= 0)) {
                            callback(this.$t('message.error.number'))
                          }
                          callback()
                        }
                      }
              ]
            }]"
            :placeholder="this.$t('label.maxmemory')"/>

            {{ $t('label.networkrate') }}

           {
                    if (value && (isNaN(value) || value <= 0)) {
                      callback(this.$t('message.error.number'))
                    }
                    callback()
                  }
                }
              ]
            }]"
            :placeholder="this.$t('label.networkrate')"/>

           { this.handleQosTypeChange(selected.target.value) }">

              {{ $t('label.none') }}

              {{ $t('label.hypervisor') }}

              {{ $t('label.storage') }}

            {{ $t('label.diskbytesreadrate') }}

           {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback(this.$t('message.error.number'))
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('label.diskbytesreadrate')"/>

            {{ $t('label.diskbyteswriterate') }}

           {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback(this.$t('message.error.number'))
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('label.diskbyteswriterate')"/>

            {{ $t('label.diskiopsreadrate') }}

           {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback(this.$t('message.error.number'))
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('label.diskiopsreadrate')"/>

            {{ $t('label.diskiopswriterate') }}

           {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback(this.$t('message.error.number'))
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('label.diskiopswriterate')"/>

            {{ $t('label.iscustomizeddiskiops') }}

           { this.isCustomizedDiskIops = val }" />

            {{ $t('label.diskiopsmin') }}

           {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback(this.$t('message.error.number'))
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('label.diskiopsmin')"/>

            {{ $t('label.diskiopsmax') }}

           {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback(this.$t('message.error.number'))
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('label.diskiopsmax')"/>

            {{ $t('label.hypervisorsnapshotreserve') }}

           {
                  if (value && (isNaN(value) || value <= 0)) {
                    callback(this.$t('message.error.number'))
                  }
                  callback()
                }
              }]
            }]"
            :placeholder="this.$t('label.hypervisorsnapshotreserve')"/>

            {{ $t('label.offerha') }}

            {{ $t('label.hosttags') }}

            {{ $t('label.storagetags') }}

           {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :loading="storageTagLoading"
            :placeholder="$t('label.storagetags')"
            v-if="this.isAdmin()">

              {{ opt }}

            {{ $t('label.limitcpuuse') }}

            {{ $t('label.isvolatile') }}

            {{ $t('label.deploymentplanner') }}

           0 ? this.deploymentPlanners[0].name : ''
            }]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :loading="deploymentPlannerLoading"
            :placeholder="this.$t('label.deploymentplanner')"
            @change="val => { this.handleDeploymentPlannerChange(val) }">

              {{ opt.name || opt.description }}

           { this.handlePlannerModeChange(selected.target.value) }">

              {{ $t('label.none') }}

              {{ $t('label.strict') }}

              {{ $t('label.preferred') }}

           { this.handleGpuChange(selected.target.value) }">

              {{ opt.title }}

           {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :placeholder="this.$t('label.vgputype')">

              {{ opt }}

           { this.isPublic = val }" />

            {{ $t('label.domain') }}

           {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            :loading="domainLoading"
            :placeholder="this.$t('label.domainid')">

              {{ opt.name || opt.description }}

            {{ $t('label.zoneid') }}

           {
                    if (value && value.length > 1 && value.indexOf(0) !== -1) {
                      callback(this.$t('message.error.zone.combined'))
                    }
                    callback()
                  }
                }
              ]
            }]"
            showSearch
            optionFilterProp="children"
            :filterOption="(input, option) => {
              return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
            }"
            @select="val => fetchvSphereStoragePolicies(val)"
            :loading="zoneLoading"
            :placeholder="this.$t('label.zoneid')">

              {{ opt.name || opt.description }}

            {{ $t('label.vmware.storage.policy') }}

              {{ policy.name || policy.id }}

        {{ this.$t('label.cancel') }}
        {{ this.$t('label.ok') }}

</template>


Minor h1 pixelated update on parents page

<template>

    <modal-timetap-schedule
        v-if="type !== 'parents'"
        :show="showTimetapModal"
        :class-type="timetapModalClassType"
        @close="showTimetapModal = false"
        @booked="onClassBooked"
    />
    <modal-timetap-confirmation
        v-if="type === 'thank-you'"
        :show="showTimetapConfirmationModal"
        @close="showTimetapConfirmationModal = false"
    />

    <page-parents-jumbotron :type="type" @cta-clicked="onScheduleAFreeClass"/>

                  CodeCombat combines the power of gameplay and personalized instruction to deliver online coding classes that your child will look forward to every week!

Remote Learning That Works

              Invest in Your Child’s Future While They Have Fun!

Personalized Learning

Our dedicated teachers offer individualized attention through a structured curriculum.  Your child will become a confident life-long coder as they conquer challenges and celebrate successes together!

The Power of Play

Our students type real Python and JavaScript while playing a game from beginning to end! Our engaging curriculum ensures that you’ll never have to force your child to show up to class. We promise.

Early Exposure to Coding

Coding is a highly sought-after job skill, and this demand will only grow. Exposing your child to coding at an early age increases the chance that they’ll pursue Computer Science later in life.

    <button-schedule-free-class @click="onScheduleAFreeClass" />

Even though the class starts every Saturday at 7am, my son is so excited that he wakes up before me! CodeCombat creates a pathway for my son to advance his coding skills.

Latthaphon Pohpon, Parent

Course Offerings

              With individual or small group class options and flexible scheduling available, this is the easiest way to get started in computer science.

Most popular

Best Value

Self-Paced

Small-Group

Private

Private

Subscription plan

$99 per year per student

$159 per month per student

$219 per month per student

$399 per month per student

60-minute sessions via Zoom

N/A

1 Group session per week

1 Private session per week

2 Private sessions per week

Student to Instructor Ratio

N/A

4:1*

1:1

1:1

CodeCombat premium license for the duration of online classes

<icon-gem />

<icon-gem />

<icon-gem />

<icon-gem />

Immediate and personalized feedback

<icon-gem />

<icon-gem />

<icon-gem />

Monthly progress updates from instructor

<icon-gem />

<icon-gem />

<icon-gem />

Weekly bonus activities, rewards, and course completion certificates

<icon-gem />

<icon-gem />

<icon-gem />

End of course projects that demonstrate concepts learned

<icon-gem />

<icon-gem />

<icon-gem />

Flexible schedule adapted to each student's learning needs

<icon-gem />

<icon-gem />

One-on-one engagement to help students stay motivated through difficult concepts

<icon-gem />

<icon-gem />

Automatic qualification into CodeCombat's AI League along with coding tournaments

<icon-gem />

*Maximum 4 students per small group session.

All subscriptions are billed on a monthly basis and may be paused, cancelled, or resumed with no fees.

    <button-schedule-free-class @click="onScheduleAFreeClass" />
    <page-parents-section-premium />

Our Curriculum

              Trusted by over 140,000 educators worldwide, our scaffolded curriculum is critical to helping students persevere. Our students type real code from the start, become more confident with each class, and learn how Computer Science is relevant to their daily lives.

Student Outcomes

Grit

CodeCombat challenges students to embrace and learn from their mistakes. Built-in hints help students identify and fix bugs, leading to greater coding confidence.

Problem Solving Skills

Through an iterative problem solving process, our students solve coding challenges and flex their creativity to build their own games, websites, and apps for end-of-course projects.

Technological Literacy

Students gain a deep understanding of Computer Science and technological literacy skills that will transfer to other disciplines as well as future careers.

            <button-arrow
                :point-left="true"
                @click="onCarouselLeft"
            />

 onCarouselDirectMove(0)">

 onCarouselDirectMove(1)">

 onCarouselDirectMove(2)">

            <button-arrow
                @click="onCarouselRight"
            />

Concepts Covered

Beginner

For students with little to no text-based coding experience.

Sequences

Algorithms

Syntax

Strings

Arguments

Loops

Variables

Cartesian coordinates

Intermediate

For students with some text-based coding experience.

Boolean logic

Properties

Advanced loops

Conditionals

Nested conditionals

Functions

Events

Basic game AI

Advanced

For students with substantial experience in text-based code and a good grasp of complex concepts.

Advanced strings

Computer arithmetic

Functions with returns

Complex conditionals

Arrays

Break/continue statements

Recursion

Stacks and Queues

    <button-schedule-free-class @click="onScheduleAFreeClass" />

FAQs

              How are instructors matched with my child?

              After the initial trial class, our team carefully matches our online instructors to each student based on their experience level, personality, interests, and schedule. Our team will work with you to improve your experience if you or your child doesn’t love learning with your instructor.

              What if I need to reschedule a class?

              We understand, and are happy to find a new date and time that works for you. Simply let your instructor or
              Education Advisor know you need to make a change. We greatly appreciate 24 hours notice for any scheduling changes.

              What kind of technology do I need to join the class?

              Any internet-connected device with a full keyboard. Laptops, PCs, and Chromebooks are recommended, however iPads will work with an external keyboard.

              What is the cancellation policy?

              You can freeze your subscription at any time and re-activate when it works for you. If you want to cancel, we hate to see you go but will process your cancellation within 24 hours.

              Are there any student age limits?

              We recommend our online classes for students aged 7 through 16. However, if your child can type, use a mouse, and is comfortable using a computer, they are welcome to join our class. If you are an adult, we simply ask you to join our private classes to ensure you get the most out of your experience.

              Can I choose my child’s courses? I know where I want my child to start.

              Yes! When enrolling in classes, you can choose whether your child should be in the beginner, intermediate, or advanced class. If you’re not sure where to start, we recommend starting with the beginner course so that your child can master fundamental concepts before advancing to more difficult courses. After the first class, the instructor will confirm whether your child is in the appropriate course.

            If you have any other questions about our online classes, please contact us.

</template>


Fix AvailableChannels URL when picking a local peer

<template>

  <SelectAddressModalGroup
    :fetchAddressArgs="fetchAddressArgs"
    @cancel="handleCancel"
    @submit="handleSelectAddressSubmit"
  />

</template>

<script>

  import { mapGetters, mapMutations, mapState } from 'vuex';
  import { SelectAddressModalGroup } from 'kolibri.coreVue.componentSets.sync';
  import { availableChannelsPageLink, selectContentPageLink } from '../manageContentLinks';

  export default {
    name: 'SelectNetworkAddressModal',
    components: {
      SelectAddressModalGroup,
    },
    props: {
      manageMode: {
        type: Boolean,
        required: false,
      },
    },
    data() {
      return {};
    },
    computed: {
      ...mapGetters('manageContent/wizard', ['isImportingMore']),
      ...mapState('manageContent/wizard', ['transferredChannel']),
      fetchAddressArgs() {
        return this.isImportingMore ? this.transferredChannel.id : '';
      },
    },
    methods: {
      ...mapMutations('manageContent/wizard', {
        resetContentWizardState: 'RESET_STATE',
      }),
      handleSelectAddressSubmit(address) {
        const addressId = address.id;
        if (this.manageMode) {
          this.$emit('submit', { addressId });
        } else {
          if (this.isImportingMore) {
            this.$router.push(
              selectContentPageLink({
                addressId,
                channelId: this.transferredChannel.id,
              })
            );
          } else {
            this.$router.push(availableChannelsPageLink({ addressId }));
          }
        }
      },
      handleCancel() {
        if (this.manageMode) {
          this.$emit('cancel');
        } else {
          this.resetContentWizardState();
        }
      },
    },
  };

</script>

<style lang="scss" scoped></style>

<template>

  <SelectAddressModalGroup
    :fetchAddressArgs="fetchAddressArgs"
    @cancel="handleCancel"
    @submit="handleSelectAddressSubmit"
  />

</template>

<script>

  import { mapGetters, mapMutations, mapState } from 'vuex';
  import { SelectAddressModalGroup } from 'kolibri.coreVue.componentSets.sync';
  import { availableChannelsPageLink, selectContentPageLink } from '../manageContentLinks';

  export default {
    name: 'SelectNetworkAddressModal',
    components: {
      SelectAddressModalGroup,
    },
    props: {
      manageMode: {
        type: Boolean,
        required: false,
      },
    },
    data() {
      return {};
    },
    computed: {
      ...mapGetters('manageContent/wizard', ['isImportingMore']),
      ...mapState('manageContent/wizard', ['transferredChannel']),
      fetchAddressArgs() {
        return this.isImportingMore ? this.transferredChannel.id : '';
      },
    },
    methods: {
      ...mapMutations('manageContent/wizard', {
        resetContentWizardState: 'RESET_STATE',
      }),
      handleSelectAddressSubmit(addressId) {
        if (this.manageMode) {
          this.$emit('submit', { addressId });
        } else {
          if (this.isImportingMore) {
            this.$router.push(
              selectContentPageLink({
                addressId,
                channelId: this.transferredChannel.id,
              })
            );
          } else {
            this.$router.push(availableChannelsPageLink({ addressId }));
          }
        }
      },
      handleCancel() {
        if (this.manageMode) {
          this.$emit('cancel');
        } else {
          this.resetContentWizardState();
        }
      },
    },
  };

</script>

<style lang="scss" scoped></style>