You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2019/12/06 19:23:06 UTC

[cloudstack-primate] branch master updated: infra: custom SSL cert setup form (#54)

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 aaf212f  infra: custom SSL cert setup form (#54)
aaf212f is described below

commit aaf212f25337eff1eeef3f876bb37218eea2123f
Author: Ritchie Vincent <rf...@gmail.com>
AuthorDate: Fri Dec 6 19:22:53 2019 +0000

    infra: custom SSL cert setup form (#54)
    
    This fixes #33
    
    Signed-off-by: Rohit Yadav <ro...@shapeblue.com>
---
 src/config/router.js             |   8 +-
 src/views/AutogenView.vue        |   9 +-
 src/views/infra/InfraSummary.vue | 299 ++++++++++++++++++++++++++++++++++-----
 3 files changed, 269 insertions(+), 47 deletions(-)

diff --git a/src/config/router.js b/src/config/router.js
index 28e61b8..51a61e9 100644
--- a/src/config/router.js
+++ b/src/config/router.js
@@ -49,13 +49,13 @@ export function generateRouterMap (section) {
       var route = {
         name: child.name,
         path: '/' + child.name,
+        hidden: child.hidden,
         meta: {
           title: child.title,
           name: child.name,
           keepAlive: true,
           icon: child.icon,
           docHelp: child.docHelp,
-          hidden: child.hidden,
           permission: child.permission,
           resourceType: child.resourceType,
           params: child.params ? child.params : {},
@@ -69,13 +69,13 @@ export function generateRouterMap (section) {
         children: [
           {
             path: '/' + child.name + '/:id',
+            hidden: child.hidden,
             meta: {
               title: child.title,
               name: child.name,
               keepAlive: true,
               icon: child.icon,
               docHelp: child.docHelp,
-              hidden: child.hidden,
               permission: child.permission,
               resourceType: child.resourceType,
               params: child.params ? child.params : {},
@@ -96,6 +96,7 @@ export function generateRouterMap (section) {
           map.children.push({
             name: action.api,
             icon: child.icon,
+            hidden: true,
             path: '/action/' + action.api,
             meta: {
               title: child.title,
@@ -103,8 +104,7 @@ export function generateRouterMap (section) {
               keepAlive: true,
               permission: [action.api]
             },
-            component: action.component,
-            hidden: true
+            component: action.component
           })
         })
       }
diff --git a/src/views/AutogenView.vue b/src/views/AutogenView.vue
index 6ecb3e7..4e07402 100644
--- a/src/views/AutogenView.vue
+++ b/src/views/AutogenView.vue
@@ -17,7 +17,7 @@
 
 <template>
   <div>
-    <a-card class="mobile-breadcrumb">
+    <a-card class="breadcrumb-card">
       <a-row>
         <a-col :span="14">
           <breadcrumb style="padding-top: 6px" />
@@ -402,7 +402,8 @@ export default {
         // handle error
         this.$notification.error({
           message: 'Request Failed',
-          description: error.response.headers['x-description']
+          description: error.response.headers['x-description'],
+          duration: 0
         })
         if (error.response.status === 431) {
           this.$router.push({ path: '/exception/404' })
@@ -514,7 +515,7 @@ export default {
           this.fetchData()
         } else if (result.jobstatus === 2) {
           this.fetchData()
-        } else {
+        } else if (result.jobstatus === 0) {
           this.$message
             .loading(this.$t(action.label) + ' in progress for ' + this.resource.name, 3)
             .then(() => this.pollActionCompletion(jobId, action))
@@ -636,7 +637,7 @@ export default {
 
 <style scoped>
 
-.mobile-breadcrumb {
+.breadcrumb-card {
   margin-left: -24px;
   margin-right: -24px;
   margin-top: -16px;
diff --git a/src/views/infra/InfraSummary.vue b/src/views/infra/InfraSummary.vue
index f2a057d..d2af424 100644
--- a/src/views/infra/InfraSummary.vue
+++ b/src/views/infra/InfraSummary.vue
@@ -17,36 +17,125 @@
 
 <template>
   <a-row :gutter="24">
-    <a-col :md="18">
-      <a-card>
-        <breadcrumb />
-      </a-card>
-    </a-col>
-    <a-col
-      :md="6" >
-      <a-card>
-        <a-button
-          style="margin-left: 10px; float: right"
-          @click="fetchData()"
-          icon="reload"
-          :loading="loading"
-          type="primary">
-          {{ $t('Refresh') }}
-        </a-button>
-        <a-button
-          style="margin-left: 10px; float: right"
-          @click="sslFormVisible = true"
-          icon="safety-certificate">
-          {{ $t('SSL Certificate') }}
-        </a-button>
-        <a-modal
-          :title="$t('SSL Certificate')"
-          v-model="sslFormVisible"
-          @ok="handle">
-          <p>Some contents...</p>
-          <p>Some contents...</p>
-          <p>Some contents...</p>
-        </a-modal>
+    <a-col :md="24">
+      <a-card class="breadcrumb-card">
+        <a-col :md="14">
+          <breadcrumb style="padding-top: 6px" />
+        </a-col>
+        <a-col :md="10">
+          <a-button
+            style="margin-left: 10px; float: right"
+            @click="fetchData()"
+            icon="reload"
+            :loading="loading"
+            type="primary">
+            {{ $t('Refresh') }}
+          </a-button>
+          <a-button
+            style="margin-left: 10px; float: right"
+            @click="sslFormVisible = true"
+            icon="safety-certificate">
+            {{ $t('SSL Certificate') }}
+          </a-button>
+          <a-modal
+            :title="$t('SSL Certificate')"
+            :visible="sslFormVisible"
+            :footer="null"
+            @cancel="sslModalClose">
+            <p>
+              Please submit a new X.509 compliant SSL certificate chain to be updated to each console proxy and secondary storage virtual instance:
+            </p>
+
+            <a-form @submit.prevent="handleSslFormSubmit" ref="sslForm" :form="form">
+              <a-form-item label="Root certificate" :required="true">
+                <a-textarea
+                  id="rootCert"
+                  rows="2"
+                  placeholder="Root certificate"
+                  :autoFocus="true"
+                  name="rootCert"
+                  v-decorator="[
+                    'root',
+                    {rules: [{ required: true, message: 'Required' }], validateTrigger:'change'}
+                  ]"
+                ></a-textarea>
+              </a-form-item>
+
+              <transition-group name="fadeInUp" tag="div">
+                <a-form-item
+                  v-for="(item, index) in intermediateCertificates"
+                  :key="`key-${index}`"
+                  class="intermediate-certificate"
+                  :label="`Intermediate certificate ${index + 1}`">
+                  <a-textarea
+                    :id="`intermediateCert${index}`"
+                    rows="2"
+                    :placeholder="`Intermediate certificate ${index + 1}`"
+                    :name="`intermediateCert${index}`"
+                    v-decorator="[
+                      `intermediate${index + 1}`,
+                      {validateTrigger:'change'}
+                    ]"
+                  ></a-textarea>
+                </a-form-item>
+              </transition-group>
+
+              <a-form-item>
+                <a-button @click="addIntermediateCert">
+                  <a-icon type="plus-circle" />
+                  Add intermediate certificate
+                </a-button>
+              </a-form-item>
+
+              <a-form-item label="Server certificate" :required="true">
+                <a-textarea
+                  id="serverCert"
+                  rows="2"
+                  placeholder="Server certificate"
+                  name="serverCert"
+                  v-decorator="[
+                    'server',
+                    {rules: [{ required: true, message: 'Required' }], validateTrigger:'change'}
+                  ]"
+                ></a-textarea>
+              </a-form-item>
+
+              <a-form-item label="PKCS#8 Private Key" :required="true">
+                <a-textarea
+                  id="pkcsKey"
+                  rows="2"
+                  placeholder="PKCS#8 Private Key"
+                  name="pkcsKey"
+                  v-decorator="[
+                    'pkcs',
+                    {rules: [{ required: true, message: 'Required' }], validateTrigger:'change'}
+                  ]"
+                ></a-textarea>
+              </a-form-item>
+
+              <a-form-item label="DNS Domain Suffix (i.e., xyz.com)" :required="true">
+                <a-input
+                  id="dnsSuffix"
+                  placeholder="DNS Domain Suffix (i.e., xyz.com)"
+                  name="dnsSuffix"
+                  v-decorator="[
+                    'dns',
+                    {rules: [{ required: true, message: 'Required' }], validateTrigger:'change'}
+                  ]"
+                ></a-input>
+              </a-form-item>
+
+              <a-form-item class="controls">
+                <a-button @click="this.sslModalClose" type="danger" class="close-button">
+                  Cancel
+                </a-button>
+                <a-button type="primary" htmlType="submit" :loading="sslFormSubmitting">
+                  Submit
+                </a-button>
+              </a-form-item>
+            </a-form>
+          </a-modal>
+        </a-col>
       </a-card>
     </a-col>
     <a-col
@@ -86,9 +175,15 @@ export default {
       routes: {},
       sections: ['zones', 'pods', 'clusters', 'hosts', 'storagepools', 'imagestores', 'systemvms', 'routers', 'cpusockets', 'managementservers', 'alerts'],
       sslFormVisible: false,
-      stats: {}
+      stats: {},
+      intermediateCertificates: [],
+      sslFormSubmitting: false,
+      maxCerts: 0
     }
   },
+  beforeCreate () {
+    this.form = this.$form.createForm(this)
+  },
   mounted () {
     this.fetchData()
   },
@@ -115,17 +210,143 @@ export default {
         this.loading = false
       })
     },
-    handleSslForm (e) {
-      console.log(e)
+
+    resetSslFormData () {
+      this.form.resetFields()
+      this.intermediateCertificates = []
+      this.sslFormSubmitting = false
+      this.sslFormVisible = false
+    },
+
+    sslModalClose () {
+      this.resetSslFormData()
+    },
+
+    addIntermediateCert () {
+      this.intermediateCertificates.push('')
+    },
+
+    pollActionCompletion (jobId, count) {
+      api('queryAsyncJobResult', { jobid: jobId }).then(json => {
+        const result = json.queryasyncjobresultresponse
+        if (result.jobstatus === 1 && this.maxCerts === count) {
+          console.log(result)
+          console.log(this.maxCerts)
+          console.log(count)
+          this.$message.success('Certificate Uploaded: ' + result.jobresult.customcertificate.message)
+          this.$notification.success({
+            message: 'Certificate Uploaded',
+            description: result.jobresult.customcertificate.message || 'Certificate successfully uploaded'
+          })
+        } else if (result.jobstatus === 2) {
+          this.$notification.error({
+            message: 'Certificate Upload Failed',
+            description: result.jobresult.errortext || 'Failed to update SSL Certificate. Failed to pass certificate validation check',
+            duration: 0
+          })
+        } else if (result.jobstatus === 0) {
+          this.$message
+            .loading('Certificate upload in progress: ' + count, 2)
+            .then(() => this.pollActionCompletion(jobId, count))
+        }
+      }).catch(e => {
+        console.log('Error encountered while fetching async job result' + e)
+      })
+    },
+
+    handleSslFormSubmit () {
+      this.sslFormSubmitting = true
+
+      this.form.validateFields(errors => {
+        if (errors) {
+          this.sslFormSubmitting = false
+          return
+        }
+
+        const formValues = this.form.getFieldsValue()
+
+        this.maxCerts = 2 + Object.keys(formValues).length
+        let count = 1
+        let data = {
+          id: count,
+          certificate: formValues.root,
+          domainsuffix: formValues.dns,
+          name: 'root'
+        }
+        api('uploadCustomCertificate', {}, 'POST', data).then(response => {
+          this.pollActionCompletion(response.uploadcustomcertificateresponse.jobid, count)
+        }).then(() => {
+          this.sslModalClose()
+        })
+
+        Object.keys(formValues).forEach(key => {
+          if (key.includes('intermediate')) {
+            count = count + 1
+            const data = {
+              id: count,
+              certificate: formValues[key],
+              domainsuffix: formValues.dns,
+              name: key
+            }
+            api('uploadCustomCertificate', {}, 'POST', data).then(response => {
+              this.pollActionCompletion(response.uploadcustomcertificateresponse.jobid, count)
+            }).then(() => {
+              this.sslModalClose()
+            })
+          }
+        })
+
+        count = count <= 2 ? 3 : count + 1
+        data = {
+          id: count,
+          certificate: formValues.server,
+          domainsuffix: formValues.dns,
+          privatekey: formValues.pkcs
+        }
+        api('uploadCustomCertificate', {}, 'POST', data).then(response => {
+          this.pollActionCompletion(response.uploadcustomcertificateresponse.jobid, count)
+        }).then(() => {
+          this.sslModalClose()
+        })
+      })
     }
   }
 }
 </script>
 
-<style lang="less" scoped>
-.chart-card-inner {
-  text-align: center;
-  white-space: nowrap;
-  overflow: hidden;
-}
+<style lang="scss" scoped>
+
+  .breadcrumb-card {
+    margin-left: -36px;
+    margin-right: -36px;
+    margin-top: -16px;
+    margin-bottom: 12px;
+  }
+
+  .chart-card-inner {
+    text-align: center;
+    white-space: nowrap;
+    overflow: hidden;
+  }
+  .intermediate-certificate {
+    opacity: 1;
+    transform: none;
+    transition: opacity 0.2s ease 0s, transform 0.5s ease;
+    will-change: transform;
+  }
+  .intermediate-certificate.fadeInUp-enter-active {
+    opacity: 0;
+    transform: translateY(10px);
+    transition: none;
+  }
+  .controls {
+    display: flex;
+    justify-content: flex-end;
+  }
+  .close-button {
+    margin-right: 20px;
+  }
+  .ant-form-item {
+    margin-bottom: 10px;
+  }
 </style>