Skip to content

Commit 5fd562b

Browse files
committed
ui: introduce section-level “advisories” with quick-fix actions
This change adds a lightweight “advisories” mechanism to section configs and ships the first advisory to help operators satisfy some of the CKS prerequisites. Signed-off-by: Abhishek Kumar <[email protected]>
1 parent 2c1aad4 commit 5fd562b

File tree

6 files changed

+367
-1
lines changed

6 files changed

+367
-1
lines changed

ui/public/locales/en.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,10 @@
287287
"label.add.isolated.network": "Add Isolated Network",
288288
"label.add.kubernetes.cluster": "Add Kubernetes Cluster",
289289
"label.add.acl.name": "ACL name",
290+
"label.add.latest.kubernetes.iso": "Add latest Kubernetes ISO",
290291
"label.add.ldap.account": "Add LDAP Account",
291292
"label.add.logical.router": "Add Logical Router to this Network",
293+
"label.add.minimum.required.compute.offering": "Add minimum required Compute Offering",
292294
"label.add.more": "Add more",
293295
"label.add.nodes": "Add Nodes to Kubernetes Cluster",
294296
"label.add.netscaler.device": "Add Netscaler Device",
@@ -1077,6 +1079,7 @@
10771079
"label.firewallruleuuid": "Firewall Rule",
10781080
"label.firstname": "First name",
10791081
"label.firstname.lower": "firstname",
1082+
"label.fix.configuration": "Fix configuration",
10801083
"label.fix.errors": "Fix errors",
10811084
"label.fixed": "Fixed Offering",
10821085
"label.for": "for",
@@ -1110,6 +1113,9 @@
11101113
"label.globo.dns.configuration": "GloboDNS configuration",
11111114
"label.glustervolume": "Volume",
11121115
"label.go.back": "Go back",
1116+
"label.go.to.compute.offerings": "Go to Compute Offerings",
1117+
"label.go.to.global.settings": "Go to Global Settings",
1118+
"label.go.to.kubernetes.isos": "Go to Kubernetes ISOs",
11131119
"label.gpu": "GPU",
11141120
"label.gpucardid": "GPU Card",
11151121
"label.gpucardname": "GPU Card",
@@ -3010,12 +3016,17 @@
30103016
"message.add.ip.v6.firewall.rule.failed": "Failed to add IPv6 firewall rule",
30113017
"message.add.ip.v6.firewall.rule.processing": "Adding IPv6 firewall rule...",
30123018
"message.add.ip.v6.firewall.rule.success": "Added IPv6 firewall rule",
3019+
"message.advisory.cks.endpoint.url.not.configured": "Endpoint URL which will be used by Kubernetes clusters is not configured correctly",
3020+
"message.advisory.cks.min.offering": "No suitable Compute Offering found for Kubernetes cluster nodes with minimum required resources (2 vCPU, 2 GB RAM)",
3021+
"message.advisory.cks.version.check": "No Kubernetes version found that can be used to deploy a Kubernetes cluster",
30133022
"message.redeliver.webhook.delivery": "Redeliver this Webhook delivery",
30143023
"message.remove.ip.v6.firewall.rule.failed": "Failed to remove IPv6 firewall rule",
30153024
"message.remove.ip.v6.firewall.rule.processing": "Removing IPv6 firewall rule...",
30163025
"message.remove.ip.v6.firewall.rule.success": "Removed IPv6 firewall rule",
30173026
"message.remove.sslcert.failed": "Failed to remove SSL certificate from load balancer",
30183027
"message.remove.sslcert.processing": "Removing SSL certificate from load balancer...",
3028+
"message.add.latest.kubernetes.iso.failed": "Failed to add latest Kubernetes ISO",
3029+
"message.add.minimum.required.compute.offering.kubernetes.cluster.failed": "Failed to add minimum required Compute Offering for Kubernetes cluster nodes",
30193030
"message.add.netris.controller": "Add Netris Provider",
30203031
"message.add.nsx.controller": "Add NSX Provider",
30213032
"message.add.network": "Add a new network for Zone: <b><span id=\"zone_name\"></span></b>",
@@ -3056,9 +3067,13 @@
30563067
"message.add.vpn.gateway": "Please confirm that you want to add a VPN Gateway.",
30573068
"message.add.vpn.gateway.failed": "Adding VPN gateway failed",
30583069
"message.add.vpn.gateway.processing": "Adding VPN gateway...",
3070+
"message.added.latest.kubernetes.iso": "Latest Kubernetes ISO added successfully",
3071+
"message.added.minimum.required.compute.offering.kubernetes.cluster": "Minimum required Compute Offering for Kubernetes cluster nodes added successfully",
30593072
"message.added.vpc.offering": "Added VPC offering",
30603073
"message.adding.firewall.policy": "Adding Firewall Policy",
30613074
"message.adding.host": "Adding host",
3075+
"message.adding.latest.kubernetes.iso": "Adding latest Kubernetes ISO",
3076+
"message.adding.minimum.required.compute.offering.kubernetes.cluster": "Adding minimum required Compute Offering for Kubernetes cluster nodes",
30623077
"message.adding.netscaler.device": "Adding Netscaler device",
30633078
"message.adding.netscaler.provider": "Adding Netscaler provider",
30643079
"message.adding.nodes.to.cluster": "Adding nodes to Kubernetes cluster",
@@ -3106,6 +3121,8 @@
31063121
"message.config.health.monitor.failed": "Configure Health Monitor failed",
31073122
"message.config.sticky.policy.failed": "Failed to configure sticky policy.",
31083123
"message.config.sticky.policy.processing": "Updating sticky policy...",
3124+
"message.config.updated": "Configuration updated successfully.",
3125+
"message.config.update.failed": "Failed to update configuration.",
31093126
"message.configure.network.ip.and.mac": "Please configure the IP address and mac address of networks if needed.",
31103127
"message.configure.network.select.default.network": "Please configure the IP address and mac address of networks if needed. Please select a network as the default network.",
31113128
"message.configuring.guest.traffic": "Configuring guest traffic",

