mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
compute: VM deployment wizard
This adds a work in progress VM deployment wizard page. Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
parent
bb9ab291df
commit
cb9c85706f
@ -16,7 +16,7 @@
|
||||
// under the License.
|
||||
|
||||
<template>
|
||||
<a-card :bordered="true">
|
||||
<a-card :bordered="true" :title="title">
|
||||
<a-skeleton active v-if="loading" />
|
||||
<div v-else>
|
||||
<div class="resource-details">
|
||||
@ -30,7 +30,7 @@
|
||||
<slot name="name">
|
||||
<h4>
|
||||
{{ resource.displayname || resource.name }}
|
||||
<console :resource="resource" size="default" />
|
||||
<console :resource="resource" size="default" v-if="resource.id" />
|
||||
</h4>
|
||||
<a-tag v-if="resource.instancename">
|
||||
{{ resource.instancename }}
|
||||
@ -73,11 +73,6 @@
|
||||
<span style="margin-left: 8px">{{ resource.ostypename }}</span>
|
||||
</div>
|
||||
|
||||
<div class="resource-detail-item">
|
||||
<slot name="details">
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div class="resource-detail-item" v-if="resource.keypair">
|
||||
<a-icon type="key" />
|
||||
<router-link :to="{ path: '/ssh/' + resource.keypair }">{{ resource.keypair }}</router-link>
|
||||
@ -170,6 +165,12 @@
|
||||
<span v-if="resource.nic && resource.nic.length > 0">{{ resource.nic.filter(e => { return e.ipaddress }).map(e => { return e.ipaddress }).join(', ') }}</span>
|
||||
<span v-else>{{ resource.ipaddress }}</span>
|
||||
</div>
|
||||
|
||||
<div class="resource-detail-item">
|
||||
<slot name="details">
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div class="resource-detail-item" v-if="resource.virtualmachineid">
|
||||
<a-icon type="desktop" class="resource-detail-item"/>
|
||||
<router-link :to="{ path: '/vm/' + resource.virtualmachineid }">{{ resource.vmname || resource.vm || resource.virtualmachinename || resource.virtualmachineid }} </router-link>
|
||||
@ -187,6 +188,10 @@
|
||||
<a-icon type="picture" class="resource-detail-item"/>
|
||||
<router-link :to="{ path: '/template/' + resource.templateid }">{{ resource.templatename || resource.templateid }} </router-link>
|
||||
</div>
|
||||
<div class="resource-detail-item" v-if="resource.diskofferingname && resource.diskofferingid">
|
||||
<a-icon type="hdd" class="resource-detail-item"/>
|
||||
<router-link :to="{ path: '/diskoffering/' + resource.diskofferingid }">{{ resource.diskofferingname || resource.diskofferingid }} </router-link>
|
||||
</div>
|
||||
<div class="resource-detail-item" v-if="resource.networkofferingid">
|
||||
<a-icon type="wifi" class="resource-detail-item"/>
|
||||
<router-link :to="{ path: '/networkoffering/' + resource.networkofferingid }">{{ resource.networkofferingname || resource.networkofferingid }} </router-link>
|
||||
@ -203,6 +208,7 @@
|
||||
<a-icon type="gateway" class="resource-detail-item"/>
|
||||
<router-link :to="{ path: '/guestnetwork/' + resource.guestnetworkid }">{{ resource.guestnetworkname || resource.guestnetworkid }} </router-link>
|
||||
</div>
|
||||
|
||||
<div class="resource-detail-item" v-if="resource.storageid">
|
||||
<a-icon type="database" class="resource-detail-item"/>
|
||||
<router-link :to="{ path: '/storagepool/' + resource.storageid }">{{ resource.storage || resource.storageid }} </router-link>
|
||||
@ -401,6 +407,10 @@ export default {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
||||
@ -938,5 +938,10 @@
|
||||
"zone": "Zone",
|
||||
"zoneId": "Zone",
|
||||
"zoneid": "Zone",
|
||||
"zonename": "Zone"
|
||||
"zonename": "Zone",
|
||||
"instance": "Instance",
|
||||
"yourInstance": "Your instance",
|
||||
"newInstance": "New instance",
|
||||
"cpu": "CPU",
|
||||
"ram": "RAM"
|
||||
}
|
||||
|
||||
37
ui/src/utils/icons.js
Normal file
37
ui/src/utils/icons.js
Normal file
@ -0,0 +1,37 @@
|
||||
// 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.
|
||||
|
||||
import _ from 'lodash'
|
||||
|
||||
const osMapping = {
|
||||
centos: 'centos',
|
||||
ubuntu: 'ubuntu',
|
||||
suse: 'suse',
|
||||
redhat: 'redhat',
|
||||
fedora: 'fedora',
|
||||
linux: 'linux',
|
||||
bsd: 'freebsd',
|
||||
apple: 'apple',
|
||||
dos: 'windows',
|
||||
windows: 'windows',
|
||||
oracle: 'java'
|
||||
}
|
||||
|
||||
export const getNormalizedOsName = (osName) => {
|
||||
osName = osName.toLowerCase()
|
||||
return _.find(osMapping, (value, key) => osName.includes(key)) || 'linux'
|
||||
}
|
||||
@ -118,7 +118,7 @@
|
||||
:footer="null"
|
||||
centered
|
||||
>
|
||||
<component :is="currentAction.component"/></component>
|
||||
<component :is="currentAction.component" :resource="resource" :loading="loading" v-bind="{currentAction}" />
|
||||
</a-modal>
|
||||
</keep-alive>
|
||||
<a-modal
|
||||
|
||||
@ -17,29 +17,307 @@
|
||||
|
||||
<template>
|
||||
<div>
|
||||
Finish this component
|
||||
<a-steps direction="vertical" :current="1">
|
||||
<a-step title="Finished" description="This is a description." />
|
||||
<a-step title="In Progress" description="This is a description." />
|
||||
<a-step title="Waiting" description="This is a description." />
|
||||
</a-steps>
|
||||
<a-row :gutter="12">
|
||||
<a-col :md="24" :lg="17">
|
||||
<a-card :bordered="true" :title="this.$t('newInstance')">
|
||||
<a-form
|
||||
:form="form"
|
||||
@submit="handleSubmit"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-form-item :label="this.$t('name')">
|
||||
<a-input
|
||||
v-decorator="['name']"
|
||||
:placeholder="this.$t('vm.name.description')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="this.$t('zoneid')">
|
||||
<a-select
|
||||
v-decorator="['zoneid', {
|
||||
rules: [{ required: zoneId.required, message: 'Please select option' }]
|
||||
}]"
|
||||
:placeholder="this.$t('vm.zone.description')"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(opt, optIndex) in zoneId.opts"
|
||||
:key="optIndex"
|
||||
:value="opt.id"
|
||||
>
|
||||
{{ opt.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-collapse
|
||||
:accordion="true"
|
||||
defaultActiveKey="templates"
|
||||
>
|
||||
<a-collapse-panel :header="this.$t('Templates')" key="templates">
|
||||
<template-selection
|
||||
:templates="templateId.opts"
|
||||
></template-selection>
|
||||
<a-form-item :label="this.$t('diskSize')">
|
||||
<a-row>
|
||||
<a-col :span="10">
|
||||
<a-slider
|
||||
:min="0"
|
||||
:max="1024"
|
||||
v-decorator="['rootdisksize']"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-input-number
|
||||
v-decorator="['rootdisksize', {
|
||||
rules: [{ required: false, message: 'Please enter a number' }]
|
||||
}]"
|
||||
:placeholder="this.$t('vm.rootdisksize')"
|
||||
:formatter="value => `${value} GB`"
|
||||
:parser="value => value.replace(' GB', '')"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel :header="this.$t('ISOs')" key="isos">
|
||||
<!-- ToDo: Add iso selection -->
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
|
||||
<compute-selection
|
||||
:compute-items="serviceOfferingId.opts"
|
||||
:value="serviceOffering ? serviceOffering.id : ''"
|
||||
@select-compute-item="($event) => updateComputeOffering($event)"
|
||||
></compute-selection>
|
||||
|
||||
<a-form-item :label="this.$t('diskOfferingId')">
|
||||
<a-select
|
||||
v-decorator="['diskofferingid', {
|
||||
rules: [{ required: diskOfferingId.required, message: 'Please select option' }]
|
||||
}]"
|
||||
:placeholder="this.$t('vm.diskoffering.description')"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(opt, optIndex) in diskOfferingId.opts"
|
||||
:key="optIndex"
|
||||
:value="opt.id"
|
||||
>
|
||||
{{ opt.name }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<div class="card-footer">
|
||||
<!-- ToDo extract as component -->
|
||||
<a-button @click="() => this.$router.back()">{{ this.$t('cancel') }}</a-button>
|
||||
<a-button type="primary" @click="handleSubmit">{{ this.$t('submit') }}</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :md="24" :lg="7">
|
||||
<info-card :resource="vm" :title="this.$t('yourInstance')" >
|
||||
<div slot="details" v-if="vm.diskofferingid || instanceConfig.rootdisksize">
|
||||
<a-icon type="hdd"></a-icon>
|
||||
<span style="margin-left: 10px">
|
||||
<span v-if="instanceConfig.rootdisksize">{{ instanceConfig.rootdisksize }} GB (Root)</span>
|
||||
<span v-if="instanceConfig.rootdisksize && instanceConfig.diskofferingid"> | </span>
|
||||
<span v-if="instanceConfig.diskofferingid">{{ diskOffering.disksize }} GB (Data)</span>
|
||||
</span>
|
||||
</div>
|
||||
</info-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import { api } from '@/api'
|
||||
import store from '@/store'
|
||||
import _ from 'lodash'
|
||||
|
||||
import InfoCard from '@/components/view/InfoCard'
|
||||
import ComputeSelection from './wizard/ComputeSelection'
|
||||
import TemplateSelection from './wizard/TemplateSelection'
|
||||
|
||||
export default {
|
||||
name: 'DeployVM',
|
||||
name: 'Wizard',
|
||||
components: {
|
||||
InfoCard,
|
||||
ComputeSelection,
|
||||
TemplateSelection
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
vm: {},
|
||||
params: [],
|
||||
visibleParams: [
|
||||
'name',
|
||||
'templateid',
|
||||
'serviceofferingid',
|
||||
'diskofferingid',
|
||||
'zoneid',
|
||||
'rootdisksize'
|
||||
],
|
||||
instanceConfig: [],
|
||||
template: {},
|
||||
serviceOffering: {},
|
||||
diskOffering: {},
|
||||
zone: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredParams () {
|
||||
return this.visibleParams.map((fieldName) => {
|
||||
return this.params.find((param) => fieldName === param.name)
|
||||
})
|
||||
},
|
||||
templateId () {
|
||||
return this.getParam('templateid')
|
||||
},
|
||||
serviceOfferingId () {
|
||||
return this.getParam('serviceofferingid')
|
||||
},
|
||||
diskOfferingId () {
|
||||
return this.getParam('diskofferingid')
|
||||
},
|
||||
zoneId () {
|
||||
return this.getParam('zoneid')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
instanceConfig (instanceConfig) {
|
||||
this.template = this.templateId.opts.find((option) => option.id === instanceConfig.templateid)
|
||||
this.serviceOffering = this.serviceOfferingId.opts.find((option) => option.id === instanceConfig.computeofferingid)
|
||||
this.diskOffering = this.diskOfferingId.opts.find((option) => option.id === instanceConfig.diskofferingid)
|
||||
this.zone = this.zoneId.opts.find((option) => option.id === instanceConfig.zoneid)
|
||||
|
||||
if (this.zone) {
|
||||
this.vm['zoneid'] = this.zone.id
|
||||
this.vm['zonename'] = this.zone.name
|
||||
}
|
||||
|
||||
if (this.template) {
|
||||
this.vm['templateid'] = this.template.id
|
||||
this.vm['templatename'] = this.template.displaytext
|
||||
this.vm['ostypeid'] = this.template.ostypeid
|
||||
this.vm['ostypename'] = this.template.ostypename
|
||||
}
|
||||
|
||||
if (this.serviceOffering) {
|
||||
this.vm['serviceofferingid'] = this.serviceOffering.id
|
||||
this.vm['serviceofferingname'] = this.serviceOffering.displaytext
|
||||
this.vm['cpunumber'] = this.serviceOffering.cpunumber
|
||||
this.vm['cpuspeed'] = this.serviceOffering.cpuspeed
|
||||
this.vm['memory'] = this.serviceOffering.memory
|
||||
}
|
||||
|
||||
if (this.diskOffering) {
|
||||
this.vm['diskofferingid'] = this.diskOffering.id
|
||||
this.vm['diskofferingname'] = this.diskOffering.displaytext
|
||||
this.vm['diskofferingsize'] = this.diskOffering.disksize
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
this.form = this.$form.createForm(this, {
|
||||
onValuesChange: (props, fields) => {
|
||||
this.instanceConfig = { ...this.form.getFieldsValue(), ...fields }
|
||||
this.vm = this.instanceConfig
|
||||
}
|
||||
})
|
||||
this.form.getFieldDecorator('computeofferingid', { initialValue: [], preserve: true })
|
||||
},
|
||||
created () {
|
||||
this.params = store.getters.apis[this.$route.name]['params']
|
||||
this.filteredParams.forEach((param) => {
|
||||
this.fetchOptions(param)
|
||||
})
|
||||
Vue.nextTick().then(() => {
|
||||
this.instanceConfig = this.form.getFieldsValue() // ToDo: maybe initialize with some other defaults
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
updateComputeOffering (id) {
|
||||
this.form.setFieldsValue({
|
||||
computeofferingid: id
|
||||
})
|
||||
},
|
||||
getParam (paramName) {
|
||||
return this.params.find((param) => param.name === paramName)
|
||||
},
|
||||
getText (option) {
|
||||
return _.get(option, 'displaytext', _.get(option, 'name'))
|
||||
},
|
||||
handleSubmit () {
|
||||
console.log('wizard submit')
|
||||
},
|
||||
fetchOptions (param) {
|
||||
const paramName = param.name
|
||||
const possibleName = `list${paramName.replace('id', '').toLowerCase()}s`
|
||||
let possibleApi
|
||||
if (paramName === 'id') {
|
||||
possibleApi = this.apiName
|
||||
} else {
|
||||
possibleApi = _.filter(Object.keys(store.getters.apis), (api) => {
|
||||
return api.toLowerCase().startsWith(possibleName)
|
||||
})[0]
|
||||
}
|
||||
|
||||
if (!possibleApi) {
|
||||
return
|
||||
}
|
||||
|
||||
param.loading = true
|
||||
param.opts = []
|
||||
const params = {}
|
||||
params.listall = true
|
||||
if (possibleApi === 'listTemplates') {
|
||||
params.templatefilter = 'executable'
|
||||
}
|
||||
api(possibleApi, params).then((response) => {
|
||||
param.loading = false
|
||||
_.map(response, (responseItem, responseKey) => {
|
||||
if (!responseKey.includes('response')) {
|
||||
return
|
||||
}
|
||||
_.map(responseItem, (response, key) => {
|
||||
if (key === 'count') {
|
||||
return
|
||||
}
|
||||
param.opts = response
|
||||
this.$forceUpdate()
|
||||
})
|
||||
})
|
||||
}).catch(function (error) {
|
||||
console.log(error.stack)
|
||||
param.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="less" scoped>
|
||||
.card-footer {
|
||||
text-align: right;
|
||||
|
||||
button + button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-list-item-meta-avatar {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.ant-collapse {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
110
ui/src/views/compute/wizard/ComputeSelection.vue
Normal file
110
ui/src/views/compute/wizard/ComputeSelection.vue
Normal file
@ -0,0 +1,110 @@
|
||||
// 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>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:dataSource="tableSource"
|
||||
:pagination="false"
|
||||
:scroll="{x: 0, y: 320}"
|
||||
:rowSelection="rowSelection"
|
||||
size="middle"
|
||||
>
|
||||
<span slot="cpuTitle"><a-icon type="appstore" /> {{ $t('cpu') }}</span>
|
||||
<span slot="ramTitle"><a-icon type="bulb" /> {{ $t('ram') }}</span>
|
||||
</a-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ComputeSelection',
|
||||
props: {
|
||||
computeItems: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
columns: [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
width: '40%'
|
||||
},
|
||||
{
|
||||
dataIndex: 'cpu',
|
||||
slots: { title: 'cpuTitle' },
|
||||
width: '30%'
|
||||
},
|
||||
{
|
||||
dataIndex: 'ram',
|
||||
slots: { title: 'ramTitle' },
|
||||
width: '30%'
|
||||
}
|
||||
],
|
||||
selectedRowKeys: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tableSource () {
|
||||
return this.computeItems.map((item) => {
|
||||
return {
|
||||
key: item.id,
|
||||
name: item.name,
|
||||
cpu: `${item.cpunumber} CPU x ${parseFloat(item.cpuspeed / 1000.0).toFixed(2)} Ghz`,
|
||||
ram: `${item.memory} MB`
|
||||
}
|
||||
})
|
||||
},
|
||||
rowSelection () {
|
||||
return {
|
||||
type: 'radio',
|
||||
selectedRowKeys: this.selectedRowKeys,
|
||||
onSelect: (row) => {
|
||||
this.$emit('select-compute-item', row.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (newValue, oldValue) {
|
||||
if (newValue && newValue !== oldValue) {
|
||||
this.selectedRowKeys = [newValue]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-table-wrapper {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.ant-table-selection-column {
|
||||
// Fix for the table header if the row selection use radio buttons instead of checkboxes
|
||||
> div:empty {
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
86
ui/src/views/compute/wizard/TemplateSelection.vue
Normal file
86
ui/src/views/compute/wizard/TemplateSelection.vue
Normal file
@ -0,0 +1,86 @@
|
||||
// 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>
|
||||
<a-tabs :defaultActiveKey="Object.keys(osTypes)[0]">
|
||||
<a-tab-pane v-for="(osList, osName) in osTypes" :key="osName">
|
||||
<span slot="tab">
|
||||
<os-logo :os-name="osName"></os-logo>
|
||||
</span>
|
||||
<a-form-item>
|
||||
<a-radio-group
|
||||
v-for="(os, osIndex) in osList"
|
||||
:key="osIndex"
|
||||
class="radio-group"
|
||||
v-decorator="['templateid', {
|
||||
rules: [{ required: true, message: 'Please select option' }]
|
||||
}]"
|
||||
>
|
||||
<a-radio
|
||||
class="radio-group__radio"
|
||||
:value="os.id"
|
||||
>{{ os.displaytext }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import OsLogo from '@/components/widgets/OsLogo'
|
||||
import { getNormalizedOsName } from '@/utils/icons'
|
||||
|
||||
export default {
|
||||
name: 'TemplateSelection',
|
||||
components: { OsLogo },
|
||||
props: {
|
||||
templates: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
osTypes () {
|
||||
const mappedTemplates = {}
|
||||
this.templates.forEach((os) => {
|
||||
const osName = getNormalizedOsName(os.ostypename)
|
||||
if (Array.isArray(mappedTemplates[osName])) {
|
||||
mappedTemplates[osName].push(os)
|
||||
} else {
|
||||
mappedTemplates[osName] = [os]
|
||||
}
|
||||
})
|
||||
return mappedTemplates
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__radio {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user