This is an automated email from the ASF dual-hosted git repository.
rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack-primate.git
The following commit(s) were added to refs/heads/master by this push:
new ba214fd compute: NICs and IPs management (#71)
ba214fd is described below
commit ba214fd502583f9cf5a1e70d52187dbbc09e4e4b
Author: Ritchie Vincent <[email protected]>
AuthorDate: Thu Dec 19 02:55:14 2019 +0000
compute: NICs and IPs management (#71)
Adds VM nic/ip management, implement some placement fixes.
Signed-off-by: Rohit Yadav <[email protected]>
---
package-lock.json | 58 ++--
package.json | 12 +-
src/components/view/DetailSettings.vue | 53 ++-
src/components/view/ListView.vue | 8 +-
src/components/view/SettingsTab.vue | 2 +-
src/config/section/iam.js | 12 +-
src/locales/en.json | 8 +
src/views/AutogenView.vue | 36 +-
src/views/compute/InstanceHardware.vue | 603 ++++++++++++++++++++++++++++++---
src/views/infra/InfraSummary.vue | 25 +-
10 files changed, 664 insertions(+), 153 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 1e54b65..c4c0c2a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2341,9 +2341,9 @@
}
},
"@fortawesome/vue-fontawesome": {
- "version": "0.1.8",
- "resolved":
"https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-0.1.8.tgz",
- "integrity":
"sha512-SdFiUD+vFDA/xKuEbnQTVrK8FDxoV0eyQaiHxmCcjAc0+vQe0Kf6oGm28opNPIt8MTgKWR3+Yg3xXP455Ae4tQ=="
+ "version": "0.1.9",
+ "resolved":
"https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-0.1.9.tgz",
+ "integrity":
"sha512-h/emhmZz+DfB2zOGLWawNwXq82UYhn9waTfUjLLmeaIqtnIyNt6kYlpQT/vzJjLZRDRvY2IEJAh1di5qKpKVpA=="
},
"@hapi/address": {
"version": "2.1.4",
@@ -8526,9 +8526,9 @@
}
},
"core-js": {
- "version": "3.4.8",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.4.8.tgz",
- "integrity":
"sha512-b+BBmCZmVgho8KnBUOXpvlqEMguko+0P+kXCwD4vIprsXC6ht1qgPxtb1OK6XgSlrySF71wkwBQ0Hv695bk9gQ=="
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.5.0.tgz",
+ "integrity":
"sha512-Ifh3kj78gzQ7NAoJXeTu+XwzDld0QRIwjBLRqAMhuLhP3d2Av5wmgE9ycfnvK6NAEjTkQ1sDPeoEZAWO3Hx1Uw=="
},
"core-js-compat": {
"version": "3.4.7",
@@ -10009,9 +10009,9 @@
"dev": true
},
"elliptic": {
- "version": "6.5.1",
- "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz",
- "integrity":
"sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==",
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
+ "integrity":
"sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
"dev": true,
"requires": {
"bn.js": "^4.4.0",
@@ -20621,9 +20621,9 @@
}
},
"serialize-javascript": {
- "version": "1.9.1",
- "resolved":
"https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz",
- "integrity":
"sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==",
+ "version": "2.1.2",
+ "resolved":
"https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
+ "integrity":
"sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
"dev": true
},
"serve-index": {
@@ -22016,16 +22016,16 @@
}
},
"terser-webpack-plugin": {
- "version": "1.4.1",
- "resolved":
"https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz",
- "integrity":
"sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==",
+ "version": "1.4.3",
+ "resolved":
"https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
+ "integrity":
"sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
"dev": true,
"requires": {
"cacache": "^12.0.2",
"find-cache-dir": "^2.1.0",
"is-wsl": "^1.1.0",
"schema-utils": "^1.0.0",
- "serialize-javascript": "^1.7.0",
+ "serialize-javascript": "^2.1.2",
"source-map": "^0.6.1",
"terser": "^4.1.2",
"webpack-sources": "^1.4.0",
@@ -23005,9 +23005,9 @@
"dev": true
},
"vue": {
- "version": "2.6.10",
- "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.10.tgz",
- "integrity":
"sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ=="
+ "version": "2.6.11",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
+ "integrity":
"sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ=="
},
"vue-cli-plugin-apollo": {
"version": "0.21.3",
@@ -23259,9 +23259,9 @@
"dev": true
},
"vue-i18n": {
- "version": "8.15.1",
- "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.15.1.tgz",
- "integrity":
"sha512-GBbz8qYCu0U2LNu4IcuFLZiuyninG4k26knvhL7GZG5Ncp4RR2VKDEH6g8gQ6I+UUBCvH2MBQVPSdxWe4DBkPw=="
+ "version": "8.15.3",
+ "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.15.3.tgz",
+ "integrity":
"sha512-PVNgo6yhOmacZVFjSapZ314oewwLyXHjJwAqjnaPN1GJAJd/dvsrShGzSiJuCX4Hc36G4epJvNXUwO8y7wEKew=="
},
"vue-i18n-extract": {
"version": "0.4.14",
@@ -23506,9 +23506,9 @@
}
},
"vue-template-compiler": {
- "version": "2.6.10",
- "resolved":
"https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz",
- "integrity":
"sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==",
+ "version": "2.6.11",
+ "resolved":
"https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz",
+ "integrity":
"sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==",
"dev": true,
"requires": {
"de-indent": "^1.0.2",
@@ -23607,9 +23607,9 @@
"dev": true
},
"webpack": {
- "version": "4.41.2",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.2.tgz",
- "integrity":
"sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A==",
+ "version": "4.41.3",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.3.tgz",
+ "integrity":
"sha512-EcNzP9jGoxpQAXq1VOoTet0ik7/VVU1MovIfcUSAjLowc7GhcQku/sOXALvq5nPpSei2HF6VRhibeJSC3i/Law==",
"dev": true,
"requires": {
"@webassemblyjs/ast": "1.8.5",
@@ -23632,7 +23632,7 @@
"node-libs-browser": "^2.2.1",
"schema-utils": "^1.0.0",
"tapable": "^1.1.3",
- "terser-webpack-plugin": "^1.4.1",
+ "terser-webpack-plugin": "^1.4.3",
"watchpack": "^1.6.0",
"webpack-sources": "^1.4.1"
}
diff --git a/package.json b/package.json
index 50e169e..daf9872 100644
--- a/package.json
+++ b/package.json
@@ -37,10 +37,10 @@
"@fortawesome/free-brands-svg-icons": "^5.12.0",
"@fortawesome/free-regular-svg-icons": "^5.12.0",
"@fortawesome/free-solid-svg-icons": "^5.12.0",
- "@fortawesome/vue-fontawesome": "^0.1.8",
+ "@fortawesome/vue-fontawesome": "^0.1.9",
"ant-design-vue": "~1.4.10",
"axios": "^0.19.0",
- "core-js": "^3.4.8",
+ "core-js": "^3.5.0",
"enquire.js": "^2.1.6",
"js-cookie": "^2.2.1",
"lodash.get": "^4.4.2",
@@ -51,10 +51,10 @@
"npm-check-updates": "^4.0.1",
"nprogress": "^0.2.0",
"viser-vue": "^2.4.7",
- "vue": "^2.6.10",
+ "vue": "^2.6.11",
"vue-clipboard2": "^0.3.1",
"vue-cropper": "0.4.9",
- "vue-i18n": "^8.15.1",
+ "vue-i18n": "^8.15.3",
"vue-ls": "^3.2.1",
"vue-router": "^3.1.3",
"vue-svg-component-runtime": "^1.0.1",
@@ -87,8 +87,8 @@
"sass-loader": "^8.0.0",
"vue-cli-plugin-i18n": "^0.6.0",
"vue-svg-icon-loader": "^2.1.1",
- "vue-template-compiler": "^2.6.10",
- "webpack": "^4.41.2"
+ "vue-template-compiler": "^2.6.11",
+ "webpack": "^4.41.3"
},
"eslintConfig": {
"root": true,
diff --git a/src/components/view/DetailSettings.vue
b/src/components/view/DetailSettings.vue
index fb7d90d..0b80a7a 100644
--- a/src/components/view/DetailSettings.vue
+++ b/src/components/view/DetailSettings.vue
@@ -35,13 +35,36 @@
:dataSource="detailOptions[newKey]"
placeholder="Value"
@change="e => onAddInputChange(e, 'newValue')" />
- <a-button type="dashed" style="width: 50%" icon="close"
@click="showAddDetail = false">Cancel</a-button>
- <a-button type="primary" style="width: 50%" icon="plus"
@click="addDetail">Add Setting</a-button>
+ <a-button type="primary" style="width: 25%" icon="plus"
@click="addDetail">Add Setting</a-button>
+ <a-button type="dashed" style="width: 25%" icon="close"
@click="showAddDetail = false">Cancel</a-button>
</div>
<a-list size="large">
<a-list-item :key="index" v-for="(item, index) in details">
<a-list-item-meta>
- <span slot="title"><strong>{{ item.name }}</strong></span>
+ <span slot="title">
+ {{ item.name }}
+ <a-button shape="circle" size="small" @click="updateDetail(index)"
v-if="item.edit">
+ <a-icon type="check-circle" theme="twoTone"
twoToneColor="#52c41a" />
+ </a-button>
+ <a-button shape="circle" size="small"
@click="hideEditDetail(index)" v-if="item.edit" style="margin-left: 5px">
+ <a-icon type="close-circle" theme="twoTone"
twoToneColor="#f5222d" />
+ </a-button>
+ <a-button shape="circle" size="small"
@click="showEditDetail(index)" v-if="!item.edit">
+ <a-icon type="edit" />
+ </a-button>
+ <a-divider type="vertical" />
+ <a-popconfirm
+ title="Delete setting?"
+ @confirm="deleteDetail(index)"
+ okText="Yes"
+ cancelText="No"
+ placement="right"
+ >
+ <a-button shape="circle" size="small">
+ <a-icon type="delete" theme="twoTone" twoToneColor="#f5222d" />
+ </a-button>
+ </a-popconfirm>
+ </span>
<span slot="description" style="word-break: break-all">
<span v-if="item.edit" style="display: flex">
<a-auto-complete
@@ -54,30 +77,6 @@
<span v-else @click="showEditDetail(index)">{{ item.value }}</span>
</span>
</a-list-item-meta>
- <div slot="actions">
- <a-button shape="circle" size="default" @click="updateDetail(index)"
v-if="item.edit">
- <a-icon type="check-circle" theme="twoTone" twoToneColor="#52c41a"
/>
- </a-button>
- <a-button shape="circle" size="default"
@click="hideEditDetail(index)" v-if="item.edit">
- <a-icon type="close-circle" theme="twoTone" twoToneColor="#f5222d"
/>
- </a-button>
- <a-button shape="circle" @click="showEditDetail(index)"
v-if="!item.edit">
- <a-icon type="edit" />
- </a-button>
- </div>
- <div slot="actions">
- <a-popconfirm
- title="Delete setting?"
- @confirm="deleteDetail(index)"
- okText="Yes"
- cancelText="No"
- placement="left"
- >
- <a-button shape="circle">
- <a-icon type="delete" theme="twoTone" twoToneColor="#f5222d" />
- </a-button>
- </a-popconfirm>
- </div>
</a-list-item>
</a-list>
</a-spin>
diff --git a/src/components/view/ListView.vue b/src/components/view/ListView.vue
index e651992..337e132 100644
--- a/src/components/view/ListView.vue
+++ b/src/components/view/ListView.vue
@@ -17,13 +17,13 @@
<template>
<a-table
- size="small"
+ size="middle"
:loading="loading"
:columns="columns"
:dataSource="items"
:rowKey="record => record.id || record.name"
:pagination="false"
- :rowSelection="{selectedRowKeys: selectedRowKeys, onChange:
onSelectChange}"
+ :rowSelection="['vm', 'event', 'alert'].includes($route.name) ?
{selectedRowKeys: selectedRowKeys, onChange: onSelectChange} : null"
:rowClassName="getRowClassName"
>
<template slot="footer">
@@ -33,7 +33,7 @@
</template>
<a slot="name" slot-scope="text, record" href="javascript:;">
- <div>
+ <div style="min-width: 150px; padding-left: 5px">
<span v-if="$route.path.startsWith('/project')" style="margin-right:
5px">
<a-button type="dashed" size="small" shape="circle" icon="login"
@click="changeProject(record)" />
</span>
@@ -41,7 +41,7 @@
<router-link :to="{ path: $route.path + '/' + record.id }"
v-if="record.id">{{ text }}</router-link>
<router-link :to="{ path: $route.path + '/' + record.name }" v-else>{{
text }}</router-link>
</div>
- <div v-if="$route.meta.related" style="padding-top: 5px">
+ <div v-if="$route.meta.related" style="padding-top: 10px; padding-left:
5px; display: inline-flex">
<span v-for="item in $route.meta.related" :key="item.path">
<router-link
v-if="$router.resolve('/' + item.name).route.name !== '404'"
diff --git a/src/components/view/SettingsTab.vue
b/src/components/view/SettingsTab.vue
index a898e27..e62975d 100644
--- a/src/components/view/SettingsTab.vue
+++ b/src/components/view/SettingsTab.vue
@@ -2,7 +2,7 @@
<a-list size="large" class="list" :loading="loading || tabLoading">
<a-list-item :key="index" v-for="(item, index) in items" class="item">
<a-list-item-meta>
- <span slot="title" style="word-break: break-all"><strong>{{ item.name
}}</strong></span>
+ <span slot="title" style="word-break: break-all">{{ item.name }}</span>
<span slot="description" style="word-break: break-all">{{
item.description }}</span>
</a-list-item-meta>
diff --git a/src/config/section/iam.js b/src/config/section/iam.js
index ca16857..b4d7450 100644
--- a/src/config/section/iam.js
+++ b/src/config/section/iam.js
@@ -173,8 +173,16 @@ export default {
icon: 'safety-certificate',
label: 'Add certificate',
dataView: true,
- args: ['name', 'certificate', 'privatekey', 'certchain', 'password'],
- show: (record) => { return record.state === 'enabled' }
+ args: ['name', 'certificate', 'privatekey', 'certchain', 'password',
'account', 'domainid'],
+ show: (record) => { return record.state === 'enabled' },
+ mapping: {
+ account: {
+ value: (record) => { return record.name }
+ },
+ domainid: {
+ value: (record) => { return record.domainid }
+ }
+ }
},
{
api: 'deleteAccount',
diff --git a/src/locales/en.json b/src/locales/en.json
index d330ce2..34ee199 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -134,6 +134,7 @@
"current": "isCurrent",
"date": "Date",
"dedicated": "Dedicated",
+"default": "Default",
"deleteconfirm": "Please confirm that you would like to delete this {name}",
"deleteprofile": "Delete Profile",
"deploymentPlanner": "Deployment planner",
@@ -632,6 +633,10 @@
"memused": "Memory Usage",
"message.edit.account": "Edit (\"-1\" indicates no limit to the amount of
resources create)",
"message.assign.instance.another": "Please specify the account type, domain,
account name and network (optional) of the new account. <br> If the default nic
of the vm is on a shared network, CloudStack will check if the network can be
used by the new account if you do not specify one network. <br> If the default
nic of the vm is on a isolated network, and the new account has more one
isolated networks, you should specify one.",
+"message.network.addVM.desc":"Please specify the network that you would like
to add this VM to. A new NIC will be added for this network.",
+"message.network.removeNIC": "Please confirm that want to remove this NIC,
which will also remove the associated network from the VM.",
+"message.network.secondaryIP" : "Please confirm that you would like to acquire
a new secondary IP for this NIC. \n NOTE: You need to manually configure the
newly-acquired secondary IP inside the virtual machine.",
+"message.network.updateIp": "Please confirm that you would like to change the
IP address for this NIC on VM.",
"minCPUNumber": "Min CPU Cores",
"minInstance": "Min Instances",
"minIops": "Min IOPS",
@@ -729,6 +734,7 @@
"protocolnumber": "#Protocol",
"provider": "HA Provider",
"providername": "Provider",
+"provisioning": "Provisioning",
"provisioningType": "Provisioning Type",
"provisioningtype": "Provisioning Type",
"publicinterface": "Public Interface",
@@ -754,6 +760,7 @@
"redundantstate": "Redundant state",
"redundantvpcrouter": "Redundant VPC",
"reenterpassword": "Re-enter Password",
+"refresh": "Refresh",
"relationaloperator": "Operator",
"required": "Required",
"requireshvm": "HVM",
@@ -946,6 +953,7 @@
"zoneId": "Zone",
"zoneid": "Zone",
"zonename": "Zone",
+"zonenamelabel": "Zone Name",
"instance": "Instance",
"yourInstance": "Your instance",
"newInstance": "New instance",
diff --git a/src/views/AutogenView.vue b/src/views/AutogenView.vue
index 2533f50..1a2f551 100644
--- a/src/views/AutogenView.vue
+++ b/src/views/AutogenView.vue
@@ -19,23 +19,25 @@
<div>
<a-card class="breadcrumb-card">
<a-row>
- <a-col :span="12">
- <breadcrumb style="padding-top: 6px" />
+ <a-col :span="24" style="display: flex">
+ <breadcrumb />
+ <a-tooltip placement="bottom">
+ <template slot="title">
+ {{ "Refresh" }}
+ </template>
+ <a-button
+ style="margin-left: 8px"
+ :loading="loading"
+ shape="round"
+ size="small"
+ icon="sync"
+ @click="fetchData()">
+ {{ $t('refresh') }}
+ </a-button>
+ </a-tooltip>
</a-col>
- <a-col :span="12">
- <span style="float: right">
- <a-tooltip placement="bottom">
- <template slot="title">
- {{ "Refresh" }}
- </template>
- <a-button
- :loading="loading"
- shape="circle"
- type="dashed"
- icon="reload"
- style="margin-right: 5px"
- @click="fetchData()" />
- </a-tooltip>
+ <a-col :span="24" style="padding-top: 12px; margin-bottom: -6px">
+ <span style="padding-left: 5px">
<a-tooltip
v-for="(action, actionIndex) in actions"
:key="actionIndex"
@@ -56,7 +58,7 @@
</a-button>
</a-tooltip>
<a-input-search
- style="width: unset"
+ style="width: 50%; padding-left: 6px"
placeholder="Search"
v-model="searchQuery"
v-if="!dataView && !treeView"
diff --git a/src/views/compute/InstanceHardware.vue
b/src/views/compute/InstanceHardware.vue
index 921e511..9720aa2 100644
--- a/src/views/compute/InstanceHardware.vue
+++ b/src/views/compute/InstanceHardware.vue
@@ -17,18 +17,22 @@
<template>
<div>
- <a-collapse v-model="activeKey">
+ <a-collapse v-model="activeKey" :bordered="false">
+
<a-collapse-panel :header="'ISO: ' + vm.isoname" v-if="vm.isoid" key="1">
<a-list
itemLayout="horizontal">
<a-list-item>
- <a-list-item-meta :description="vm.isoid">
- <a slot="title" href="">
- <router-link :to="{ path: '/iso/' + vm.isoid }">{{ vm.isoname
}}</router-link>
- </a> ({{ vm.isoname }})
- <a-avatar slot="avatar">
- <a-icon type="usb" />
- </a-avatar>
+ <a-list-item-meta>
+ <div slot="avatar">
+ <a-avatar>
+ <a-icon type="usb" />
+ </a-avatar>
+ </div>
+ <div slot="title">
+ <router-link :to="{ path: '/iso/' + vm.isoid }">{{ vm.isoname
}}</router-link> <br/>
+ <a-icon type="barcode"/> {{ vm.isoid }}
+ </div>
</a-list-item-meta>
</a-list-item>
</a-list>
@@ -42,59 +46,220 @@
>
<a-list-item slot="renderItem" slot-scope="item">
<a-list-item-meta>
- <div slot="title">
- <router-link :to="{ path: '/volume/' + item.id }">{{ item.name
}}</router-link> ({{ item.type }}) <br/>
- <status :text="item.state" displayText /><br/>
+ <div slot="avatar">
+ <a-avatar>
+ <a-icon type="hdd" />
+ </a-avatar>
</div>
- <div slot="description">
- <a-icon type="barcode"/> {{ item.id }}
+ <div slot="title">
+ <router-link :to="{ path: '/volume/' + item.id }">{{ item.name
}} </router-link>
+ <a-tag v-if="item.type">
+ {{ item.type }}
+ </a-tag>
+ <a-tag v-if="item.state">
+ {{ item.state }}
+ </a-tag>
+ <a-tag v-if="item.provisioningtype">
+ {{ item.provisioningtype }}
+ </a-tag>
+ <br/>
+ {{ $t('size') }}: {{ (item.size / (1024 * 1024 *
1024.0)).toFixed(2) }} GB<br/>
+ {{ $t('physicalsize') }}: {{ (item.physicalsize / (1024 * 1024
* 1024.0)).toFixed(4) }} GB<br/>
+ {{ $t('storagePool') }}: {{ item.storage }} ({{
item.storagetype }})<br/>
+ <a-icon type="barcode"/> {{ item.id }} <br/>
</div>
- <a-avatar slot="avatar">
- <a-icon type="hdd" />
- </a-avatar>
</a-list-item-meta>
- <p>
- Size: {{ (item.size / (1024 * 1024 * 1024.0)).toFixed(4) }}
GB<br/>
- Physical Size: {{ (item.physicalsize / (1024 * 1024 *
1024.0)).toFixed(4) }} GB<br/>
- Provisioning: {{ item.provisioningtype }}<br/>
- Storage Pool: {{ item.storage }} ({{ item.storagetype }})<br/>
- </p>
+ <div slot="actions" class="actions">
+ </div>
</a-list-item>
</a-list>
-
</a-collapse-panel>
+
<a-collapse-panel :header="'Network Adapter(s): ' + (vm && vm.nic ?
vm.nic.length : 0)" key="3" >
+ <a-button type="primary" @click="showAddModal" :loading="loadingNic">
+ <a-icon type="plus"></a-icon> {{ $t('label.network.addVM') }}
+ </a-button>
<a-list
size="small"
itemLayout="horizontal"
:dataSource="vm.nic"
+ class="list"
+ :loading="loadingNic"
>
- <a-list-item slot="renderItem" slot-scope="item">
+ <a-list-item slot="renderItem" slot-scope="item" class="list__item">
<a-list-item-meta>
- <div slot="title">
- <span v-show="item.isdefault">(Default) </span>
- <router-link :to="{ path: '/guestnetwork/' + item.networkid
}">{{ item.networkname }} </router-link><br/>
- Mac Address: {{ item.macaddress }}<br/>
- <span v-if="item.ipaddress">Address: {{ item.ipaddress }}
<br/></span>
- Netmask: {{ item.netmask }}<br/>
- Gateway: {{ item.gateway }}<br/>
+ <div slot="avatar">
+ <a-avatar slot="avatar">
+ <a-icon type="wifi" />
+ </a-avatar>
+ <br/>
+ <a-popconfirm
+ title="Please confirm that you would like to make this NIC
the default for this VM."
+ @confirm="setAsDefault(item)"
+ okText="Yes"
+ cancelText="No"
+ >
+ <a-button
+ style="margin-top: 10px"
+ icon="arrow-right"
+ size="small"
+ shape="round" />
+ </a-popconfirm>
+ <br/>
+ <a-tooltip placement="right" v-if="item.type !== 'L2'">
+ <template slot="title">
+ {{ "Change IP Address" }}
+ </template>
+ <a-button
+ style="margin-top: 10px"
+ icon="swap"
+ size="small"
+ shape="round"
+ @click="editIpAddressNic = item.id; showUpdateIpModal =
true" />
+ </a-tooltip>
+ <br/>
+ <a-tooltip placement="right" v-if="item.type !== 'L2'">
+ <template slot="title">
+ {{ "Manage Secondary IP Addresses" }}
+ </template>
+ <a-button
+ style="margin-top: 10px"
+ icon="environment"
+ size="small"
+ shape="round"
+ @click="fetchSecondaryIPs(item.id)" />
+ </a-tooltip>
+ <br/>
+ <a-popconfirm
+ :title="$t('message.network.removeNIC')"
+ @confirm="removeNIC(item)"
+ okText="Yes"
+ cancelText="No"
+ v-if="!item.isdefault"
+ >
+ <a-button
+ style="margin-top: 10px"
+ type="danger"
+ icon="delete"
+ size="small"
+ shape="round" />
+ </a-popconfirm>
</div>
- <div slot="description">
+ <div slot="title">
+ <router-link :to="{ path: '/guestnetwork/' + item.networkid
}">{{ item.networkname }} </router-link>
+ <a-tag v-if="item.isdefault">
+ {{ $t('default') }}
+ </a-tag>
+ <a-tag v-if="item.type">
+ {{ item.type }}
+ </a-tag>
+ <a-tag v-if="item.broadcasturi">
+ {{ item.broadcasturi }}
+ </a-tag>
+ <a-tag v-if="item.isolationuri">
+ {{ item.isolationuri }}
+ </a-tag>
+ <br />
+ {{ $t('macaddress') }}: {{ item.macaddress }}<br/>
+ <span v-if="item.ipaddress">
+ {{ $t('IP Address') }}: {{ item.ipaddress }}
+ <br/>
+ </span>
+ <span v-if="item.secondaryip && item.type !== 'L2'">
+ {{ $t('Secondary IPs') }}: {{ item.secondaryip.map(x =>
x.ipaddress).join(', ') }}
+ <br/>
+ </span>
+ <span v-if="item.netmask">
+ {{ $t('netmask') }}: {{ item.netmask }}
+ <br/>
+ </span>
+ <span v-if="item.gateway">
+ {{ $t('gateway') }}: {{ item.gateway }}
+ <br/>
+ </span>
<a-icon type="barcode"/> {{ item.id }}
</div>
- <a-avatar slot="avatar">
- <a-icon type="wifi" />
- </a-avatar>
</a-list-item-meta>
- <p>
- Type: {{ item.type }}<br/>
- Broadcast URI: {{ item.broadcasturi }}<br/>
- Isolation URI: {{ item.isolationuri }}<br/>
- </p>
</a-list-item>
</a-list>
</a-collapse-panel>
</a-collapse>
+
+ <a-modal
+ :visible="showAddNetworkModal"
+ :title="$t('label.network.addVM')"
+ @cancel="closeModals"
+ @ok="submitAddNetwork">
+ {{ $t('message.network.addVM.desc') }}
+
+ <div class="modal-form">
+ <p class="modal-form__label">{{ $t('Network') }}:</p>
+ <a-select :defaultValue="addNetworkData.network" @change="e =>
addNetworkData.network === e">
+ <a-select-option
+ v-for="network in addNetworkData.allNetworks"
+ :key="network.id"
+ :value="network.id">
+ {{ network.name }}
+ </a-select-option>
+ </a-select>
+ <p class="modal-form__label">{{ $t('publicip') }}:</p>
+ <a-input v-model="addNetworkData.ip"></a-input>
+ </div>
+
+ </a-modal>
+
+ <a-modal
+ :visible="showUpdateIpModal"
+ :title="$t('label.change.ipaddress')"
+ @cancel="closeModals"
+ @ok="submitUpdateIP"
+ >
+ {{ $t('message.network.updateIp') }}
+
+ <div class="modal-form">
+ <p class="modal-form__label">{{ $t('publicip') }}:</p>
+ <a-input v-model="editIpAddressValue"></a-input>
+ </div>
+
+ </a-modal>
+
+ <a-modal
+ :visible="showSecondaryIpModal"
+ :title="$t('label.acquire.new.secondary.ip')"
+ :footer="null"
+ :closable="false"
+ class="wide-modal"
+ >
+ <p>
+ {{ $t('message.network.secondaryIP') }}
+ </p>
+ <a-divider />
+ <a-input placeholder="Enter new secondary IP address"
v-model="newSecondaryIp"></a-input>
+ <div style="margin-top: 10px; display: flex; justify-content:flex-end;">
+ <a-button @click="submitSecondaryIP" type="primary"
style="margin-right: 10px;">Add Secondary IP</a-button>
+ <a-button @click="closeModals">Close</a-button>
+ </div>
+
+ <a-divider />
+ <a-list itemLayout="vertical">
+ <a-list-item v-for="(ip, index) in secondaryIPs" :key="index">
+ <a-popconfirm
+ title="Release IP?"
+ @confirm="removeSecondaryIP(ip.id)"
+ okText="Yes"
+ cancelText="No"
+ >
+ <a-button
+ type="danger"
+ shape="circle"
+ size="small"
+ icon="delete" />
+ {{ ip.ipaddress }}
+ </a-popconfirm>
+ </a-list-item>
+ </a-list>
+ </a-modal>
+
</div>
</template>
@@ -120,12 +285,27 @@ export default {
default: false
}
},
+ inject: ['parentFetchData'],
data () {
return {
vm: {},
volumes: [],
totalStorage: 0,
- activeKey: ['1', '2', '3']
+ activeKey: ['1', '2', '3'],
+ showAddNetworkModal: false,
+ showUpdateIpModal: false,
+ showSecondaryIpModal: false,
+ addNetworkData: {
+ allNetworks: [],
+ network: '',
+ ip: ''
+ },
+ loadingNic: false,
+ editIpAddressNic: '',
+ editIpAddressValue: '',
+ secondaryIPs: [],
+ selectedNicId: '',
+ newSecondaryIp: ''
}
},
created () {
@@ -154,26 +334,339 @@ export default {
}
this.$set(this.resource, 'volumes', this.volumes)
})
+ },
+ listNetworks () {
+ api('listNetworks', {
+ listAll: 'true',
+ zoneid: this.vm.zoneid
+ }).then(response => {
+ this.addNetworkData.allNetworks =
response.listnetworksresponse.network.filter(network => !this.vm.nic.map(nic =>
nic.networkid).includes(network.id))
+ this.addNetworkData.network = this.addNetworkData.allNetworks[0].id
+ })
+ },
+ fetchSecondaryIPs (nicId) {
+ this.showSecondaryIpModal = true
+ this.selectedNicId = nicId
+ api('listNics', {
+ nicId: nicId,
+ keyword: '',
+ virtualmachineid: this.vm.id
+ }).then(response => {
+ this.secondaryIPs = response.listnicsresponse.nic[0].secondaryip
+ })
+ },
+ showAddModal () {
+ this.showAddNetworkModal = true
+ this.listNetworks()
+ },
+ closeModals () {
+ this.showAddNetworkModal = false
+ this.showUpdateIpModal = false
+ this.showSecondaryIpModal = false
+ this.addNetworkData.network = ''
+ this.addNetworkData.ip = ''
+ this.editIpAddressValue = ''
+ this.newSecondaryIp = ''
+ },
+ submitAddNetwork () {
+ const params = {}
+ params.virtualmachineid = this.vm.id
+ params.networkid = this.addNetworkData.network
+ if (this.addNetworkData.ip) {
+ params.ipaddress = this.addNetworkData.ip
+ }
+ this.showAddNetworkModal = false
+ this.loadingNic = true
+ api('addNicToVirtualMachine', params).then(response => {
+ this.$pollJob({
+ jobId: response.addnictovirtualmachineresponse.jobid,
+ successMessage: `Successfully added network`,
+ successMethod: () => {
+ this.loadingNic = false
+ this.closeModals()
+ this.parentFetchData()
+ },
+ errorMessage: 'Adding network failed',
+ errorMethod: () => {
+ this.loadingNic = false
+ this.closeModals()
+ this.parentFetchData()
+ },
+ loadingMessage: `Adding network...`,
+ catchMessage: 'Error encountered while fetching async job result',
+ catchMethod: () => {
+ this.loadingNic = false
+ this.closeModals()
+ this.parentFetchData()
+ }
+ })
+ }).catch(error => {
+ this.$notification.error({
+ message: `Error ${error.response.status}`,
+ description: error.response.data.errorresponse.errortext
+ })
+ this.loadingNic = false
+ })
+ },
+ setAsDefault (item) {
+ this.loadingNic = true
+ api('updateDefaultNicForVirtualMachine', {
+ virtualmachineid: this.vm.id,
+ nicid: item.id
+ }).then(response => {
+ this.$pollJob({
+ jobId: response.updatedefaultnicforvirtualmachineresponse.jobid,
+ successMessage: `Successfully set ${item.networkname} to default.
Please manually update the default NIC on the VM now.`,
+ successMethod: () => {
+ this.loadingNic = false
+ this.parentFetchData()
+ },
+ errorMessage: `Error setting ${item.networkname} to default`,
+ errorMethod: () => {
+ this.loadingNic = false
+ this.parentFetchData()
+ },
+ loadingMessage: `Setting ${item.networkname} to default...`,
+ catchMessage: 'Error encountered while fetching async job result',
+ catchMethod: () => {
+ this.loadingNic = false
+ this.parentFetchData()
+ }
+ })
+ }).catch(error => {
+ this.$notification.error({
+ message: `Error ${error.response.status}`,
+ description: error.response.data.errorresponse.errortext
+ })
+ this.loadingNic = false
+ })
+ },
+ submitUpdateIP () {
+ this.loadingNic = true
+ this.showUpdateIpModal = false
+ api('updateVmNicIp', {
+ nicId: this.editIpAddressNic,
+ ipaddress: this.editIpAddressValue
+ }).then(response => {
+ this.$pollJob({
+ jobId: response.updatevmnicipresponse.jobid,
+ successMessage: `Successfully updated IP Address`,
+ successMethod: () => {
+ this.loadingNic = false
+ this.closeModals()
+ this.parentFetchData()
+ },
+ errorMessage: `Error`,
+ errorMethod: () => {
+ this.loadingNic = false
+ this.closeModals()
+ this.parentFetchData()
+ },
+ loadingMessage: `Updating IP Address...`,
+ catchMessage: 'Error encountered while fetching async job result',
+ catchMethod: () => {
+ this.loadingNic = false
+ this.closeModals()
+ this.parentFetchData()
+ }
+ })
+ })
+ .catch(error => {
+ this.$notification.error({
+ message: `Error ${error.response.status}`,
+ description: error.response.data.errorresponse.errortext
+ })
+ this.loadingNic = false
+ })
+ },
+ removeNIC (item) {
+ this.loadingNic = true
+
+ api('removeNicFromVirtualMachine', {
+ nicid: item.id,
+ virtualmachineid: this.vm.id
+ }).then(response => {
+ this.$pollJob({
+ jobId: response.removenicfromvirtualmachineresponse.jobid,
+ successMessage: `Successfully removed`,
+ successMethod: () => {
+ this.loadingNic = false
+ this.parentFetchData()
+ },
+ errorMessage: `There was an error`,
+ errorMethod: () => {
+ this.loadingNic = false
+ this.parentFetchData()
+ },
+ loadingMessage: `Removing NIC...`,
+ catchMessage: 'Error encountered while fetching async job result',
+ catchMethod: () => {
+ this.loadingNic = false
+ this.parentFetchData()
+ }
+ })
+ })
+ .catch(error => {
+ this.$notification.error({
+ message: `Error ${error.response.status}`,
+ description: error.response.data.errorresponse.errortext
+ })
+ this.loadingNic = false
+ })
+ },
+ submitSecondaryIP () {
+ this.loadingNic = true
+
+ const params = {}
+ params.nicid = this.selectedNicId
+ if (this.newSecondaryIp) {
+ params.ipaddress = this.newSecondaryIp
+ }
+
+ api('addIpToNic', params).then(response => {
+ this.$pollJob({
+ jobId: response.addiptovmnicresponse.jobid,
+ successMessage: `Successfully added secondary IP Address`,
+ successMethod: () => {
+ this.loadingNic = false
+ this.fetchSecondaryIPs(this.selectedNicId)
+ this.parentFetchData()
+ },
+ errorMessage: `There was an error adding the secondary IP Address`,
+ errorMethod: () => {
+ this.loadingNic = false
+ this.fetchSecondaryIPs(this.selectedNicId)
+ this.parentFetchData()
+ },
+ loadingMessage: `Add Secondary IP address...`,
+ catchMessage: 'Error encountered while fetching async job result',
+ catchMethod: () => {
+ this.loadingNic = false
+ this.fetchSecondaryIPs(this.selectedNicId)
+ this.parentFetchData()
+ }
+ })
+ }).catch(error => {
+ this.$notification.error({
+ message: `Error ${error.response.status}`,
+ description: error.response.data.addiptovmnicresponse.errortext
+ })
+ this.loadingNic = false
+ })
+ },
+ removeSecondaryIP (id) {
+ this.loadingNic = true
+
+ api('removeIpFromNic', { id }).then(response => {
+ this.$pollJob({
+ jobId: response.removeipfromnicresponse.jobid,
+ successMessage: `Successfully removed secondary IP Address`,
+ successMethod: () => {
+ this.loadingNic = false
+ this.fetchSecondaryIPs(this.selectedNicId)
+ this.parentFetchData()
+ },
+ errorMessage: `There was an error removing the secondary IP Address`,
+ errorMethod: () => {
+ this.loadingNic = false
+ this.fetchSecondaryIPs(this.selectedNicId)
+ this.parentFetchData()
+ },
+ loadingMessage: `Removing Secondary IP address...`,
+ catchMessage: 'Error encountered while fetching async job result',
+ catchMethod: () => {
+ this.loadingNic = false
+ this.fetchSecondaryIPs(this.selectedNicId)
+ this.parentFetchData()
+ }
+ })
+ }).catch(error => {
+ this.$notification.error({
+ message: `Error ${error.response.status}`,
+ description: error.response.data.errorresponse.errortext
+ })
+ this.loadingNic = false
+ this.fetchSecondaryIPs(this.selectedNicId)
+ })
}
}
}
</script>
-<style lang="less" scoped>
-.page-header-wrapper-grid-content-main {
- width: 100%;
- height: 100%;
- min-height: 100%;
- transition: 0.3s;
- .vm-detail {
- .svg-inline--fa {
- margin-left: -1px;
- margin-right: 8px;
+<style lang="scss" scoped>
+ .page-header-wrapper-grid-content-main {
+ width: 100%;
+ height: 100%;
+ min-height: 100%;
+ transition: 0.3s;
+ .vm-detail {
+ .svg-inline--fa {
+ margin-left: -1px;
+ margin-right: 8px;
+ }
+ span {
+ margin-left: 10px;
+ }
+ margin-bottom: 8px;
}
- span {
- margin-left: 10px;
+ }
+
+ .list {
+ margin-top: 20px;
+
+ &__item {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+
+ @media (min-width: 760px) {
+ flex-direction: row;
+ align-items: center;
+ }
}
- margin-bottom: 8px;
}
-}
+
+ .modal-form {
+ display: flex;
+ flex-direction: column;
+
+ &__label {
+ margin-top: 20px;
+ margin-bottom: 5px;
+ font-weight: bold;
+
+ &--no-margin {
+ margin-top: 0;
+ }
+ }
+ }
+
+ .actions {
+ display: flex;
+ margin-left: -24px;
+
+ button {
+ &:not(:last-child) {
+ margin-right: 10px;
+ }
+ }
+
+ @media (min-width: 760px) {
+ flex-direction: column;
+ margin-left: 24px;
+
+ button {
+ &:not(:last-child) {
+ margin-bottom: 10px;
+ margin-right: 0;
+ }
+ }
+ }
+ }
+</style>
+
+<style lang="scss">
+ .wide-modal {
+ min-width: 50vw;
+ }
</style>
diff --git a/src/views/infra/InfraSummary.vue b/src/views/infra/InfraSummary.vue
index d2af424..1ef76d0 100644
--- a/src/views/infra/InfraSummary.vue
+++ b/src/views/infra/InfraSummary.vue
@@ -19,23 +19,24 @@
<a-row :gutter="24">
<a-col :md="24">
<a-card class="breadcrumb-card">
- <a-col :md="14">
+ <a-col :md="24" style="display: flex">
<breadcrumb style="padding-top: 6px" />
- </a-col>
- <a-col :md="10">
<a-button
- style="margin-left: 10px; float: right"
- @click="fetchData()"
- icon="reload"
+ style="margin-left: 12px; margin-top: 4px"
:loading="loading"
- type="primary">
- {{ $t('Refresh') }}
+ icon="reload"
+ size="small"
+ shape="round"
+ @click="fetchData()" >
+ {{ $t('refresh') }}
</a-button>
<a-button
- style="margin-left: 10px; float: right"
- @click="sslFormVisible = true"
- icon="safety-certificate">
- {{ $t('SSL Certificate') }}
+ style="margin-left: 12px; margin-top: 4px"
+ icon="safety-certificate"
+ size="small"
+ shape="round"
+ @click="sslFormVisible = true">
+ {{ $t('Setup SSL Certificate') }}
</a-button>
<a-modal
:title="$t('SSL Certificate')"