cloudstack-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ro...@apache.org
Subject [cloudstack-primate] branch master updated: compute: NICs and IPs management (#71)
Date Thu, 19 Dec 2019 02:55:23 GMT
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 <rfcvincent@gmail.com>
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 <rohit.yadav@shapeblue.com>
---
 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')"


Mime
View raw message