ui/src/api/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,7 @@ export function oauthlogin (arg) {
140140
}
141141
})
142142
}
143+
144+
export function getBaseUrl () {
145+
return vueProps.axios.defaults.baseURL
146+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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+
18+
<template>
19+
<div>
20+
<div v-for="advisory in advisories" :key="advisory.id" style="margin-bottom: 10px;">
21+
<a-alert
22+
:type="advisory.severity || 'info'"
23+
:show-icon="true"
24+
:closable="true"
25+
:message="$t(advisory.message)"
26+
@close="onAlertClose(advisory)">
27+
<template #description>
28+
<a-space direction="horizontal" size="small">
29+
<span v-for="(action, idx) in advisory.actions" :key="idx">
30+
<a-button
31+
v-if="typeof action.show === 'function' ? action.show($store) : action.show"
32+
size="small"
33+
:type="(action.primary || advisory.actions.length === 1) ? 'primary' : 'default'"
34+
@click="onAlertBtnClick(action, advisory)">
35+
{{ $t(action.label) }}
36+
</a-button>
37+
</span>
38+
</a-space>
39+
</template>
40+
</a-alert>
41+
</div>
42+
</div>
43+
</template>
44+
45+
<script>
46+
47+
const DISMISSED_ADVISORIES_KEY = 'dismissed_advisories'
48+
49+
export default {
50+
name: 'AdvisoriesView',
51+
components: {
52+
},
53+
props: {},
54+
data () {
55+
return {
56+
advisories: []
57+
}
58+
},
59+
created () {
60+
this.evaluateAdvisories()
61+
},
62+
computed: {
63+
},
64+
methods: {
65+
evaluateAdvisories () {
66+
this.advisories = []
67+
const metaAdvisories = this.$route.meta.advisories || []
68+
const dismissedAdvisories = this.$localStorage.get(DISMISSED_ADVISORIES_KEY) || []
69+
for (const advisory of metaAdvisories) {
70+
if (dismissedAdvisories.includes(advisory.id)) {
71+
continue
72+
}
73+
Promise.resolve(advisory.condition(this.$store)).then(active => {
74+
if (active) {
75+
this.advisories.push(advisory)
76+
} else if (advisory.dismissOnConditionFail) {
77+
this.dismissAdvisory(advisory.id, true)
78+
}
79+
})
80+
}
81+
},
82+
onAlertClose (advisory) {
83+
this.dismissAdvisory(advisory.id)
84+
},
85+
dismissAdvisory (advisoryId, skipUpdateLocal) {
86+
let dismissedAdvisories = this.$localStorage.get(DISMISSED_ADVISORIES_KEY) || []
87+
dismissedAdvisories = dismissedAdvisories.filter(id => id !== advisoryId)
88+
dismissedAdvisories.push(advisoryId)
89+
this.$localStorage.set(DISMISSED_ADVISORIES_KEY, dismissedAdvisories)
90+
if (skipUpdateLocal) {
91+
return
92+
}
93+
this.advisories = this.advisories.filter(advisory => advisory.id !== advisoryId)
94+
},
95+
undismissAdvisory (advisory, evaluate) {
96+
let dismissedAdvisories = this.$localStorage.get(DISMISSED_ADVISORIES_KEY) || []
97+
dismissedAdvisories = dismissedAdvisories.filter(id => id !== advisory.id)
98+
this.$localStorage.set(DISMISSED_ADVISORIES_KEY, dismissedAdvisories)
99+
if (evaluate) {
100+
Promise.resolve(advisory.condition(this.$store)).then(active => {
101+
if (active) {
102+
this.advisories.push(advisory)
103+
}
104+
})
105+
} else {
106+
this.advisories.push(advisory)
107+
}
108+
},
109+
handleAdvisoryActionError (action, advisory, evaluate) {
110+
if (action.errorMessage) {
111+
this.showActionMessage('error', advisory.id, action.errorMessage)
112+
}
113+
this.undismissAdvisory(advisory, evaluate)
114+
},
115+
handleAdvisoryActionResult (action, advisory, result) {
116+
if (result && action.successMessage) {
117+
this.showActionMessage('success', advisory.id, action.successMessage)
118+
return
119+
}
120+
this.handleAdvisoryActionError(action, advisory, false)
121+
},
122+
showActionMessage (type, key, content) {
123+
const data = {
124+
content: this.$t(content),
125+
key: key,
126+
duration: type === 'loading' ? 0 : 3
127+
}
128+
if (type === 'loading') {
129+
this.$message.loading(data)
130+
} else if (type === 'success') {
131+
this.$message.success(data)
132+
} else if (type === 'error') {
133+
this.$message.error(data)
134+
} else {
135+
this.$message.info(data)
136+
}
137+
},
138+
onAlertBtnClick (action, advisory) {
139+
this.dismissAdvisory(advisory.id)
140+
if (typeof action.run !== 'function') {
141+
return
142+
}
143+
if (action.loadingLabel) {
144+
this.showActionMessage('loading', advisory.id, action.loadingLabel)
145+
}
146+
const result = action.run(this.$store, this.$router)
147+
if (result instanceof Promise) {
148+
result.then(success => {
149+
this.handleAdvisoryActionResult(action, advisory, success)
150+
}).catch(() => {
151+
this.handleAdvisoryActionError(action, advisory, true)
152+
})
153+
} else {
154+
this.handleAdvisoryActionResult(action, advisory, result)
155+
}
156+
}
157+
}
158+
}
159+
</script>

ui/src/config/router.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ function generateRouterMap (section) {
8181
filters: child.filters,
8282
params: child.params ? child.params : {},
8383
columns: child.columns,
84+
advisories: child.advisories,
8485
details: child.details,
8586
searchFilters: child.searchFilters,
8687
related: child.related,
@@ -180,6 +181,10 @@ function generateRouterMap (section) {
180181
map.meta.columns = section.columns
181182
}
182183

184+
if (section.advisories) {
185+
map.meta.advisories = section.advisories
186+
}
187+
183188
if (section.actions) {
184189
map.meta.actions = section.actions
185190
}

0 commit comments

Comments
 (0)