Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions ui/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,10 @@
"label.add.isolated.network": "Add Isolated Network",
"label.add.kubernetes.cluster": "Add Kubernetes Cluster",
"label.add.acl.name": "ACL name",
"label.add.latest.kubernetes.iso": "Add latest Kubernetes ISO",
"label.add.ldap.account": "Add LDAP Account",
"label.add.logical.router": "Add Logical Router to this Network",
"label.add.minimum.required.compute.offering": "Add minimum required Compute Offering",
"label.add.more": "Add more",
"label.add.nodes": "Add Nodes to Kubernetes Cluster",
"label.add.netscaler.device": "Add Netscaler Device",
Expand Down Expand Up @@ -1078,6 +1080,7 @@
"label.firstname": "First name",
"label.firstname.lower": "firstname",
"label.fix.errors": "Fix errors",
"label.fix.global.setting": "Fix Global Setting",
"label.fixed": "Fixed Offering",
"label.for": "for",
"label.forcks": "For CKS",
Expand Down Expand Up @@ -1110,6 +1113,9 @@
"label.globo.dns.configuration": "GloboDNS configuration",
"label.glustervolume": "Volume",
"label.go.back": "Go back",
"label.go.to.compute.offerings": "Go to Compute Offerings",
"label.go.to.global.settings": "Go to Global Settings",
"label.go.to.kubernetes.isos": "Go to Kubernetes ISOs",
"label.gpu": "GPU",
"label.gpucardid": "GPU Card",
"label.gpucardname": "GPU Card",
Expand Down Expand Up @@ -3010,12 +3016,17 @@
"message.add.ip.v6.firewall.rule.failed": "Failed to add IPv6 firewall rule",
"message.add.ip.v6.firewall.rule.processing": "Adding IPv6 firewall rule...",
"message.add.ip.v6.firewall.rule.success": "Added IPv6 firewall rule",
"message.advisory.cks.endpoint.url.not.configured": "Endpoint URL which will be used by Kubernetes clusters is not configured correctly",
"message.advisory.cks.min.offering": "No suitable Compute Offering found for Kubernetes cluster nodes with minimum required resources (2 vCPU, 2 GB RAM)",
"message.advisory.cks.version.check": "No Kubernetes version found that can be used to deploy a Kubernetes cluster",
"message.redeliver.webhook.delivery": "Redeliver this Webhook delivery",
"message.remove.ip.v6.firewall.rule.failed": "Failed to remove IPv6 firewall rule",
"message.remove.ip.v6.firewall.rule.processing": "Removing IPv6 firewall rule...",
"message.remove.ip.v6.firewall.rule.success": "Removed IPv6 firewall rule",
"message.remove.sslcert.failed": "Failed to remove SSL certificate from load balancer",
"message.remove.sslcert.processing": "Removing SSL certificate from load balancer...",
"message.add.latest.kubernetes.iso.failed": "Failed to add latest Kubernetes ISO",
"message.add.minimum.required.compute.offering.kubernetes.cluster.failed": "Failed to add minimum required Compute Offering for Kubernetes cluster nodes",
"message.add.netris.controller": "Add Netris Provider",
"message.add.nsx.controller": "Add NSX Provider",
"message.add.network": "Add a new network for Zone: <b><span id=\"zone_name\"></span></b>",
Expand Down Expand Up @@ -3056,9 +3067,13 @@
"message.add.vpn.gateway": "Please confirm that you want to add a VPN Gateway.",
"message.add.vpn.gateway.failed": "Adding VPN gateway failed",
"message.add.vpn.gateway.processing": "Adding VPN gateway...",
"message.added.latest.kubernetes.iso": "Latest Kubernetes ISO added successfully",
"message.added.minimum.required.compute.offering.kubernetes.cluster": "Minimum required Compute Offering for Kubernetes cluster nodes added successfully",
"message.added.vpc.offering": "Added VPC offering",
"message.adding.firewall.policy": "Adding Firewall Policy",
"message.adding.host": "Adding host",
"message.adding.latest.kubernetes.iso": "Adding latest Kubernetes ISO",
"message.adding.minimum.required.compute.offering.kubernetes.cluster": "Adding minimum required Compute Offering for Kubernetes cluster nodes",
"message.adding.netscaler.device": "Adding Netscaler device",
"message.adding.netscaler.provider": "Adding Netscaler provider",
"message.adding.nodes.to.cluster": "Adding nodes to Kubernetes cluster",
Expand Down Expand Up @@ -3494,6 +3509,8 @@
"message.failed.to.remove": "Failed to remove",
"message.forgot.password.success": "An email has been sent to your email address with instructions on how to reset your password.",
"message.generate.keys": "Please confirm that you would like to generate new API/Secret keys for this User.",
"message.global.setting.updated": "Global Setting updated successfully.",
"message.global.setting.update.failed": "Failed to update Global Setting.",
"message.chart.statistic.info": "The shown charts are self-adjustable, that means, if the value gets close to the limit or overpass it, it will grow to adjust the shown value",
"message.chart.statistic.info.hypervisor.additionals": "The metrics data depend on the hypervisor plugin used for each hypervisor. The behavior can vary across different hypervisors. For instance, with KVM, metrics are real-time statistics provided by libvirt. In contrast, with VMware, the metrics are averaged data for a given time interval controlled by configuration.",
"message.guest.traffic.in.advanced.zone": "Guest Network traffic is communication between end-user Instances. Specify a range of VLAN IDs or VXLAN Network identifiers (VNIs) to carry guest traffic for each physical Network.",
Expand Down
4 changes: 4 additions & 0 deletions ui/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,7 @@ export function oauthlogin (arg) {
}
})
}

