Show warning icons in tabs (#260)

* Add unstyled warning to onion tab

* Style the warning icon

* Add warning for http3

* Add warning icon to global security tab

* Surface warnings for domains

* Show warning icon for duplicate domains

* Improve http3 warning string names

* Show warning message/icon for Brotli
This commit is contained in:
Matt (IPv4) Cowley 2021-05-13 15:28:17 +01:00 committed by GitHub
parent fe5f2b234d
commit 27d090daeb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 172 additions and 79 deletions

View file

@ -42,9 +42,9 @@ export default {
certificationType: 'Certification type',
customCertificate: 'Custom certificate',
letsEncryptEmail: `${common.letsEncrypt} email`,
http3Warning1: 'HTTP/3 isn\'t a standard NGINX module, check the ',
http3Warning2: 'NGINX QUIC readme ',
http3Warning3: ' or the ',
http3Warning4: 'Cloudflare quiche project ',
http3Warning5: ' for how to build NGINX with HTTP/3!',
http3IsANonStandardModule: 'HTTP/3 isn\'t a standard NGINX module, check the ',
http3NginxQuicReadme: 'NGINX QUIC readme',
http3OrThe: ' or the ',
http3CloudflareQuicheProject: 'Cloudflare quiche project',
http3ForBuildingNginxWithHttp3: ' for how to build NGINX with HTTP/3!',
};

View file

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -31,6 +31,9 @@ export default {
enableGzipCompression: `${common.enable} gzip compression`,
brotliCompression: 'Brotli compression',
enableBrotliCompression: `${common.enable} brotli compression`,
brotliIsANonStandardModule: 'Brotli isn\'t a standard NGINX module, check the ',
brotliGoogleNgxBrotliProject: 'Google ngx_brotli project',
brotliForBuildingNginxWithBrotli: ' for how to build NGINX with Brotli!',
expirationForAssets: 'Expiration for assets',
expirationForMedia: 'Expiration for media',
expirationForSvgs: 'Expiration for SVGs',

View file

@ -42,9 +42,9 @@ export default {
certificationType: 'Type de certification',
customCertificate: 'Certificat personnalisé',
letsEncryptEmail: `E-mail ${common.letsEncrypt}`,
http3Warning1: 'HTTP/3 isn\'t a standard NGINX module, check the ', // TODO: translate
http3Warning2: 'NGINX QUIC readme ', // TODO: translate
http3Warning3: ' or the ', // TODO: translate
http3Warning4: 'Cloudflare quiche project ', // TODO: translate
http3Warning5: ' for how to build NGINX with HTTP/3!', // TODO: translate
http3IsANonStandardModule: 'HTTP/3 isn\'t a standard NGINX module, check the ', // TODO: translate
http3NginxQuicReadme: 'NGINX QUIC readme', // TODO: translate
http3OrThe: ' or the ', // TODO: translate
http3CloudflareQuicheProject: 'Cloudflare quiche project', // TODO: translate
http3ForBuildingNginxWithHttp3: ' for how to build NGINX with HTTP/3!', // TODO: translate
};

View file

@ -31,6 +31,9 @@ export default {
enableGzipCompression: `${common.enable} la compression gzip`,
brotliCompression: 'Compression Brotli',
enableBrotliCompression: `${common.enable} la compression brotli`,
brotliIsANonStandardModule: 'Brotli isn\'t a standard NGINX module, check the ', // TODO: translate
brotliGoogleNgxBrotliProject: 'Google ngx_brotli project', // TODO: translate
brotliForBuildingNginxWithBrotli: ' for how to build NGINX with Brotli!', // TODO: translate
expirationForAssets: 'Expiration des assets',
expirationForMedia: 'Expiration des medias',
expirationForSvgs: 'Expiration des SVGs',

View file

@ -42,9 +42,9 @@ export default {
certificationType: 'Tipo de certificação',
customCertificate: 'Certificado personalizado',
letsEncryptEmail: `E-mail do ${common.letsEncrypt}`,
http3Warning1: 'HTTP/3 isn\'t a standard NGINX module, check the ', // TODO: translate
http3Warning2: 'NGINX QUIC readme ', // TODO: translate
http3Warning3: ' or the ', // TODO: translate
http3Warning4: 'Cloudflare quiche project ', // TODO: translate
http3Warning5: ' for how to build NGINX with HTTP/3!', // TODO: translate
http3IsANonStandardModule: 'HTTP/3 isn\'t a standard NGINX module, check the ', // TODO: translate
http3NginxQuicReadme: 'NGINX QUIC readme', // TODO: translate
http3OrThe: ' or the ', // TODO: translate
http3CloudflareQuicheProject: 'Cloudflare quiche project', // TODO: translate
http3ForBuildingNginxWithHttp3: ' for how to build NGINX with HTTP/3!', // TODO: translate
};

View file

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -31,6 +31,9 @@ export default {
enableGzipCompression: `${common.enable} compressão gzip`,
brotliCompression: 'Compressão Brotli',
enableBrotliCompression: `${common.enable} compressão brotli`,
brotliIsANonStandardModule: 'Brotli isn\'t a standard NGINX module, check the ', // TODO: translate
brotliGoogleNgxBrotliProject: 'Google ngx_brotli project', // TODO: translate
brotliForBuildingNginxWithBrotli: ' for how to build NGINX with Brotli!', // TODO: translate
expirationForAssets: 'Expiração de ativos',
expirationForMedia: 'Expiração de mídia',
expirationForSvgs: 'Expiração de SVGs',

View file

@ -42,9 +42,9 @@ export default {
certificationType: 'Тип сертификации',
customCertificate: 'Другой сертификат',
letsEncryptEmail: `${common.letsEncrypt} email`,
http3Warning1: 'HTTP/3 isn\'t a standard NGINX module, check the ', // TODO: translate
http3Warning2: 'NGINX QUIC readme ', // TODO: translate
http3Warning3: ' or the ', // TODO: translate
http3Warning4: 'Cloudflare quiche project ', // TODO: translate
http3Warning5: ' for how to build NGINX with HTTP/3!', // TODO: translate
http3IsANonStandardModule: 'HTTP/3 isn\'t a standard NGINX module, check the ', // TODO: translate
http3NginxQuicReadme: 'NGINX QUIC readme', // TODO: translate
http3OrThe: ' or the ', // TODO: translate
http3CloudflareQuicheProject: 'Cloudflare quiche project', // TODO: translate
http3ForBuildingNginxWithHttp3: ' for how to build NGINX with HTTP/3!', // TODO: translate
};

View file

@ -31,6 +31,9 @@ export default {
enableGzipCompression: `${common.enable} gzip сжатие`,
brotliCompression: 'Brotli сжатие',
enableBrotliCompression: `${common.enable} brotli сжатие`,
brotliIsANonStandardModule: 'Brotli isn\'t a standard NGINX module, check the ', // TODO: translate
brotliGoogleNgxBrotliProject: 'Google ngx_brotli project', // TODO: translate
brotliForBuildingNginxWithBrotli: ' for how to build NGINX with Brotli!', // TODO: translate
expirationForAssets: 'Истечение срока для ассетов',
expirationForMedia: 'Истечение срока для медиа файлов',
expirationForSvgs: 'Истечение срока для SVG файлов',

View file

@ -42,9 +42,9 @@ export default {
certificationType: '证书类型',
customCertificate: '本地证书',
letsEncryptEmail: `${common.letsEncrypt} 邮箱`,
http3Warning1: 'HTTP/3 并不是一个标准的 NGINX 模块, 请查看 ',
http3Warning2: 'NGINX QUIC 使用文档',
http3Warning3: ' 或者 ',
http3Warning4: 'Cloudflare quiche 项目',
http3Warning5: ' 以构建支持 HTTP/3 的 NGINX!',
http3IsANonStandardModule: 'HTTP/3 并不是一个标准的 NGINX 模块, 请查看 ',
http3NginxQuicReadme: 'NGINX QUIC 使用文档',
http3OrThe: ' 或者 ',
http3CloudflareQuicheProject: 'Cloudflare quiche 项目',
http3ForBuildingNginxWithHttp3: ' 以构建支持 HTTP/3 的 NGINX!',
};

View file

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -31,6 +31,9 @@ export default {
enableGzipCompression: `${common.enable}Gzip压缩`,
brotliCompression: 'Brotli 压缩',
enableBrotliCompression: `${common.enable}brotli压缩`,
brotliIsANonStandardModule: 'Brotli isn\'t a standard NGINX module, check the ', // TODO: translate
brotliGoogleNgxBrotliProject: 'Google ngx_brotli project', // TODO: translate
brotliForBuildingNginxWithBrotli: ' for how to build NGINX with Brotli!', // TODO: translate
expirationForAssets: '资源有效期',
expirationForMedia: '媒体资源有效期',
expirationForSvgs: 'SVGs有效期',

View file

@ -42,9 +42,9 @@ export default {
certificationType: '證書類型',
customCertificate: '本地證書',
letsEncryptEmail: `${common.letsEncrypt} 郵箱`,
http3Warning1: 'HTTP/3 isn\'t a standard NGINX module, check the ', // TODO: translate
http3Warning2: 'NGINX QUIC readme ', // TODO: translate
http3Warning3: ' or the ', // TODO: translate
http3Warning4: 'Cloudflare quiche project ', // TODO: translate
http3Warning5: ' for how to build NGINX with HTTP/3!', // TODO: translate
http3IsANonStandardModule: 'HTTP/3 isn\'t a standard NGINX module, check the ', // TODO: translate
http3NginxQuicReadme: 'NGINX QUIC readme', // TODO: translate
http3OrThe: ' or the ', // TODO: translate
http3CloudflareQuicheProject: 'Cloudflare quiche project', // TODO: translate
http3ForBuildingNginxWithHttp3: ' for how to build NGINX with HTTP/3!', // TODO: translate
};

View file

@ -1,5 +1,5 @@
/*
Copyright 2020 DigitalOcean
Copyright 2021 DigitalOcean
This code is licensed under the MIT License.
You may obtain a copy of the License at
@ -31,6 +31,9 @@ export default {
enableGzipCompression: `${common.enable}Gzip壓縮`,
brotliCompression: 'Brotli 壓縮',
enableBrotliCompression: `${common.enable}brotli壓縮`,
brotliIsANonStandardModule: 'Brotli isn\'t a standard NGINX module, check the ', // TODO: translate
brotliGoogleNgxBrotliProject: 'Google ngx_brotli project', // TODO: translate
brotliForBuildingNginxWithBrotli: ' for how to build NGINX with Brotli!', // TODO: translate
expirationForAssets: '資源有效期',
expirationForMedia: '媒體資源有效期',
expirationForSvgs: 'SVGs有效期',

View file

@ -53,6 +53,10 @@ THE SOFTWARE.
&.has-margin-top {
margin-top: .75rem;
}
&.has-small-margin-top {
margin-top: .25rem;
}
}
> p {

View file

@ -97,17 +97,24 @@ THE SOFTWARE.
}
i {
font-size: .75em;
margin: 0;
}
}
// Handle having the remove icon as well as the add icon
i {
font-size: .75em;
// Add domain button
&.fa-plus {
font-size: .75em;
margin: 0 .35rem 0 0;
}
// Warning icon
&.fa-exclamation-triangle {
color: $warning-hover;
font-size: .9em;
margin: 0 0 .1rem .35rem;
}
}
}
}

View file

@ -66,6 +66,7 @@ THE SOFTWARE.
<li v-for="data in activeDomains" :class="data[1] === active ? 'is-active' : undefined">
<a class="domain" @click="active = data[1]">
{{ data[0].server.domain.computed }}{{ changes(data[1]) }}
<i v-if="warnings(data[1])" class="fas fa-exclamation-triangle"></i>
</a>
<a class="remove" @click="remove(data[1])">
<i class="fas fa-times"></i>
@ -79,6 +80,7 @@ THE SOFTWARE.
<template v-for="data in activeDomains">
<Domain :key="data[1]"
:ref="`domain-${data[1]}`"
:data="data[0]"
:style="{ display: data[1] === active ? undefined : 'none' }"
></Domain>
@ -284,6 +286,10 @@ THE SOFTWARE.
if (changes) return ` (${changes.toLocaleString()})`;
return '';
},
warnings(index) {
if (!Object.prototype.hasOwnProperty.call(this.$refs, `domain-${index}`)) return false;
return this.$refs[`domain-${index}`][0].hasWarnings || false;
},
add() {
const data = clone(Domain.delegated);

View file

@ -34,7 +34,10 @@ THE SOFTWARE.
<div class="tabs">
<ul>
<li v-for="tab in tabs" :class="tabClass(tab.key)">
<a @click="showTab(tab.key)">{{ $t(tab.display) }}{{ changes(tab.key) }}</a>
<a @click="showTab(tab.key)">
{{ $t(tab.display) }}{{ changes(tab.key) }}
<i v-if="warnings(tab.key)" class="fas fa-exclamation-triangle"></i>
</a>
</li>
</ul>
</div>
@ -42,6 +45,7 @@ THE SOFTWARE.
<component :is="tab"
v-for="tab in tabs"
:key="tab.key"
:ref="tab.key"
:data="$props.data[tab.key]"
:style="{ display: active === tab.key ? undefined : 'none' }"
class="container"
@ -102,6 +106,9 @@ THE SOFTWARE.
if (index >= 0) return tabs[index];
return false;
},
hasWarnings() {
return Object.values(this.$refs).some(ref => ref[0].hasWarnings || false);
},
},
methods: {
changesCount(tab) {
@ -113,6 +120,10 @@ THE SOFTWARE.
if (changes) return ` (${changes.toLocaleString()})`;
return '';
},
warnings(tab) {
if (!Object.prototype.hasOwnProperty.call(this.$refs, tab)) return false;
return this.$refs[tab][0].hasWarnings || false;
},
setValue(tab, key, val) {
Object.assign(this.$props.data[tab][key], { value: val, computed: val });
},

View file

@ -62,8 +62,8 @@ THE SOFTWARE.
</div>
</div>
<div v-if="http3Enabled" class="field is-horizontal">
<div class="field-label">
<div v-if="http3Enabled" class="field is-horizontal is-aligned-top">
<div class="field-label has-small-margin-top">
<label class="label">{{ $t('templates.domainSections.https.http3') }}</label>
</div>
<div class="field-body">
@ -76,6 +76,22 @@ THE SOFTWARE.
</PrettyCheck>
</div>
</div>
<div v-if="showHttp3Warning" class="control">
<label class="text message is-warning">
<span class="message-body">
{{ $t('templates.domainSections.https.http3IsANonStandardModule') }}
<ExternalLink :text="$t('templates.domainSections.https.http3NginxQuicReadme')"
link="https://quic.nginx.org/README"
></ExternalLink>
{{ $t('templates.domainSections.https.http3OrThe') }}
<ExternalLink :text="$t('templates.domainSections.https.http3CloudflareQuicheProject')"
link="https://github.com/cloudflare/quiche/tree/master/extras/nginx"
></ExternalLink>
{{ $t('templates.domainSections.https.http3ForBuildingNginxWithHttp3') }}
</span>
</label>
</div>
</div>
</div>
</div>
@ -224,23 +240,6 @@ THE SOFTWARE.
</div>
</div>
</div>
<template v-if="$props.data.http3.value">
<br />
<div class="message is-warning">
<div class="message-body">
{{ $t('templates.domainSections.https.http3Warning1') }}
<ExternalLink :text="$t('templates.domainSections.https.http3Warning2')"
link="https://quic.nginx.org/README"
></ExternalLink>
{{ $t('templates.domainSections.https.http3Warning3') }}
<ExternalLink :text="$t('templates.domainSections.https.http3Warning4')"
link="https://github.com/cloudflare/quiche/tree/master/extras/nginx"
></ExternalLink>
{{ $t('templates.domainSections.https.http3Warning5') }}
</div>
</div>
</template>
</div>
</template>
@ -320,7 +319,15 @@ THE SOFTWARE.
props: {
data: Object, // Data delegated back to us from parent
},
computed: computedFromDefaults(defaults, 'https'), // Getters & setters for the delegated data
computed: {
...computedFromDefaults(defaults, 'https'), // Getters & setters for the delegated data
showHttp3Warning() {
return this.$props.data.http3.computed;
},
hasWarnings() {
return this.showHttp3Warning;
},
},
watch: {
// Disable everything if https is disabled
'$props.data.https': {

View file

@ -98,6 +98,9 @@ THE SOFTWARE.
incorrectEnding() {
return this.onionLocationChanged && !this.$props.data.onionLocation.computed.endsWith('.onion');
},
hasWarnings() {
return this.incorrectEnding;
},
},
watch: {
'$props.data.onionLocation': {

View file

@ -49,16 +49,17 @@ THE SOFTWARE.
</div>
</div>
<template v-if="duplicateDomain">
<br />
<div class="message is-warning">
<div class="message-body">
{{ $t('templates.domainSections.server.oneOrMoreOtherDomainsAreAlsoNamed') }}
<code class="slim">{{ $props.data.domain.computed }}</code>.
{{ $t('templates.domainSections.server.thisWillCauseIssuesWithConfigGeneration') }}
</div>
<div v-if="duplicateDomain" class="field">
<div class="control">
<label class="text message is-warning">
<span class="message-body">
{{ $t('templates.domainSections.server.oneOrMoreOtherDomainsAreAlsoNamed') }}
<code class="slim">{{ $props.data.domain.computed }}</code>.
{{ $t('templates.domainSections.server.thisWillCauseIssuesWithConfigGeneration') }}
</span>
</label>
</div>
</template>
</div>
<div class="field is-horizontal">
<div class="field-label">
@ -204,6 +205,9 @@ THE SOFTWARE.
return this.$parent.$parent.$data.domains
.filter(d => d && d.server.domain.computed === this.$props.data.domain.computed).length > 1;
},
hasWarnings() {
return this.duplicateDomain;
},
},
watch: {
'$props.data.domain': {

View file

@ -29,7 +29,10 @@ THE SOFTWARE.
<div class="tabs">
<ul>
<li v-for="tab in tabs" :class="tabClass(tab.key)">
<a @click="showTab(tab.key)">{{ $t(tab.display) }}{{ changes(tab.key) }}</a>
<a @click="showTab(tab.key)">
{{ $t(tab.display) }}{{ changes(tab.key) }}
<i v-if="warnings(tab.key)" class="fas fa-exclamation-triangle"></i>
</a>
</li>
</ul>
</div>
@ -37,6 +40,7 @@ THE SOFTWARE.
<component :is="tab"
v-for="tab in tabs"
:key="tab.key"
:ref="tab.key"
:data="$props.data[tab.key]"
:style="{ display: active === tab.key ? undefined : 'none' }"
class="container"
@ -99,6 +103,10 @@ THE SOFTWARE.
if (changes) return ` (${changes.toLocaleString()})`;
return '';
},
warnings(tab) {
if (!Object.prototype.hasOwnProperty.call(this.$refs, tab)) return false;
return this.$refs[tab][0].hasWarnings || false;
},
setValue(tab, key, val) {
Object.assign(this.$props.data[tab][key], { value: val, computed: val });
},

View file

@ -44,8 +44,8 @@ THE SOFTWARE.
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<div class="field is-horizontal is-aligned-top">
<div class="field-label has-small-margin-top">
<label class="label">{{ $t('templates.globalSections.performance.brotliCompression') }}</label>
</div>
<div class="field-body">
@ -58,6 +58,19 @@ THE SOFTWARE.
</PrettyCheck>
</div>
</div>
<div v-if="showBrotliWarning" class="control">
<label class="text message is-warning">
<span class="message-body">
{{ $t('templates.globalSections.performance.brotliIsANonStandardModule') }}
<ExternalLink
:text="$t('templates.globalSections.performance.brotliGoogleNgxBrotliProject')"
link="https://github.com/google/ngx_brotli"
></ExternalLink>
{{ $t('templates.globalSections.performance.brotliForBuildingNginxWithBrotli') }}
</span>
</label>
</div>
</div>
</div>
</div>
@ -134,6 +147,7 @@ THE SOFTWARE.
<script>
import PrettyCheck from 'pretty-checkbox-vue/check';
import ExternalLink from 'do-vue/src/templates/external_link';
import delegatedFromDefaults from '../../util/delegated_from_defaults';
import computedFromDefaults from '../../util/computed_from_defaults';
@ -171,10 +185,19 @@ THE SOFTWARE.
delegated: delegatedFromDefaults(defaults), // Data the parent will present here
components: {
PrettyCheck,
ExternalLink,
},
props: {
data: Object, // Data delegated back to us from parent
},
computed: computedFromDefaults(defaults, 'performance'), // Getters & setters for the delegated data
computed: {
...computedFromDefaults(defaults, 'performance'), // Getters & setters for the delegated data
showBrotliWarning() {
return this.$props.data.brotliCompression.computed;
},
hasWarnings() {
return this.showBrotliWarning;
},
},
};
</script>

View file

@ -55,14 +55,13 @@ THE SOFTWARE.
:placeholder="$props.data.contentSecurityPolicy.default"
/>
</div>
<template v-if="hasWordPress && !hasUnsafeEval">
<br />
<div class="message is-warning">
<div class="message-body"
v-html="$t('templates.globalSections.security.whenUsingWordPressUnsafeEvalIsOftenRequiredToAllowFunctionality')"
></div>
</div>
</template>
<div v-if="hasWordPress && !hasUnsafeEval" class="control">
<label class="text message is-warning">
<span class="message-body"
v-html="$t('templates.globalSections.security.whenUsingWordPressUnsafeEvalIsOftenRequiredToAllowFunctionality')"
></span>
</label>
</div>
</div>
</div>
</div>
@ -203,6 +202,9 @@ THE SOFTWARE.
hasUnsafeEval() {
return this.$props.data.contentSecurityPolicy.computed.includes('\'unsafe-eval\'');
},
hasWarnings() {
return this.hasWordPress && !this.hasUnsafeEval;
},
},
watch: {
// Check referrer policy selection is valid