From 2afaa9fdd69fbe5df151b2d5714625084b880dfa Mon Sep 17 00:00:00 2001 From: Vishesh Date: Thu, 8 Jun 2023 20:10:15 +0530 Subject: [PATCH 01/11] UI: Notify users when upgrades are available Notify when a new version of CloudStack or a restart is required for networks & VPCs --- ui/public/locales/en.json | 3 ++ ui/src/components/page/GlobalFooter.vue | 9 ++++ ui/src/store/getters.js | 1 + ui/src/store/modules/user.js | 55 +++++++++++++++++++++++++ 4 files changed, 68 insertions(+) diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 2e5412bf7378..1b3f57bd3d70 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1468,6 +1468,7 @@ "label.networkspeed": "Network speed", "label.networktype": "Network type", "label.networkwrite": "Network write", +"label.new.version.available": "Newer version available", "label.new": "New", "label.new.autoscale.vmgroup": "New AutoScaling Group", "label.new.instance.group": "New Instance group", @@ -3196,6 +3197,8 @@ "message.no.description": "No description entered.", "message.offering.internet.protocol.warning": "WARNING: IPv6 supported Networks use static routing and will require upstream routes to be configured manually.", "message.offering.ipv6.warning": "Please refer documentation for creating IPv6 enabled Network/VPC offering IPv6 support in CloudStack - Isolated Networks and VPC Network Tiers", +"message.notification.restart.required.network": "Restart required for network(s). Click here to view network(s) which require restart.", +"message.notification.restart.required.vpc": "Restart required for VPC(s). Click here to view VPC(s) which require restart.", "message.ovf.configurations": "OVF configurations available for the selected appliance. Please select the desired value. Incompatible compute offerings will get disabled.", "message.path.description": "NFS: exported path from the server. VMFS: /datacenter name/datastore name. SharedMountPoint: path where primary storage is mounted, such as /mnt/primary.", "message.please.confirm.remove.ssh.key.pair": "Please confirm that you want to remove this SSH key pair.", diff --git a/ui/src/components/page/GlobalFooter.vue b/ui/src/components/page/GlobalFooter.vue index 854cecc78ace..2ec921ec7efa 100644 --- a/ui/src/components/page/GlobalFooter.vue +++ b/ui/src/components/page/GlobalFooter.vue @@ -22,6 +22,15 @@
CloudStack {{ $store.getters.features.cloudstackversion }} + + + + + {{ $t('label.new.version.available') + ': ' + $store.getters.latestVersion }} + + diff --git a/ui/src/store/getters.js b/ui/src/store/getters.js index 67b168be8c28..405fd11bad11 100644 --- a/ui/src/store/getters.js +++ b/ui/src/store/getters.js @@ -28,6 +28,7 @@ const getters = { apis: state => state.user.apis, features: state => state.user.features, userInfo: state => state.user.info, + latestVersion: state => state.user.latestVersion, addRouters: state => state.permission.addRouters, multiTab: state => state.app.multiTab, listAllProjects: state => state.app.listAllProjects, diff --git a/ui/src/store/modules/user.js b/ui/src/store/modules/user.js index 24302a940335..0434e42b47f9 100644 --- a/ui/src/store/modules/user.js +++ b/ui/src/store/modules/user.js @@ -24,6 +24,7 @@ import router from '@/router' import store from '@/store' import { oauthlogin, login, logout, api } from '@/api' import { i18n } from '@/locales' +import { axios } from '../../utils/request' import { ACCESS_TOKEN, @@ -167,6 +168,9 @@ const user = { }, SET_OAUTH_PROVIDER_USED_TO_LOGIN: (state, provider) => { vueProps.$localStorage.set(OAUTH_PROVIDER, provider) + }, + SET_LATEST_VERSION: (state, version) => { + state.latestVersion = version } }, @@ -212,6 +216,7 @@ const user = { commit('SET_2FA_PROVIDER', result.providerfor2fa) commit('SET_2FA_ISSUER', result.issuerfor2fa) commit('SET_LOGIN_FLAG', false) + commit('SET_LATEST_VERSION', '') notification.destroy() resolve() @@ -294,6 +299,20 @@ const user = { const result = response.listusersresponse.user[0] commit('SET_INFO', result) commit('SET_NAME', result.firstname + ' ' + result.lastname) + if (result.rolename === 'Root Admin') { + axios.get( + 'https://api.github.com/repos/apache/cloudstack/releases' + ).then(response => { + for (const release of response) { + if (release.tag_name.toLowerCase().includes('rc')) { + continue + } else { + commit('SET_LATEST_VERSION', release.tag_name) + break + } + } + }).catch(ignored => {}) + } resolve(cachedApis) }).catch(error => { reject(error) @@ -332,6 +351,42 @@ const user = { }).catch(error => { reject(error) }) + + api('listNetworks', { restartrequired: true }).then(response => { + if (response.listnetworksresponse.count > 0) { + notification.info({ + message: i18n.global.t('label.restartrequired'), + description: i18n.global.t('message.notification.restart.required.network'), + duration: 0, + onClick: () => { + router.push({ path: '/guestnetwork', query: { restartrequired: true } }) + }, + onClose: () => { + let countNotify = store.getters.countNotify + countNotify > 0 ? countNotify-- : countNotify = 0 + store.commit('SET_COUNT_NOTIFY', countNotify) + } + }) + } + }).catch(ignored => {}) + + api('listVPCs', { restartrequired: true }).then(response => { + if (response.listvpcsresponse.count > 0) { + notification.info({ + message: i18n.global.t('label.restartrequired'), + description: i18n.global.t('message.notification.restart.required.vpc'), + duration: 0, + onClick: () => { + router.push({ path: '/vpc', query: { restartrequired: true } }) + }, + onClose: () => { + let countNotify = store.getters.countNotify + countNotify > 0 ? countNotify-- : countNotify = 0 + store.commit('SET_COUNT_NOTIFY', countNotify) + } + }) + } + }).catch(ignored => {}) } api('listUsers', { username: Cookies.get('username') }).then(response => { From 8aa4e1851bf7346f101ac69a2bc622c30ec069f2 Mon Sep 17 00:00:00 2001 From: Vishesh Date: Fri, 9 Jun 2023 10:10:37 +0530 Subject: [PATCH 02/11] Add filter on restart required --- ui/src/components/view/ListView.vue | 6 ++++++ ui/src/components/view/SearchView.vue | 12 +++++++++++- ui/src/config/section/network.js | 4 ++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index 27bd6a2fb363..6387019910ad 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -90,6 +90,12 @@ {{ text }} {{ text }} + + + + + + diff --git a/ui/src/components/view/SearchView.vue b/ui/src/components/view/SearchView.vue index 07e2d14e8bcb..90d83f9f3c73 100644 --- a/ui/src/components/view/SearchView.vue +++ b/ui/src/components/view/SearchView.vue @@ -303,7 +303,7 @@ export default { } if (['zoneid', 'domainid', 'imagestoreid', 'storageid', 'state', 'account', 'hypervisor', 'level', 'clusterid', 'podid', 'groupid', 'entitytype', 'accounttype', 'systemvmtype', 'scope', 'provider', - 'type', 'scope', 'managementserverid', 'serviceofferingid', 'diskofferingid', 'usagetype'].includes(item) + 'type', 'scope', 'managementserverid', 'serviceofferingid', 'diskofferingid', 'usagetype', 'restartrequired'].includes(item) ) { type = 'list' } else if (item === 'tags') { @@ -395,6 +395,16 @@ export default { this.fields[providerIndex].loading = false } + if (arrayField.includes('restartrequired')) { + const restartRequiredIndex = this.fields.findIndex(item => item.name === 'restartrequired') + this.fields[restartRequiredIndex].loading = true + this.fields[restartRequiredIndex].opts = [ + { id: 'true', name: 'label.yes' }, + { id: 'false', name: 'label.no' } + ] + this.fields[restartRequiredIndex].loading = false + } + if (arrayField.includes('resourcetype')) { const resourceTypeIndex = this.fields.findIndex(item => item.name === 'resourcetype') this.fields[resourceTypeIndex].loading = true diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js index 26b4f279e3d1..1ff704a306f6 100644 --- a/ui/src/config/section/network.js +++ b/ui/src/config/section/network.js @@ -54,7 +54,7 @@ export default { return fields }, filters: ['all', 'account', 'domainpath', 'shared'], - searchFilters: ['keyword', 'zoneid', 'domainid', 'account', 'type', 'tags'], + searchFilters: ['keyword', 'zoneid', 'domainid', 'account', 'type', 'restartrequired', 'tags'], related: [{ name: 'vm', title: 'label.instances', @@ -218,7 +218,7 @@ export default { return fields }, details: ['name', 'id', 'displaytext', 'cidr', 'networkdomain', 'ip6routes', 'ispersistent', 'redundantvpcrouter', 'restartrequired', 'zonename', 'account', 'domain', 'dns1', 'dns2', 'ip6dns1', 'ip6dns2', 'publicmtu'], - searchFilters: ['name', 'zoneid', 'domainid', 'account', 'tags'], + searchFilters: ['name', 'zoneid', 'domainid', 'account', 'restartrequired', 'tags'], related: [{ name: 'vm', title: 'label.instances', From 9f8f8dd570631a2826c5c1af3139164afe12f80f Mon Sep 17 00:00:00 2001 From: Vishesh Date: Fri, 9 Jun 2023 11:19:08 +0530 Subject: [PATCH 03/11] Fetch latest release from github only once a day --- ui/src/components/page/GlobalFooter.vue | 6 ++-- ui/src/store/modules/user.js | 41 +++++++++++++++---------- ui/src/store/mutation-types.js | 1 + 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/ui/src/components/page/GlobalFooter.vue b/ui/src/components/page/GlobalFooter.vue index 2ec921ec7efa..ba7a64f6d41e 100644 --- a/ui/src/components/page/GlobalFooter.vue +++ b/ui/src/components/page/GlobalFooter.vue @@ -22,13 +22,13 @@
CloudStack {{ $store.getters.features.cloudstackversion }} - + - {{ $t('label.new.version.available') + ': ' + $store.getters.latestVersion }} + {{ $t('label.new.version.available') + ': ' + $store.getters.latestVersion.version }} diff --git a/ui/src/store/modules/user.js b/ui/src/store/modules/user.js index 0434e42b47f9..dcea8e8c5aad 100644 --- a/ui/src/store/modules/user.js +++ b/ui/src/store/modules/user.js @@ -39,7 +39,8 @@ import { DARK_MODE, CUSTOM_COLUMNS, OAUTH_DOMAIN, - OAUTH_PROVIDER + OAUTH_PROVIDER, + LATEST_CS_VERSION } from '@/store/mutation-types' const user = { @@ -170,6 +171,7 @@ const user = { vueProps.$localStorage.set(OAUTH_PROVIDER, provider) }, SET_LATEST_VERSION: (state, version) => { + vueProps.$localStorage.set(LATEST_CS_VERSION, version) state.latestVersion = version } }, @@ -216,7 +218,8 @@ const user = { commit('SET_2FA_PROVIDER', result.providerfor2fa) commit('SET_2FA_ISSUER', result.issuerfor2fa) commit('SET_LOGIN_FLAG', false) - commit('SET_LATEST_VERSION', '') + const latestVersion = vueProps.$localStorage.get(LATEST_CS_VERSION, { version: '', fetchedTs: 0 }) + commit('SET_LATEST_VERSION', latestVersion) notification.destroy() resolve() @@ -282,10 +285,12 @@ const user = { const cachedCustomColumns = vueProps.$localStorage.get(CUSTOM_COLUMNS, {}) const domainStore = vueProps.$localStorage.get(DOMAIN_STORE, {}) const darkMode = vueProps.$localStorage.get(DARK_MODE, false) + const latestVersion = vueProps.$localStorage.get(LATEST_CS_VERSION, { version: '', fetchedTs: 0 }) const hasAuth = Object.keys(cachedApis).length > 0 commit('SET_DOMAIN_STORE', domainStore) commit('SET_DARK_MODE', darkMode) + commit('SET_LATEST_VERSION', latestVersion) if (hasAuth) { console.log('Login detected, using cached APIs') commit('SET_ZONES', cachedZones) @@ -299,20 +304,7 @@ const user = { const result = response.listusersresponse.user[0] commit('SET_INFO', result) commit('SET_NAME', result.firstname + ' ' + result.lastname) - if (result.rolename === 'Root Admin') { - axios.get( - 'https://api.github.com/repos/apache/cloudstack/releases' - ).then(response => { - for (const release of response) { - if (release.tag_name.toLowerCase().includes('rc')) { - continue - } else { - commit('SET_LATEST_VERSION', release.tag_name) - break - } - } - }).catch(ignored => {}) - } + store.dispatch('SetCsLatestVersion', result.rolename) resolve(cachedApis) }).catch(error => { reject(error) @@ -393,6 +385,7 @@ const user = { const result = response.listusersresponse.user[0] commit('SET_INFO', result) commit('SET_NAME', result.firstname + ' ' + result.lastname) + store.dispatch('SetCsLatestVersion', result.rolename) }).catch(error => { reject(error) }) @@ -543,6 +536,22 @@ const user = { SetDomainStore ({ commit }, domainStore) { commit('SET_DOMAIN_STORE', domainStore) }, + SetCsLatestVersion ({ commit }, rolename) { + if (rolename === 'Root Admin' && (+new Date() - store.getters.latestVersion.fetchedTs) > 24 * 60 * 60 * 1000) { + axios.get( + 'https://api.github.com/repos/apache/cloudstack/releases' + ).then(response => { + for (const release of response) { + if (release.tag_name.toLowerCase().includes('rc')) { + continue + } else { + commit('SET_LATEST_VERSION', { version: release.tag_name, fetchedTs: (+new Date()) }) + break + } + } + }).catch(ignored => {}) + } + }, SetDarkMode ({ commit }, darkMode) { commit('SET_DARK_MODE', darkMode) }, diff --git a/ui/src/store/mutation-types.js b/ui/src/store/mutation-types.js index 77aeb8fb7b6f..93dfc9fbc200 100644 --- a/ui/src/store/mutation-types.js +++ b/ui/src/store/mutation-types.js @@ -35,6 +35,7 @@ export const USE_BROWSER_TIMEZONE = 'USE_BROWSER_TIMEZONE' export const SERVER_MANAGER = 'SERVER_MANAGER' export const DOMAIN_STORE = 'DOMAIN_STORE' export const DARK_MODE = 'DARK_MODE' +export const LATEST_CS_VERSION = 'LATEST_CS_VERSION' export const VUE_VERSION = 'VUE_VERSION' export const CUSTOM_COLUMNS = 'CUSTOM_COLUMNS' export const RELOAD_ALL_PROJECTS = 'RELOAD_ALL_PROJECTS' From d78d8d19b85351be73483ff25c3ec2fbb7e0be94 Mon Sep 17 00:00:00 2001 From: Vishesh Date: Sun, 11 Jun 2023 14:55:20 +0530 Subject: [PATCH 04/11] Use Alert component instead of a notification --- ui/public/locales/en.json | 5 +- ui/src/components/page/GlobalFooter.vue | 9 ---- ui/src/components/page/GlobalLayout.vue | 60 +++++++++++++++++++++-- ui/src/components/view/ListView.vue | 2 +- ui/src/store/getters.js | 2 + ui/src/store/modules/user.js | 64 +++++++++++++------------ ui/src/store/mutation-types.js | 2 + ui/src/views/AutogenView.vue | 9 ++-- 8 files changed, 101 insertions(+), 52 deletions(-) diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 1b3f57bd3d70..34b36151039c 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1468,7 +1468,6 @@ "label.networkspeed": "Network speed", "label.networktype": "Network type", "label.networkwrite": "Network write", -"label.new.version.available": "Newer version available", "label.new": "New", "label.new.autoscale.vmgroup": "New AutoScaling Group", "label.new.instance.group": "New Instance group", @@ -3197,8 +3196,8 @@ "message.no.description": "No description entered.", "message.offering.internet.protocol.warning": "WARNING: IPv6 supported Networks use static routing and will require upstream routes to be configured manually.", "message.offering.ipv6.warning": "Please refer documentation for creating IPv6 enabled Network/VPC offering IPv6 support in CloudStack - Isolated Networks and VPC Network Tiers", -"message.notification.restart.required.network": "Restart required for network(s). Click here to view network(s) which require restart.", -"message.notification.restart.required.vpc": "Restart required for VPC(s). Click here to view VPC(s) which require restart.", +"message.restart.required.network": "Restart required for network(s). Click here to view network(s) which require restart.", +"message.restart.required.vpc": "Restart required for VPC(s). Click here to view VPC(s) which require restart.", "message.ovf.configurations": "OVF configurations available for the selected appliance. Please select the desired value. Incompatible compute offerings will get disabled.", "message.path.description": "NFS: exported path from the server. VMFS: /datacenter name/datastore name. SharedMountPoint: path where primary storage is mounted, such as /mnt/primary.", "message.please.confirm.remove.ssh.key.pair": "Please confirm that you want to remove this SSH key pair.", diff --git a/ui/src/components/page/GlobalFooter.vue b/ui/src/components/page/GlobalFooter.vue index ba7a64f6d41e..854cecc78ace 100644 --- a/ui/src/components/page/GlobalFooter.vue +++ b/ui/src/components/page/GlobalFooter.vue @@ -22,15 +22,6 @@
CloudStack {{ $store.getters.features.cloudstackversion }} - - - - - {{ $t('label.new.version.available') + ': ' + $store.getters.latestVersion.version }} - - diff --git a/ui/src/components/page/GlobalLayout.vue b/ui/src/components/page/GlobalLayout.vue index 6dd5c530fa5b..dadb5dc77cc6 100644 --- a/ui/src/components/page/GlobalLayout.vue +++ b/ui/src/components/page/GlobalLayout.vue @@ -17,11 +17,54 @@