You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by nv...@apache.org on 2022/03/30 10:06:09 UTC

[cloudstack] branch main updated: UI - Fixes UI bugs (#6162)

This is an automated email from the ASF dual-hosted git repository.

nvazquez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new 1a304cc  UI - Fixes UI bugs (#6162)
1a304cc is described below

commit 1a304ccf68a75d835cf5e723b56b57155be6ebda
Author: Hoang Nguyen <ho...@unitech.vn>
AuthorDate: Wed Mar 30 17:05:36 2022 +0700

    UI - Fixes UI bugs (#6162)
    
    * fixes
    
    * remove console
    
    * fix hidden clear notification button
    
    * fixes
    
    * fixes
    
    * fixes navigation to ssh from comments
---
 ui/src/components/header/HeaderNotice.vue      |  2 -
 ui/src/components/menu/SMenu.vue               |  2 -
 ui/src/components/page/GlobalLayout.vue        |  5 ++
 ui/src/components/page/PageHeader.vue          |  4 --
 ui/src/components/page/PageLayout.vue          |  3 -
 ui/src/components/view/ActionButton.vue        |  4 +-
 ui/src/components/view/InfoCard.vue            |  4 +-
 ui/src/components/view/ListView.vue            |  9 +--
 ui/src/components/widgets/Breadcrumb.vue       |  2 -
 ui/src/components/widgets/TooltipButton.vue    | 67 +++++++++++-----------
 ui/src/core/lazy_lib/icons_use.js              |  2 +
 ui/src/layouts/UserLayout.vue                  |  5 ++
 ui/src/style/vars.less                         | 18 +++++-
 ui/src/views/AutogenView.vue                   |  3 +
 ui/src/views/auth/Login.vue                    |  2 +
 ui/src/views/compute/DeployVM.vue              |  4 +-
 ui/src/views/compute/DestroyVM.vue             | 77 +++++++++++++-------------
 ui/src/views/dashboard/UsageDashboard.vue      | 17 ++++--
 ui/src/views/dashboard/UsageDashboardChart.vue |  2 -
 ui/src/views/infra/InfraSummary.vue            |  4 +-
 ui/src/views/project/AccountsTab.vue           | 52 ++++++++---------
 ui/src/views/project/InvitationsTemplate.vue   | 25 ++++++---
 22 files changed, 172 insertions(+), 141 deletions(-)

diff --git a/ui/src/components/header/HeaderNotice.vue b/ui/src/components/header/HeaderNotice.vue
index 440c05a..fe9c579 100644
--- a/ui/src/components/header/HeaderNotice.vue
+++ b/ui/src/components/header/HeaderNotice.vue
@@ -68,11 +68,9 @@
 
 <script>
 import store from '@/store'
-import RenderIcon from '@/utils/renderIcon'
 
 export default {
   name: 'HeaderNotice',
-  components: { RenderIcon },
   data () {
     return {
       loading: false,
diff --git a/ui/src/components/menu/SMenu.vue b/ui/src/components/menu/SMenu.vue
index d18fd1a..2c828a8 100644
--- a/ui/src/components/menu/SMenu.vue
+++ b/ui/src/components/menu/SMenu.vue
@@ -61,11 +61,9 @@
 </template>
 
 <script>
-import RenderIcon from '@/utils/renderIcon'
 
 export default {
   name: 'SMenu',
-  components: { RenderIcon },
   props: {
     menu: {
       type: Array,
diff --git a/ui/src/components/page/GlobalLayout.vue b/ui/src/components/page/GlobalLayout.vue
index 597b584..571a68e 100644
--- a/ui/src/components/page/GlobalLayout.vue
+++ b/ui/src/components/page/GlobalLayout.vue
@@ -209,6 +209,11 @@ export default {
         }, 16)
       })
     }
+    const countNotify = this.$store.getters.countNotify
+    this.showClear = false
+    if (countNotify && countNotify > 0) {
+      this.showClear = true
+    }
   },
   beforeUnmount () {
     document.body.classList.remove('dark')
diff --git a/ui/src/components/page/PageHeader.vue b/ui/src/components/page/PageHeader.vue
index e4990e2..01e28ed 100644
--- a/ui/src/components/page/PageHeader.vue
+++ b/ui/src/components/page/PageHeader.vue
@@ -51,13 +51,9 @@
 </template>
 
 <script>
-import RenderIcon from '@/utils/renderIcon'
 
 export default {
   name: 'PageHeader',
-  components: {
-    RenderIcon
-  },
   props: {
     title: {
       type: String,
diff --git a/ui/src/components/page/PageLayout.vue b/ui/src/components/page/PageLayout.vue
index b2be039..e0bf6f9 100644
--- a/ui/src/components/page/PageLayout.vue
+++ b/ui/src/components/page/PageLayout.vue
@@ -54,14 +54,11 @@
 </template>
 
 <script>
-import RenderIcon from '@/utils/renderIcon'
-
 import PageHeader from './PageHeader'
 
 export default {
   name: 'LayoutContent',
   components: {
-    RenderIcon,
     PageHeader
   },
   // ['desc', 'logo', 'title', 'avatar', 'linkList', 'extraImage']
diff --git a/ui/src/components/view/ActionButton.vue b/ui/src/components/view/ActionButton.vue
index 8b2fb30..da4bcbf 100644
--- a/ui/src/components/view/ActionButton.vue
+++ b/ui/src/components/view/ActionButton.vue
@@ -80,13 +80,11 @@
 
 <script>
 import { api } from '@/api'
-import RenderIcon from '@/utils/renderIcon'
 import Console from '@/components/widgets/Console'
 
 export default {
   name: 'ActionButton',
   components: {
-    RenderIcon,
     Console
   },
   data () {
@@ -94,7 +92,7 @@ export default {
       actionBadge: {}
     }
   },
-  mounted () {
+  created () {
     this.handleShowBadge()
   },
   props: {
diff --git a/ui/src/components/view/InfoCard.vue b/ui/src/components/view/InfoCard.vue
index 1be2925..a1e4ef3 100644
--- a/ui/src/components/view/InfoCard.vue
+++ b/ui/src/components/view/InfoCard.vue
@@ -697,7 +697,6 @@
 
 <script>
 import { api } from '@/api'
-import RenderIcon from '@/utils/renderIcon'
 import Console from '@/components/widgets/Console'
 import OsLogo from '@/components/widgets/OsLogo'
 import Status from '@/components/widgets/Status'
@@ -714,8 +713,7 @@ export default {
     Status,
     TooltipButton,
     UploadResourceIcon,
-    ResourceIcon,
-    RenderIcon
+    ResourceIcon
   },
   props: {
     resource: {
diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue
index fa1f7da..abdd575 100644
--- a/ui/src/components/view/ListView.vue
+++ b/ui/src/components/view/ListView.vue
@@ -98,7 +98,7 @@
         </span>
 
         <span v-if="record.hasannotations">
-          <span v-if="record.id">
+          <span v-if="record.id && $route.path !== '/ssh'">
             <router-link :to="{ path: $route.path + '/' + record.id }">{{ text }}</router-link>
             <router-link :to="{ path: $route.path + '/' + record.id, query: { tab: 'comments' } }"><message-filled style="padding-left: 10px" size="small"/></router-link>
           </span>
@@ -398,7 +398,6 @@ import Status from '@/components/widgets/Status'
 import QuickView from '@/components/view/QuickView'
 import TooltipButton from '@/components/widgets/TooltipButton'
 import ResourceIcon from '@/components/view/ResourceIcon'
-import RenderIcon from '@/utils/renderIcon'
 
 export default {
   name: 'ListView',
@@ -407,8 +406,7 @@ export default {
     Status,
     QuickView,
     TooltipButton,
-    ResourceIcon,
-    RenderIcon
+    ResourceIcon
   },
   props: {
     columns: {
@@ -682,6 +680,9 @@ export default {
       return record.nic.filter(e => { return e.ip6address }).map(e => { return e.ip6address }).join(', ') || text
     },
     generateCommentsPath (record) {
+      if (this.entityTypeToPath(record.entitytype) === 'ssh') {
+        return '/' + this.entityTypeToPath(record.entitytype) + '/' + record.entityname
+      }
       return '/' + this.entityTypeToPath(record.entitytype) + '/' + record.entityid
     },
     generateHumanReadableEntityType (record) {
diff --git a/ui/src/components/widgets/Breadcrumb.vue b/ui/src/components/widgets/Breadcrumb.vue
index 83d88df..d871390 100644
--- a/ui/src/components/widgets/Breadcrumb.vue
+++ b/ui/src/components/widgets/Breadcrumb.vue
@@ -61,11 +61,9 @@
 </template>
 
 <script>
-import RenderIcon from '@/utils/renderIcon'
 
 export default {
   name: 'Breadcrumb',
-  components: { RenderIcon },
   props: {
     resource: {
       type: Object,
diff --git a/ui/src/components/widgets/TooltipButton.vue b/ui/src/components/widgets/TooltipButton.vue
index 8e6e4e2..c0a2a46 100644
--- a/ui/src/components/widgets/TooltipButton.vue
+++ b/ui/src/components/widgets/TooltipButton.vue
@@ -20,47 +20,46 @@
     <template #title v-if="tooltip">
       {{ tooltip }}
     </template>
-    <a-button
-      style="margin-left: -5px"
-      v-if="copyResource"
-      shape="circle"
-      :size="size"
-      :type="type"
-      :danger="danger"
-      :disabled="disabled"
-      :class="buttonClass"
-      :loading="loading"
-      @click="handleClicked()"
-      v-clipboard:copy="copyResource" >
-      <template #icon v-if="icon"><render-icon :icon="icon" /></template>
-      <template v-if="iconType && iconTwoToneColor">
-        <render-icon :icon="iconType" :props="{ theme: 'twoTone', twoToneColor: iconTwoToneColor }" />
-      </template>
-    </a-button>
-    <a-button
-      v-else
-      shape="circle"
-      :size="size"
-      :type="type"
-      :danger="danger"
-      :disabled="disabled"
-      :class="buttonClass"
-      :loading="loading"
-      @click="handleClicked()" >
-      <template #icon v-if="icon"><render-icon :icon="icon" /></template>
-      <template v-if="iconType && iconTwoToneColor">
-        <render-icon :icon="iconType" :props="{ theme: 'twoTone', twoToneColor: iconTwoToneColor }" />
-      </template>
-    </a-button>
+    <span style="margin-right: 5px">
+      <a-button
+        v-if="copyResource"
+        shape="circle"
+        :size="size"
+        :type="type"
+        :danger="danger"
+        :disabled="disabled"
+        :class="buttonClass"
+        :loading="loading"
+        @click="handleClicked()"
+        v-clipboard:copy="copyResource" >
+        <template #icon v-if="icon"><render-icon :icon="icon" /></template>
+        <template v-if="iconType && iconTwoToneColor">
+          <render-icon :icon="iconType" :props="{ theme: 'twoTone', twoToneColor: iconTwoToneColor }" />
+        </template>
+      </a-button>
+      <a-button
+        v-else
+        shape="circle"
+        :size="size"
+        :type="type"
+        :danger="danger"
+        :disabled="disabled"
+        :class="buttonClass"
+        :loading="loading"
+        @click="handleClicked()" >
+        <template #icon v-if="icon"><render-icon :icon="icon" /></template>
+        <template v-if="iconType && iconTwoToneColor">
+          <render-icon :icon="iconType" :props="{ theme: 'twoTone', twoToneColor: iconTwoToneColor }" />
+        </template>
+      </a-button>
+    </span>
   </a-tooltip>
 </template>
 
 <script>
-import RenderIcon from '@/utils/renderIcon'
 
 export default {
   name: 'TooltipButton',
-  components: { RenderIcon },
   props: {
     tooltip: {
       type: String,
diff --git a/ui/src/core/lazy_lib/icons_use.js b/ui/src/core/lazy_lib/icons_use.js
index a01b287..9382775 100644
--- a/ui/src/core/lazy_lib/icons_use.js
+++ b/ui/src/core/lazy_lib/icons_use.js
@@ -152,6 +152,7 @@ import {
   UploadOutlined,
   WifiOutlined
 } from '@ant-design/icons-vue'
+import renderIcon from '@/utils/renderIcon'
 
 export default {
   install: (app) => {
@@ -290,5 +291,6 @@ export default {
     app.component('UserOutlined', UserOutlined)
     app.component('UploadOutlined', UploadOutlined)
     app.component('WifiOutlined', WifiOutlined)
+    app.component('renderIcon', renderIcon)
   }
 }
diff --git a/ui/src/layouts/UserLayout.vue b/ui/src/layouts/UserLayout.vue
index 9bce99c..24ed6b4 100644
--- a/ui/src/layouts/UserLayout.vue
+++ b/ui/src/layouts/UserLayout.vue
@@ -78,6 +78,11 @@ export default {
     if (layoutMode === 'dark') {
       document.body.classList.add('dark-mode')
     }
+    const countNotify = this.$store.getters.countNotify
+    this.showClear = false
+    if (countNotify && countNotify > 0) {
+      this.showClear = true
+    }
   },
   beforeUnmount () {
     document.body.classList.remove('userLayout')
diff --git a/ui/src/style/vars.less b/ui/src/style/vars.less
index 1686da9..0c3bff4 100644
--- a/ui/src/style/vars.less
+++ b/ui/src/style/vars.less
@@ -47,7 +47,7 @@
 @box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.15);
 @loading-color: @primary-color;
 
-.ant-layout.ant-layout-has-sider>.ant-layout,
+// .ant-layout.ant-layout-has-sider>.ant-layout,
 .ant-layout.ant-layout-has-sider>.ant-layout-content {
   width: auto;
 }
@@ -171,6 +171,18 @@ a {
   fill: @navigation-text-color;
 }
 
+.ant-menu-dark .ant-menu-item {
+  .anticon .custom-icon {
+    fill: @project-nav-text-color;
+  }
+
+  &.ant-menu-item-selected {
+    .anticon .custom-icon {
+      fill: #fff;
+    }
+  }
+}
+
 .ant-menu-item a {
   .anticon .custom-icon {
     transition: fill 0.3s ease-out;
@@ -180,6 +192,10 @@ a {
   }
 }
 
+.ant-menu-dark .ant-menu-item:hover .anticon .custom-icon {
+  fill: #fff;
+}
+
 .ant-menu-item:hover,
 .ant-menu-item-active,
 .ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open,
diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue
index 4ddf9f2..beac49f 100644
--- a/ui/src/views/AutogenView.vue
+++ b/ui/src/views/AutogenView.vue
@@ -1190,6 +1190,9 @@ export default {
                 })
               }
             }
+            if ('successMethod' in action) {
+              action.successMethod(this, result)
+            }
             resolve(true)
           },
           errorMethod: () => {
diff --git a/ui/src/views/auth/Login.vue b/ui/src/views/auth/Login.vue
index 2e8ff78..f513ddb 100644
--- a/ui/src/views/auth/Login.vue
+++ b/ui/src/views/auth/Login.vue
@@ -282,6 +282,8 @@ export default {
       })
     },
     loginSuccess (res) {
+      this.$notification.destroy()
+      this.$store.commit('SET_COUNT_NOTIFY', 0)
       this.$router.push({ path: '/dashboard' }).catch(() => {})
     },
     requestFailed (err) {
diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue
index c65f48b..883b0fc 100644
--- a/ui/src/views/compute/DeployVM.vue
+++ b/ui/src/views/compute/DeployVM.vue
@@ -1648,7 +1648,6 @@ export default {
     handleSubmitAndStay (e) {
       this.form.stayonpage = true
       this.handleSubmit(e.domEvent)
-      this.form.stayonpage = false
     },
     handleSubmit (e) {
       console.log('wizard submit')
@@ -1884,6 +1883,9 @@ export default {
         }).catch(error => {
           this.$notifyError(error)
           this.loading.deploy = false
+        }).finally(() => {
+          this.form.stayonpage = false
+          this.loading.deploy = false
         })
       }).catch(err => {
         this.formRef.value.scrollToField(err.errorFields[0].name)
diff --git a/ui/src/views/compute/DestroyVM.vue b/ui/src/views/compute/DestroyVM.vue
index 5247cae..de7b6da 100644
--- a/ui/src/views/compute/DestroyVM.vue
+++ b/ui/src/views/compute/DestroyVM.vue
@@ -83,43 +83,45 @@
         </div>
         <div v-if="selectedRowKeys.length > 0" class="row-keys">
           <a-divider />
-          <a-table
-            v-if="selectedRowKeys.length > 0"
-            size="middle"
-            :columns="chosenColumns"
-            :dataSource="selectedItems"
-            :rowKey="(record, idx) => record.id || record.name || record.usageType || idx + '-' + Math.random()"
-            :pagination="true"
-            style="overflow-y: auto"
-          >
-            <template
-              #expandedRowRender="record"
-              style="margin: 0">
-              <a-form-item :label="$t('label.delete.volumes')" v-if="listVolumes[record.id].opts.length > 0">
-                <a-select
-                  mode="multiple"
-                  showSearch
-                  optionFilterProp="label"
-                  :filterOption="(input, option) => {
-                    return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
-                  }"
-                  :loading="listVolumes[record.id].loading"
-                  :placeholder="$t('label.delete.volumes')"
-                  @change="(value) => onChangeVolume(record.id, value)">
-                  <a-select-option v-for="item in listVolumes[record.id].opts" :key="item.id">
-                    {{ item.name || item.description }}
-                  </a-select-option>
-                </a-select>
-              </a-form-item>
-              <span v-else v-html="$t('label.volume.empty')" />
-            </template>
-          </a-table>
-          <a-form-item v-if="$store.getters.userInfo.roletype === 'Admin' || $store.getters.features.allowuserexpungerecovervm">
-            <template #label>
-              <tooltip-label :title="$t('label.expunge')" :tooltip="apiParams.expunge.description"/>
-            </template>
-            <a-switch v-model:checked="expunge" v-focus="true" />
-          </a-form-item>
+          <a-form layout="vertical">
+            <a-table
+              v-if="selectedRowKeys.length > 0"
+              size="middle"
+              :columns="chosenColumns"
+              :dataSource="selectedItems"
+              :rowKey="(record, idx) => record.id || record.name || record.usageType || idx + '-' + Math.random()"
+              :pagination="true"
+              style="overflow-y: auto"
+            >
+              <template
+                #expandedRowRender="{ record } "
+                style="margin: 0">
+                <a-form-item :label="$t('label.delete.volumes')" v-if="listVolumes[record.id].opts.length > 0">
+                  <a-select
+                    mode="multiple"
+                    showSearch
+                    optionFilterProp="label"
+                    :filterOption="(input, option) => {
+                      return option.children[0].children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+                    }"
+                    :loading="listVolumes[record.id].loading"
+                    :placeholder="$t('label.delete.volumes')"
+                    @change="(value) => onChangeVolume(record.id, value)">
+                    <a-select-option v-for="item in listVolumes[record.id].opts" :key="item.id">
+                      {{ item.name || item.description }}
+                    </a-select-option>
+                  </a-select>
+                </a-form-item>
+                <span v-else v-html="$t('label.volume.empty')" />
+              </template>
+            </a-table>
+            <a-form-item v-if="$store.getters.userInfo.roletype === 'Admin' || $store.getters.features.allowuserexpungerecovervm">
+              <template #label>
+                <tooltip-label :title="$t('label.expunge')" :tooltip="apiParams.expunge.description"/>
+              </template>
+              <a-switch v-model:checked="expunge" v-focus="true" />
+            </a-form-item>
+          </a-form>
         </div>
 
         <div :span="24" class="action-button">
@@ -214,7 +216,6 @@ export default {
             this.listVolumes[item.id].loading = false
             this.listVolumes[item.id].opts = item.volumes || []
           })
-          this.$forceUpdate()
         })
       }
     },
diff --git a/ui/src/views/dashboard/UsageDashboard.vue b/ui/src/views/dashboard/UsageDashboard.vue
index 7bf1f13..ec43593 100644
--- a/ui/src/views/dashboard/UsageDashboard.vue
+++ b/ui/src/views/dashboard/UsageDashboard.vue
@@ -52,7 +52,7 @@
                 :bordered="false"
                 :loading="loading"
                 :style="stat.bgcolor ? { 'background': stat.bgcolor } : {}">
-                <router-link :to="{ path: stat.path, query: stat.query }">
+                <router-link v-if="stat.path" :to="{ path: stat.path, query: stat.query }">
                   <div
                     class="usage-dashboard-chart-card-inner">
                     <h3>{{ stat.name }}</h3>
@@ -68,8 +68,7 @@
         </a-card>
       </a-row>
     </a-col>
-    <a-col
-      :xl="8">
+    <a-col :xl="8">
       <chart-card :loading="loading" >
         <div class="usage-dashboard-chart-card-inner">
           <a-button>
@@ -100,7 +99,6 @@
 <script>
 import { api } from '@/api'
 import store from '@/store'
-import RenderIcon from '@/utils/renderIcon'
 
 import ChartCard from '@/components/widgets/ChartCard'
 import UsageDashboardChart from '@/views/dashboard/UsageDashboardChart'
@@ -109,8 +107,7 @@ export default {
   name: 'UsageDashboard',
   components: {
     ChartCard,
-    UsageDashboardChart,
-    RenderIcon
+    UsageDashboardChart
   },
   props: {
     resource: {
@@ -142,6 +139,8 @@ export default {
       (newValue, oldValue) => {
         if (newValue && newValue.id && (!oldValue || newValue.id !== oldValue.id)) {
           this.fetchData()
+        } else if (store.getters.userInfo.roletype !== 'Admin') {
+          this.fetchData()
         }
       }
     )
@@ -273,4 +272,10 @@ export default {
        white-space: normal;
     }
   }
+
+  @media (max-width: 1200px) {
+    .ant-col-xl-8 {
+      width: 100%;
+    }
+  }
 </style>
diff --git a/ui/src/views/dashboard/UsageDashboardChart.vue b/ui/src/views/dashboard/UsageDashboardChart.vue
index 8dd0666..6934c32 100644
--- a/ui/src/views/dashboard/UsageDashboardChart.vue
+++ b/ui/src/views/dashboard/UsageDashboardChart.vue
@@ -44,11 +44,9 @@
 </template>
 
 <script>
-import RenderIcon from '@/utils/renderIcon'
 
 export default {
   name: 'UsageDashboardChart',
-  components: { RenderIcon },
   props: {
     stats: {
       type: Array,
diff --git a/ui/src/views/infra/InfraSummary.vue b/ui/src/views/infra/InfraSummary.vue
index 158b0e6..3bb16f4 100644
--- a/ui/src/views/infra/InfraSummary.vue
+++ b/ui/src/views/infra/InfraSummary.vue
@@ -169,7 +169,6 @@
 import { ref, reactive, toRaw } from 'vue'
 import { api } from '@/api'
 import router from '@/router'
-import RenderIcon from '@/utils/renderIcon'
 
 import Breadcrumb from '@/components/widgets/Breadcrumb'
 import ChartCard from '@/components/widgets/ChartCard'
@@ -180,8 +179,7 @@ export default {
   components: {
     Breadcrumb,
     ChartCard,
-    TooltipLabel,
-    RenderIcon
+    TooltipLabel
   },
   data () {
     return {
diff --git a/ui/src/views/project/AccountsTab.vue b/ui/src/views/project/AccountsTab.vue
index 96118b8..333a27e 100644
--- a/ui/src/views/project/AccountsTab.vue
+++ b/ui/src/views/project/AccountsTab.vue
@@ -35,32 +35,32 @@
           <template #action="{ record }">
             <div v-if="record.projectroleid">
               <span v-if="imProjectAdmin && dataSource.length > 1" class="account-button-action">
-              <tooltip-button
-                tooltipPlacement="top"
-                :tooltip="record.userid ? $t('label.make.user.project.owner') : $t('label.make.project.owner')"
-                v-if="record.role !== owner"
-                type="default"
-                icon="arrow-up-outlined"
-                size="small"
-                @onClick="promoteAccount(record)" />
-              <tooltip-button
-                tooltipPlacement="top"
-                :tooltip="record.userid ? $t('label.demote.project.owner.user') : $t('label.demote.project.owner')"
-                v-if="updateProjectApi.params.filter(x => x.name === 'swapowner').length > 0 && record.role === owner"
-                type="default"
-                icon="arrow-down-outlined"
-                size="small"
-                @onClick="demoteAccount(record)" />
-              <tooltip-button
-                tooltipPlacement="top"
-                :tooltip="record.userid ? $t('label.remove.project.user') : $t('label.remove.project.account')"
-                type="primary"
-                :danger="true"
-                icon="delete-outlined"
-                size="small"
-                :disabled="!('deleteAccountFromProject' in $store.getters.apis)"
-                @onClick="onShowConfirmDelete(record)" />
-            </span>
+                <tooltip-button
+                  tooltipPlacement="top"
+                  :tooltip="record.userid ? $t('label.make.user.project.owner') : $t('label.make.project.owner')"
+                  v-if="record.role !== owner"
+                  type="default"
+                  icon="arrow-up-outlined"
+                  size="small"
+                  @onClick="promoteAccount(record)" />
+                <tooltip-button
+                  tooltipPlacement="top"
+                  :tooltip="record.userid ? $t('label.demote.project.owner.user') : $t('label.demote.project.owner')"
+                  v-if="updateProjectApi.params.filter(x => x.name === 'swapowner').length > 0 && record.role === owner"
+                  type="default"
+                  icon="arrow-down-outlined"
+                  size="small"
+                  @onClick="demoteAccount(record)" />
+                <tooltip-button
+                  tooltipPlacement="top"
+                  :tooltip="record.userid ? $t('label.remove.project.user') : $t('label.remove.project.account')"
+                  type="primary"
+                  :danger="true"
+                  icon="delete-outlined"
+                  size="small"
+                  :disabled="!('deleteAccountFromProject' in $store.getters.apis)"
+                  @onClick="onShowConfirmDelete(record)" />
+              </span>
             </div>
           </template>
         </a-table>
diff --git a/ui/src/views/project/InvitationsTemplate.vue b/ui/src/views/project/InvitationsTemplate.vue
index c40eb76..af13412 100644
--- a/ui/src/views/project/InvitationsTemplate.vue
+++ b/ui/src/views/project/InvitationsTemplate.vue
@@ -44,7 +44,6 @@
               <tooltip-button
                 tooltipPlacement="top"
                 :tooltip="$t('label.accept.project.invitation')"
-                type="success"
                 icon="check-outlined"
                 size="small"
                 @onClick="onShowConfirmAcceptInvitation(record)"/>
@@ -247,12 +246,24 @@ export default {
       params.accept = state
 
       api('updateProjectInvitation', params).then(json => {
-        const hasJobId = this.checkForAddAsyncJob(json, title, record.project)
-
-        if (hasJobId) {
-          this.fetchData()
-          this.$emit('refresh-data')
-        }
+        this.$pollJob({
+          jobId: json.updateprojectinvitationresponse.jobid,
+          title,
+          description: record.project,
+          successMethod: () => {
+            this.fetchData()
+            this.$emit('refresh-data')
+          },
+          errorMethod: () => {
+            this.fetchData()
+            this.$emit('refresh-data')
+          },
+          catchMessage: this.$t('error.fetching.async.job.result'),
+          catchMethod: () => {
+            this.fetchData()
+            this.$emit('refresh-data')
+          }
+        })
       }).catch(error => {
         // show error
         this.$notifyError(error)