mirror of https://github.com/apache/cloudstack.git
146 lines
4.5 KiB
Vue
146 lines
4.5 KiB
Vue
// 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>
|
|
<a-affix v-if="showBanner" class="announcement-banner-container">
|
|
<a-alert
|
|
:type="bannerConfig.type || 'default'"
|
|
:show-icon="bannerConfig.showIcon !== false"
|
|
:closable="bannerConfig.closable !== false"
|
|
:banner="true"
|
|
@close="handleClose"
|
|
:style="[ { border: borderColor }]"
|
|
>
|
|
<template #message>
|
|
<div class="banner-content" v-html="sanitizedMessage" :style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#888' }]" />
|
|
</template>
|
|
</a-alert>
|
|
</a-affix>
|
|
</template>
|
|
|
|
<script>
|
|
import DOMPurify from 'dompurify'
|
|
|
|
export default {
|
|
name: 'AnnouncementBanner',
|
|
data () {
|
|
return {
|
|
showBanner: false,
|
|
bannerConfig: {},
|
|
dismissed: false
|
|
}
|
|
},
|
|
computed: {
|
|
sanitizedMessage () {
|
|
if (!this.bannerConfig.message) return ''
|
|
const cleanHTML = DOMPurify.sanitize(this.bannerConfig.message, {
|
|
ALLOWED_TAGS: [
|
|
'p', 'div', 'span', 'br', 'strong', 'b', 'em', 'i', 'u',
|
|
'a', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
'small', 'mark', 'del', 'ins', 'sub', 'sup'
|
|
],
|
|
ALLOWED_ATTR: ['href', 'target', 'rel', 'class', 'id', 'style'],
|
|
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|xxx):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i,
|
|
FORBID_TAGS: ['script', 'object', 'embed', 'form', 'input', 'textarea', 'select', 'button'],
|
|
FORBID_ATTR: ['onclick', 'onload', 'onerror', 'onmouseover', 'onfocus', 'onblur']
|
|
})
|
|
return cleanHTML
|
|
},
|
|
borderColor () {
|
|
const colorMap = {
|
|
error: '#ffa39e',
|
|
warning: '#ffe58f',
|
|
success: '#b7eb8f',
|
|
info: '#b3cde3'
|
|
}
|
|
const color = colorMap[this.bannerConfig.type]
|
|
return color ? `1px solid ${color}` : '0px'
|
|
}
|
|
},
|
|
mounted () {
|
|
this.loadBannerConfig()
|
|
},
|
|
methods: {
|
|
loadBannerConfig () {
|
|
const config = this.$config?.announcementBanner || {}
|
|
if (config && config.enabled && config.message) {
|
|
this.bannerConfig = config
|
|
if (config.persistDismissal) {
|
|
const dismissedKey = `cs-banner-dismissed-${this.getBannerHash()}`
|
|
this.dismissed = this.$localStorage.get(dismissedKey) === 'true'
|
|
}
|
|
if (!this.dismissed && this.isWithinDisplayPeriod()) {
|
|
this.showBanner = true
|
|
}
|
|
}
|
|
},
|
|
isWithinDisplayPeriod () {
|
|
const config = this.bannerConfig
|
|
const now = new Date()
|
|
|
|
if (config.startDate) {
|
|
const startDate = new Date(config.startDate)
|
|
if (now < startDate) return false
|
|
}
|
|
|
|
if (config.endDate) {
|
|
const endDate = new Date(config.endDate)
|
|
if (now > endDate) return false
|
|
}
|
|
return true
|
|
},
|
|
handleClose () {
|
|
this.showBanner = false
|
|
if (this.bannerConfig.persistDismissal) {
|
|
const dismissedKey = `cs-banner-dismissed-${this.getBannerHash()}`
|
|
this.$localStorage.set(dismissedKey, 'true')
|
|
}
|
|
if (this.bannerConfig.onClose) {
|
|
this.bannerConfig.onClose()
|
|
}
|
|
},
|
|
getBannerHash () {
|
|
// Create a simple hash of the message content for dismissal tracking
|
|
let hash = 0
|
|
const str = this.bannerConfig.message || ''
|
|
for (let i = 0; i < str.length; i++) {
|
|
const char = str.charCodeAt(i)
|
|
hash = ((hash << 5) - hash) + char
|
|
hash = hash & hash // Convert to 32bit integer
|
|
}
|
|
return Math.abs(hash).toString()
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.announcement-banner-container {
|
|
z-index: 1000;
|
|
top: 0;
|
|
margin: 0;
|
|
width: 100%;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.banner-content {
|
|
line-height: 1.7;
|
|
text-align: center
|
|
}
|
|
</style>
|