Skip to content

Commit 19e8477

Browse files
committed
more advisories
Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
1 parent 4cd2868 commit 19e8477

File tree

4 files changed

+267
-29
lines changed

4 files changed

+267
-29
lines changed

ui/public/locales/en.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,7 +1149,12 @@
11491149
"label.go.back": "Go back",
11501150
"label.go.to.compute.offerings": "Go to Compute Offerings",
11511151
"label.go.to.global.settings": "Go to Global Settings",
1152+
"label.go.to.networks": "Go to Networks",
1153+
"label.go.to.templates": "Go to Templates",
1154+
"label.go.to.isos": "Go to ISOs",
1155+
"label.go.to.volumes": "Go to Volumes",
11521156
"label.go.to.kubernetes.isos": "Go to Kubernetes ISOs",
1157+
"label.go.to.snapshots": "Go to Volume Snapshots",
11531158
"label.gpu": "GPU",
11541159
"label.gpucardid": "GPU Card",
11551160
"label.gpucardname": "GPU Card",
@@ -3075,9 +3080,14 @@
30753080
"message.add.ip.v6.firewall.rule.failed": "Failed to add IPv6 firewall rule",
30763081
"message.add.ip.v6.firewall.rule.processing": "Adding IPv6 firewall rule...",
30773082
"message.add.ip.v6.firewall.rule.success": "Added IPv6 firewall rule",
3083+
"message.advisory.instance.compute.offering.missing": "No compute offering found for deploying an Instance.",
3084+
"message.advisory.instance.image.missing": "No suitable Template/ISO/Volume/Volume Snapshot found for deploying an Instance. Please make sure you have a Template/ISO/Volume/Snapshot ready for Instance deployment.",
3085+
"message.advisory.instance.network.missing": "No suitable Network found for deploying an Instance. Please create a Network to be used by the Instance.",
30783086
"message.advisory.cks.endpoint.url.not.configured": "Endpoint URL which will be used by Kubernetes clusters is not configured correctly",
30793087
"message.advisory.cks.min.offering": "No suitable Compute Offering found for Kubernetes cluster nodes with minimum required resources (2 vCPU, 2 GB RAM)",
30803088
"message.advisory.cks.version.check": "No Kubernetes version found that can be used to deploy a Kubernetes cluster",
3089+
"message.advisory.vnf.appliance.compute.offering.missing": "No compute offering found for deploying a VNF appliance.",
3090+
"message.advisory.vnf.appliance.template.missing": "No VNF Template found for deploying a VNF appliance. Please make sure you have a VNF Template for appliance deployment.",
30813091
"message.redeliver.webhook.delivery": "Redeliver this Webhook delivery",
30823092
"message.remove.ip.v6.firewall.rule.failed": "Failed to remove IPv6 firewall rule",
30833093
"message.remove.ip.v6.firewall.rule.processing": "Removing IPv6 firewall rule...",

ui/src/config/section/compute.js

Lines changed: 120 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { isZoneCreated } from '@/utils/zone'
2121
import { getAPI, postAPI, getBaseUrl } from '@/api'
2222
import { getLatestKubernetesIsoParams } from '@/utils/acsrepo'
2323
import kubernetesIcon from '@/assets/icons/kubernetes.svg?inline'
24+
import { hasNoItems } from '@/utils/advisory'
2425