export function getBaseUrl () {
return vueProps.axios.defaults.baseURL
}
161 changes: 161 additions & 0 deletions ui/src/components/view/AdvisoriesView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// 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.

<template>
<div>
<div v-for="advisory in advisories" :key="advisory.id" style="margin-bottom: 10px;">
<a-alert
:type="advisory.severity || 'info'"
:show-icon="true"
:closable="true"
:message="$t(advisory.message)"
@close="onAlertClose(advisory)">
<template #description>
<a-space direction="horizontal" size="small">
<span v-for="(action, idx) in advisory.actions" :key="idx">
<a-button
v-if="typeof action.show === 'function' ? action.show($store) : action.show"
size="small"
:type="(action.primary || advisory.actions.length === 1) ? 'primary' : 'default'"
@click="onAlertBtnClick(action, advisory)">
{{ $t(action.label) }}
</a-button>
</span>
</a-space>
</template>
</a-alert>
</div>
</div>
</template>

<script>

const DISMISSED_ADVISORIES_KEY = 'dismissed_advisories'

export default {
name: 'AdvisoriesView',
components: {
},
props: {},
data () {
return {
advisories: []
}
},
created () {
this.evaluateAdvisories()
},
computed: {
},
methods: {
async evaluateAdvisories () {
this.advisories = []
const metaAdvisories = this.$route.meta.advisories || []
const dismissedAdvisories = this.$localStorage.get(DISMISSED_ADVISORIES_KEY) || []
const advisoryPromises = metaAdvisories.map(async advisory => {
if (dismissedAdvisories.includes(advisory.id)) {
return null
}
const active = await Promise.resolve(advisory.condition(this.$store))
if (active) {
return advisory
} else if (advisory.dismissOnConditionFail) {
this.dismissAdvisory(advisory.id, true)
}
return null
})
const results = await Promise.all(advisoryPromises)
this.advisories = results.filter(a => a !== null)
},
onAlertClose (advisory) {
this.dismissAdvisory(advisory.id)
},
dismissAdvisory (advisoryId, skipUpdateLocal) {
let dismissedAdvisories = this.$localStorage.get(DISMISSED_ADVISORIES_KEY) || []
dismissedAdvisories = dismissedAdvisories.filter(id => id !== advisoryId)
dismissedAdvisories.push(advisoryId)
this.$localStorage.set(DISMISSED_ADVISORIES_KEY, dismissedAdvisories)
if (skipUpdateLocal) {
return
}
this.advisories = this.advisories.filter(advisory => advisory.id !== advisoryId)
},
undismissAdvisory (advisory, evaluate) {
let dismissedAdvisories = this.$localStorage.get(DISMISSED_ADVISORIES_KEY) || []
dismissedAdvisories = dismissedAdvisories.filter(id => id !== advisory.id)
this.$localStorage.set(DISMISSED_ADVISORIES_KEY, dismissedAdvisories)
if (evaluate) {
Promise.resolve(advisory.condition(this.$store)).then(active => {
if (active) {
this.advisories.push(advisory)
}
})
} else {
this.advisories.push(advisory)
}
},
handleAdvisoryActionError (action, advisory, evaluate) {
if (action.errorMessage) {
this.showActionMessage('error', advisory.id, action.errorMessage)
}
this.undismissAdvisory(advisory, evaluate)
},
handleAdvisoryActionResult (action, advisory, result) {
if (result && action.successMessage) {
this.showActionMessage('success', advisory.id, action.successMessage)
return
}
this.handleAdvisoryActionError(action, advisory, false)
},
showActionMessage (type, key, content) {
const data = {
content: this.$t(content),
key: key,
duration: type === 'loading' ? 0 : 3
}
if (type === 'loading') {
this.$message.loading(data)
} else if (type === 'success') {
this.$message.success(data)
} else if (type === 'error') {
this.$message.error(data)
} else {
this.$message.info(data)
}
},
onAlertBtnClick (action, advisory) {
this.dismissAdvisory(advisory.id)
if (typeof action.run !== 'function') {
return
}
if (action.loadingLabel) {
this.showActionMessage('loading', advisory.id, action.loadingLabel)
}
const result = action.run(this.$store, this.$router)
if (result instanceof Promise) {
result.then(success => {
this.handleAdvisoryActionResult(action, advisory, success)
}).catch(() => {
this.handleAdvisoryActionError(action, advisory, true)
})
} else {
this.handleAdvisoryActionResult(action, advisory, result)
}
}
}
}
</script>
5 changes: 5 additions & 0 deletions ui/src/config/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ function generateRouterMap (section) {
filters: child.filters,
params: child.params ? child.params : {},
columns: child.columns,
advisories: !vueProps.$config.advisoriesDisabled ? child.advisories : undefined,
details: child.details,
searchFilters: child.searchFilters,
related: child.related,
Expand Down Expand Up @@ -180,6 +181,10 @@ function generateRouterMap (section) {
map.meta.columns = section.columns
}

if (!vueProps.$config.advisoriesDisabled && section.advisories) {
map.meta.advisories = section.advisories
}

if (section.actions) {
map.meta.actions = section.actions
}
Expand Down
Loading
Loading