cloudstack/ui/src/utils/plugins.js
Abhishek Kumar 4884f52c90
ui: allow provisioning backups during instance deploy (#11612)
* ui: allow assigning backup offring during instance deploy

Add backup offering selection to Deploy VM wizard and assign selected backup offering to the VM after successful deployment. This enables users to choose a backup offering during VM creation, and the VM is automatically associated with the selected offering post-deployment.

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* changes for schedules

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* Update ui/public/locales/pt_BR.json

* Update ui/src/views/compute/wizard/DeployInstanceBackupSelection.vue

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* address review

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* fix

* allow only one schdeule per interval type

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>

* show message same internaltype schedule

* show backup step only when zone has offering

---------

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-23 14:51:42 +05:30

611 lines
19 KiB
JavaScript

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import _ from 'lodash'
import { i18n } from '@/locales'
import { getAPI } from '@/api'
import { message, notification, Modal } from 'ant-design-vue'
import eventBus from '@/config/eventBus'
import store from '@/store'
import { sourceToken } from '@/utils/request'
import { toLocalDate, toLocaleDate } from '@/utils/date'
export const pollJobPlugin = {
install (app) {
app.config.globalProperties.$pollJob = function (options) {
/**
* @param {String} jobId
* @param {String} [name='']
* @param {String} [title='']
* @param {String} [description='']
* @param {Boolean} [showSuccessMessage=true]
* @param {String} [successMessage=Success]
* @param {Function} [successMethod=() => {}]
* @param {String} [errorMessage=Error]
* @param {Function} [errorMethod=() => {}]
* @param {Object} [showLoading=true]
* @param {String} [loadingMessage=Loading...]
* @param {String} [catchMessage=Error caught]
* @param {Function} [catchMethod=() => {}]
* @param {Object} [action=null]
* @param {Object} [bulkAction=false]
* @param {String} resourceId
*/
const {
jobId,
name = '',
title = '',
description = '',
showSuccessMessage = true,
successMessage = i18n.global.t('label.success'),
successMethod = () => {},
errorMessage = i18n.global.t('label.error'),
errorMethod = () => {},
loadingMessage = `${i18n.global.t('label.loading')}...`,
showLoading = true,
catchMessage = i18n.global.t('label.error.caught'),
catchMethod = () => {},
action = null,
bulkAction = false,
resourceId = null
} = options
store.dispatch('AddHeaderNotice', {
key: jobId,
title,
description,
status: 'progress',
timestamp: new Date()
})
eventBus.on('update-job-details', (args) => {
const { jobId, resourceId } = args
const fullPath = this.$route.fullPath
const path = this.$route.path
var jobs = this.$store.getters.headerNotices.map(job => {
if (job.key === jobId) {
if (resourceId && !path.includes(resourceId)) {
job.path = path + '/' + resourceId
} else {
job.path = fullPath
}
}
return job
})
this.$store.commit('SET_HEADER_NOTICES', jobs)
})
options.originalPage = options.originalPage || this.$router.currentRoute.value.path
getAPI('queryAsyncJobResult', { jobId }).then(json => {
const result = json.queryasyncjobresultresponse
eventBus.emit('update-job-details', { jobId, resourceId })
if (result.jobstatus === 1) {
if (showSuccessMessage) {
var content = successMessage
if (successMessage === 'Success' && action && action.label) {
content = i18n.global.t(action.label)
}
if (name) {
content = content + ' - ' + name
}
message.success({
content,
key: jobId,
duration: 2
})
} else {
message.destroy(jobId)
}
store.dispatch('AddHeaderNotice', {
key: jobId,
title,
description,
status: 'done',
duration: 2,
timestamp: new Date()
})
eventBus.emit('update-job-details', { jobId, resourceId })
// Ensure we refresh on the same / parent page
const currentPage = this.$router.currentRoute.value.path
const samePage = options.originalPage === currentPage || options.originalPage.startsWith(currentPage + '/')
if (samePage && (!action || !('isFetchData' in action) || (action.isFetchData))) {
eventBus.emit('async-job-complete', action)
}
successMethod(result)
} else if (result.jobstatus === 2) {
if (!bulkAction) {
message.error({
content: errorMessage,
key: jobId,
duration: 1
})
}
var errMessage = errorMessage
if (action && action.label) {
errMessage = i18n.global.t(action.label)
}
var desc = result.jobresult.errortext
if (name) {
desc = `(${name}) ${desc}`
}
let onClose = () => {}
if (!bulkAction) {
let countNotify = store.getters.countNotify
countNotify++
store.commit('SET_COUNT_NOTIFY', countNotify)
onClose = () => {
let countNotify = store.getters.countNotify
countNotify > 0 ? countNotify-- : countNotify = 0
store.commit('SET_COUNT_NOTIFY', countNotify)
}
}
notification.error({
top: '65px',
message: errMessage,
description: desc,
key: jobId,
duration: 0,
onClose: onClose
})
store.dispatch('AddHeaderNotice', {
key: jobId,
title,
description: desc,
status: 'failed',
duration: 2,
timestamp: new Date()
})
eventBus.emit('update-job-details', { jobId, resourceId })
// Ensure we refresh on the same / parent page
const currentPage = this.$router.currentRoute.value.path
const samePage = options.originalPage === currentPage || options.originalPage.startsWith(currentPage + '/')
if (samePage && (!action || !('isFetchData' in action) || (action.isFetchData))) {
eventBus.emit('async-job-complete', action)
}
errorMethod(result)
} else if (result.jobstatus === 0) {
if (showLoading) {
message.loading({
content: loadingMessage,
key: jobId,
duration: 0
})
}
setTimeout(() => {
this.$pollJob(options, action)
}, 3000)
}
}).catch(e => {
console.error(`${catchMessage} - ${e}`)
if (!sourceToken.isCancel(e)) {
let countNotify = store.getters.countNotify
countNotify++
store.commit('SET_COUNT_NOTIFY', countNotify)
notification.error({
top: '65px',
message: i18n.global.t('label.error'),
description: catchMessage,
duration: 0,
onClose: () => {
let countNotify = store.getters.countNotify
countNotify > 0 ? countNotify-- : countNotify = 0
store.commit('SET_COUNT_NOTIFY', countNotify)
}
})
}
catchMethod && catchMethod()
})
}
}
}
export const notifierPlugin = {
install (app) {
app.config.globalProperties.$notifyError = function (error) {
console.log(error)
var msg = i18n.global.t('message.request.failed')
var desc = ''
if (error && error.response) {
if (error.response.status) {
msg = `${i18n.global.t('message.request.failed')} (${error.response.status})`
}
if (error.message) {
desc = error.message
}
if (error.response.headers && 'x-description' in error.response.headers) {
desc = error.response.headers['x-description']
}
if (desc === '' && error.response.data) {
const responseKey = _.findKey(error.response.data, 'errortext')
if (responseKey) {
desc = error.response.data[responseKey].errortext
}
}
}
let countNotify = store.getters.countNotify
countNotify++
store.commit('SET_COUNT_NOTIFY', countNotify)
notification.error({
top: '65px',
message: msg,
description: desc,
duration: 0,
onClose: () => {
let countNotify = store.getters.countNotify
countNotify > 0 ? countNotify-- : countNotify = 0
store.commit('SET_COUNT_NOTIFY', countNotify)
}
})
}
app.config.globalProperties.$notification = {
defaultConfig: {
top: '65px',
onClose: () => {
let countNotify = store.getters.countNotify
countNotify > 0 ? countNotify-- : countNotify = 0
store.commit('SET_COUNT_NOTIFY', countNotify)
}
},
setCountNotify: () => {
let countNotify = store.getters.countNotify
countNotify++
store.commit('SET_COUNT_NOTIFY', countNotify)
},
info: (config) => {
app.config.globalProperties.$notification.setCountNotify()
config = Object.assign({}, app.config.globalProperties.$notification.defaultConfig, config)
notification.info(config)
},
error: (config) => {
app.config.globalProperties.$notification.setCountNotify()
config = Object.assign({}, app.config.globalProperties.$notification.defaultConfig, config)
notification.error(config)
},
success: (config) => {
app.config.globalProperties.$notification.setCountNotify()
config = Object.assign({}, app.config.globalProperties.$notification.defaultConfig, config)
notification.success(config)
},
warning: (config) => {
app.config.globalProperties.$notification.setCountNotify()
config = Object.assign({}, app.config.globalProperties.$notification.defaultConfig, config)
notification.warning(config)
},
warn: (config) => {
app.config.globalProperties.$notification.setCountNotify()
config = Object.assign({}, app.config.globalProperties.$notification.defaultConfig, config)
notification.warn(config)
},
close: (key) => notification.close(key),
destroy: () => notification.destroy()
}
app.config.globalProperties.$messageConfigSuccess = function (msg, configrecord) {
if (configrecord.isdynamic) {
msg += `. ${this.$t('message.setting.update.delay')}`
}
message.success(msg)
}
}
}
export const toLocaleDatePlugin = {
install (app) {
app.config.globalProperties.$toLocaleDate = function (date) {
const { timezoneoffset, usebrowsertimezone } = this.$store.getters
return toLocaleDate({ date, timezoneoffset, usebrowsertimezone })
}
app.config.globalProperties.$toLocalDate = function (date) {
const { timezoneoffset, usebrowsertimezone } = this.$store.getters
return toLocalDate({ date, timezoneoffset, usebrowsertimezone }).toISOString()
}
}
}
export const configUtilPlugin = {
install (app) {
app.config.globalProperties.$applyDocHelpMappings = function (docHelp) {
var docHelpMappings = this.$config.docHelpMappings
if (docHelp && docHelpMappings &&
docHelpMappings.constructor === Object && Object.keys(docHelpMappings).length > 0) {
for (var key in docHelpMappings) {
if (docHelp.includes(key) && docHelp !== docHelpMappings[key]) {
docHelp = docHelp.replace(key, docHelpMappings[key])
break
}
}
}
return docHelp
}
}
}
export const showIconPlugin = {
install (app) {
app.config.globalProperties.$showIcon = function (resource) {
var resourceType = this.$route.path.split('/')[1]
if (resource) {
resourceType = resource
}
if (['zone', 'zones', 'template', 'iso', 'account', 'accountuser', 'vm', 'domain', 'project', 'vpc', 'guestnetwork', 'guestoscategory'].includes(resourceType)) {
return true
} else {
return false
}
}
}
}
export const resourceTypePlugin = {
install (app) {
app.config.globalProperties.$getResourceType = function () {
const type = this.$route.path.split('/')[1]
if (type === 'vm') {
return 'UserVM'
} else if (type === 'accountuser') {
return 'User'
} else if (type === 'guestnetwork') {
return 'Network'
} else {
return type
}
}
app.config.globalProperties.$getRouteFromResourceType = function (resourceType) {
switch (resourceType) {
case 'VirtualMachine':
return 'vm'
case 'DomainRouter':
return 'router'
case 'ConsoleProxy':
return 'systemvm'
case 'User':
return 'accountuser'
case 'Network':
return 'guestnetwork'
case 'ServiceOffering':
return 'computeoffering'
case 'IpAddress':
return 'publicip'
case 'NetworkAcl':
return 'acllist'
case 'KubernetesCluster':
return 'kubernetes'
case 'KubernetesSupportedVersion':
return 'kubernetesiso'
case 'ExtensionCustomAction':
return 'customaction'
case 'SystemVm':
case 'PhysicalNetwork':
case 'Backup':
case 'SecurityGroup':
case 'StoragePool':
case 'ImageStore':
case 'Template':
case 'Iso':
case 'Host':
case 'Volume':
case 'Account':
case 'Snapshot':
case 'Project':
case 'Domain':
case 'DiskOffering':
case 'NetworkOffering':
case 'VpcOffering':
case 'BackupOffering':
case 'Zone':
case 'Vpc':
case 'VmSnapshot':
case 'Pod':
case 'Cluster':
case 'Role':
case 'AffinityGroup':
case 'VpnCustomerGateway':
case 'AutoScaleVmGroup':
case 'QuotaTariff':
case 'GuestOsCategory':
case 'Extension':
return resourceType.toLowerCase()
}
return ''
}
app.config.globalProperties.$getIconFromResourceType = function (resourceType) {
var routePath = this.$getRouteFromResourceType(resourceType)
if (!routePath) return ''
var route = this.$router.resolve('/' + routePath)
if (routePath === 'kubernetes') {
return route?.meta?.icon[0]
}
return route?.meta?.icon || ''
}
}
}
export const apiMetaUtilPlugin = {
install (app) {
app.config.globalProperties.$getApiParams = function () {
var apiParams = {}
for (var argument of arguments) {
var apiConfig = this.$store.getters.apis[argument] || {}
if (apiConfig && apiConfig.params) {
apiConfig.params.forEach(param => {
apiParams[param.name] = param
})
}
}
return apiParams
}
}
}
export const localesPlugin = {
install (app) {
app.config.globalProperties.$t = i18n.global.t
}
}
const KiB = 1024
const MiB = 1024 * KiB
const GiB = 1024 * MiB
const TiB = 1024 * GiB
export const fileSizeUtilPlugin = {
install (app) {
app.config.globalProperties.$bytesToGiB = function (bytes) {
if (bytes == null || bytes === 0) {
return 0
}
return (bytes / GiB).toFixed(2)
}
app.config.globalProperties.$bytesToHumanReadableSize = function (bytes) {
if (bytes == null) {
return ''
}
if (bytes < KiB && bytes >= 0) {
return bytes + ' bytes'
}
if (bytes < MiB) {
return (bytes / KiB).toFixed(2) + ' KiB'
} else if (bytes < GiB) {
return (bytes / MiB).toFixed(2) + ' MiB'
} else if (bytes < TiB) {
return (bytes / GiB).toFixed(2) + ' GiB'
} else {
return (bytes / TiB).toFixed(2) + ' TiB'
}
}
}
}
function isBase64 (str) {
try {
const decoded = new TextDecoder().decode(Uint8Array.from(atob(str), c => c.charCodeAt(0)))
return btoa(decoded) === str
} catch (err) {
return false
}
}
export const genericUtilPlugin = {
install (app) {
app.config.globalProperties.$isValidUuid = function (uuid) {
const regexExp = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi
return regexExp.test(uuid)
}
app.config.globalProperties.$toBase64AndURIEncoded = function (text) {
if (isBase64(text)) {
return text
}
return encodeURI(btoa(unescape(encodeURIComponent(text))))
}
}
}
export function createPathBasedOnVmType (vmtype, virtualmachineid) {
let path = ''
switch (vmtype) {
case 'ConsoleProxy':
case 'SecondaryStorageVm':
path = '/systemvm/'
break
case 'DomainRouter':
path = '/router/'
break
default:
path = '/vm/'
}
return path + virtualmachineid
}
export const dialogUtilPlugin = {
install (app) {
app.config.globalProperties.$resetConfigurationValueConfirm = function (configRecord, callback) {
Modal.confirm({
title: i18n.global.t('label.reset.config.value'),
content: `${i18n.global.t('message.confirm.reset.configuration.value').replace('%x', configRecord.name)}`,
okText: i18n.global.t('label.yes'),
cancelText: i18n.global.t('label.no'),
okType: 'primary',
onOk: () => callback(configRecord)
})
}
}
}
export const cpuArchitectureUtilPlugin = {
install (app) {
app.config.globalProperties.$fetchCpuArchitectureTypes = function () {
const architectures = [
{ id: 'x86_64', name: 'Intel/AMD 64 bits (x86_64)' },
{ id: 'aarch64', name: 'ARM 64 bits (aarch64)' }
]
return architectures.map(item => ({ ...item, description: item.name }))
}
}
}
export const imagesUtilPlugin = {
install (app) {
app.config.globalProperties.$fetchTemplateTypes = function (hypervisor) {
const baseTypes = ['USER']
if (hypervisor === 'External') {
return baseTypes.map(type => ({ id: type, name: type, description: type }))
}
baseTypes.push('VNF')
const adminTypes = ['SYSTEM', 'BUILTIN', 'ROUTING']
const types = [...baseTypes]
if (store.getters.userInfo?.roletype === 'Admin') {
types.push(...adminTypes)
}
return types.map(type => ({ id: type, name: type, description: type }))
}
}
}
export const extensionsUtilPlugin = {
install (app) {
app.config.globalProperties.$fetchCustomActionRoleTypes = function () {
const roleTypes = []
const roleTypesList = ['Admin', 'Resource Admin', 'Domain Admin', 'User']
roleTypesList.forEach((item) => {
roleTypes.push({
id: item.replace(' ', ''),
description: item
})
})
return roleTypes
}
}
}
export const backupUtilPlugin = {
install (app) {
app.config.globalProperties.$isBackupProviderSupportsQuiesceVm = function (provider) {
if (!provider && typeof provider !== 'string') {
return false
}
return ['nas'].includes(provider.toLowerCase())
}
}
}