2526
export default {
2627
name: 'compute',
@@ -100,6 +101,119 @@ export default {
100101
tabs: [{
101102
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/InstanceTab.vue')))
102103
}],
104+
advisories: [
105+
{
106+
id: 'instance-image-check',
107+
severity: 'warning',
108+
message: 'message.advisory.instance.image.missing',
109+
condition: async (store) => {
110+
return await hasNoItems(store,
111+
'listTemplates',
112+
{ isvnf: false, templatefilter: 'executable', isready: true }) &&
113+
await hasNoItems(store, 'listIsos', { isofilter: 'executable', bootable: true, isready: true }) &&
114+
await hasNoItems(store, 'listVolumes', { state: 'Ready' }) &&
115+
await hasNoItems(store, 'listSnapshots')
116+
},
117+
actions: [
118+
{
119+
label: 'label.register.template',
120+
show: (store) => { return ('registerTemplate' in store.getters.apis) },
121+
primary: true,
122+
run: (store, router) => {
123+
router.push({ name: 'template', query: { action: 'registerTemplate' } })
124+
return false
125+
}
126+
},
127+
{
128+
label: 'label.go.to.templates',
129+
show: (store) => { return ('listTemplates' in store.getters.apis) },
130+
run: (store, router) => {
131+
router.push({ name: 'template' })
132+
return false
133+
}
134+
},
135+
{
136+
label: 'label.go.to.isos',
137+
show: (store) => { return ('listIsos' in store.getters.apis) },
138+
run: (store, router) => {
139+
router.push({ name: 'iso' })
140+
return false
141+
}
142+
},
143+
{
144+
label: 'label.go.to.volumes',
145+
show: (store) => { return ('listVolumes' in store.getters.apis) },
146+
run: (store, router) => {
147+
router.push({ name: 'volume' })
148+
return false
149+
}
150+
},
151+
{
152+
label: 'label.go.to.snapshots',
153+
show: (store) => { return ('listSnapshots' in store.getters.apis) },
154+
run: (store, router) => {
155+
router.push({ name: 'snapshot' })
156+
return false
157+
}
158+
}
159+
]
160+
},
161+
{
162+
id: 'instance-compute-offering-check',
163+
severity: 'warning',
164+
message: 'message.advisory.instance.compute.offering.missing',
165+
condition: async (store) => {
166+
return await hasNoItems(store, 'listServiceOfferings', { issystem: false })
167+
},
168+
actions: [
169+
{
170+
label: 'label.add.compute.offering',
171+
show: (store) => { return ('createServiceOffering' in store.getters.apis) },
172+
primary: true,
173+
run: (store, router) => {
174+
router.push({ name: 'computeoffering', query: { action: 'createServiceOffering' } })
175+
return false
176+
}
177+
},
178+
{
179+
label: 'label.go.to.compute.offerings',
180+
show: (store) => { return ('listServiceOfferings' in store.getters.apis) },
181+
run: (store, router) => {
182+
router.push({ name: 'computeoffering' })
183+
return false
184+
}
185+
}
186+
]
187+
},
188+
{
189+
id: 'instance-network-check',
190+
severity: 'warning',
191+
message: 'message.advisory.instance.network.missing',
192+
dismissOnConditionFail: true,
193+
condition: async (store) => {
194+
return await hasNoItems(store, 'listNetworks')
195+
},
196+
actions: [
197+
{
198+
label: 'label.add.network',
199+
show: (store) => { return ('createNetwork' in store.getters.apis) },
200+
primary: true,
201+
run: (store, router) => {
202+
router.push({ name: 'guestnetwork', query: { action: 'createNetwork' } })
203+
return false
204+
}
205+
},
206+
{
207+
label: 'label.go.to.networks',
208+
show: (store) => { return ('listNetworks' in store.getters.apis) },
209+
run: (store, router) => {
210+
router.push({ name: 'guestnetworks' })
211+
return false
212+
}
213+
}
214+
]
215+
}
216+
],
103217
actions: [
104218
{
105219
api: 'deployVirtualMachine',
@@ -589,23 +703,12 @@ export default {
589703
id: 'cks-min-offering',
590704
severity: 'warning',
591705
message: 'message.advisory.cks.min.offering',
592-
docsHelp: 'plugins/cloudstack-kubernetes-service.html',
593-
dismissOnConditionFail: true,
594706
condition: async (store) => {
595-
if (!('listServiceOfferings' in store.getters.apis)) {
596-
return false
597-
}
598-
const params = {
599-
cpunumber: 2,
600-
memory: 2048,
601-
issystem: false
602-
}
603-
try {
604-
const json = await getAPI('listServiceOfferings', params)
605-
const offerings = json?.listserviceofferingsresponse?.serviceoffering || []
606-
return !offerings.some(o => !o.iscustomized)
607-
} catch (error) {}
608-
return false
707+
return await hasNoItems(store,
708+
'listServiceOfferings',
709+
{ cpunumber: 2, memory: 2048, issystem: false },
710+
o => !o.iscustomized
711+
)
609712
},
610713
actions: [
611714
{
@@ -647,19 +750,8 @@ export default {
647750
id: 'cks-version-check',
648751
severity: 'warning',
649752
message: 'message.advisory.cks.version.check',
650-
docsHelp: 'plugins/cloudstack-kubernetes-service.html',
651-
dismissOnConditionFail: true,
652753
condition: async (store) => {
653-
const api = 'listKubernetesSupportedVersions'
654-
if (!(api in store.getters.apis)) {
655-
return false
656-
}
657-
try {
658-
const json = await getAPI(api, {})
659-
const versions = json?.listkubernetessupportedversionsresponse?.kubernetessupportedversion || []
660-
return versions.length === 0
661-
} catch (error) {}
662-
return false
754+
return await hasNoItems(store, 'listKubernetesSupportedVersions')
663755
},
664756
actions: [
665757
{
@@ -702,7 +794,6 @@ export default {
702794
id: 'cks-endpoint-url',
703795
severity: 'warning',
704796
message: 'message.advisory.cks.endpoint.url.not.configured',
705-
docsHelp: 'plugins/cloudstack-kubernetes-service.html',
706797
dismissOnConditionFail: true,
707798
condition: async (store) => {
708799
if (!['Admin'].includes(store.getters.userInfo.roletype)) {

ui/src/config/section/network.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import store from '@/store'
2020
import tungsten from '@/assets/icons/tungsten.svg?inline'
2121
import { isAdmin } from '@/role'
2222
import { isZoneCreated } from '@/utils/zone'
23+
import { hasNoItems } from '@/utils/advisory'
2324

2425
export default {
2526
name: 'network',
@@ -397,6 +398,66 @@ export default {
397398
tabs: [{
398399
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/InstanceTab.vue')))
399400
}],
401+
advisories: [
402+
{
403+
id: 'vnfapp-image-check',
404+
severity: 'warning',
405+
message: 'message.advisory.vnf.appliance.template.missing',
406+
condition: async (store) => {
407+
return await hasNoItems(store,
408+
'listVnfTemplates',
409+
{ isvnf: true, templatefilter: 'executable', isready: true })
410+
},
411+
actions: [
412+
{
413+
label: 'label.register.template',
414+
show: (store) => { return ('registerTemplate' in store.getters.apis) },
415+
primary: true,
416+
run: (store, router) => {
417+
router.push({ name: 'template', query: { action: 'registerTemplate' } })
418+
return false
419+
}
420+
},
421+
{
422+
label: 'label.go.to.templates',
423+
show: (store) => { return ('listTemplates' in store.getters.apis) },
424+
primary: false,
425+
run: (store, router) => {
426+
router.push({ name: 'template' })
427+
return false
428+
}
429+
}
430+
]
431+
},
432+
{
433+
id: 'vnfapp-compute-offering-check',
434+
severity: 'warning',
435+
message: 'message.advisory.vnf.appliance.compute.offering.missing',
436+
condition: async (store) => {
437+
return await hasNoItems(store, 'listServiceOfferings', { issystem: false })
438+
},
439+
actions: [
440+
{
441+
label: 'label.add.compute.offering',
442+
show: (store) => { return ('createServiceOffering' in store.getters.apis) },
443+
primary: true,
444+
run: (store, router) => {
445+
router.push({ name: 'computeoffering', query: { action: 'createServiceOffering' } })
446+
return false
447+
}
448+
},
449+
{
450+
label: 'label.go.to.compute.offerings',
451+
show: (store) => { return ('listServiceOfferings' in store.getters.apis) },
452+
primary: false,
453+
run: (store, router) => {
454+
router.push({ name: 'computeoffering' })
455+
return false
456+
}
457+
}
458+
]
459+
}
460+
],
400461
actions: [
401462
{
402463
api: 'deployVnfAppliance',

ui/src/utils/advisory/index.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
import { getAPI } from '@/api'
18+
19+
/**
20+
* Generic helper to check if an API has no items (useful for advisory conditions)
21+
* @param {Object} store - Vuex store instance
22+
* @param {string} apiName - Name of the API to call (e.g., 'listNetworks')
23+
* @param {Object} params - Optional parameters to merge with defaults
24+
* @param {Function} filterFunc - Optional function to filter items. If provided, returns true if no items match the filter.
25+
* @param {string} itemsKey - Optional key for items array in response. If not provided, will be deduced from apiName
26+
* @returns {Promise<boolean>} - Returns true if no items exist (advisory should be shown), false otherwise
27+
*/
28+
export async function hasNoItems (store, apiName, params = {}, filterFunc = null, itemsKey = null) {
29+
if (!(apiName in store.getters.apis)) {
30+
return false
31+
}
32+
33+
// If itemsKey not provided, deduce it from apiName
34+
if (!itemsKey) {
35+
// Remove 'list' prefix: listNetworks -> Networks
36+
let key = apiName.replace(/^list/i, '')
37+
// Convert to lowercase
38+
key = key.toLowerCase()
39+
// Handle plural forms: remove trailing 's' or convert 'ies' to 'y'
40+
if (key.endsWith('ies')) {
41+
key = key.slice(0, -3) + 'y'
42+
} else if (key.endsWith('s')) {
43+
key = key.slice(0, -1)
44+
}
45+
itemsKey = key
46+
}
47+
48+
const allParams = {
49+
listall: true,
50+
...params
51+
}
52+
53+
if (filterFunc == null) {
54+
allParams.page = 1
55+
allParams.pageSize = 1
56+
}
57+
58+
console.debug(`Checking if API ${apiName} has no items with params`, allParams)
59+
60+
try {
61+
const json = await getAPI(apiName, allParams)
62+
// Auto-derive response key: listNetworks -> listnetworksresponse
63+
const responseKey = `${apiName.toLowerCase()}response`
64+
const items = json?.[responseKey]?.[itemsKey] || []
65+
if (filterFunc) {
66+
const a = !items.some(filterFunc)
67+
console.debug(`API ${apiName} has ${items.length} items, after filter has items: ${items.filter(filterFunc)[0]}, returning ${a}`)
68+
const it = items.filter(filterFunc)
69+
console.debug(`Filtered items:`, it)
70+
return a
71+
}
72+
return items.length === 0
73+
} catch (error) {
74+
return false
75+
}
76+
}

0 commit comments

Comments
 (0)