diff --git a/package-lock.json b/package-lock.json index 0612de5..41cadc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8291,6 +8291,11 @@ "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==" }, + "vue-select": { + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/vue-select/-/vue-select-3.10.3.tgz", + "integrity": "sha512-SgLmiSwnJwT2erxjq42AA1iTzu1uqhA5MPuF4UDtGot5YSgJLKy71H0LO0hHaBpIjb7d/nJHiufYKcrSakaupw==" + }, "vue-template-compiler": { "version": "2.6.11", "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz", diff --git a/package.json b/package.json index f255318..08ce233 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "pretty-checkbox-vue": "^1.1.9", "vue": "^2.6.11", "vue-hot-reload-api": "^2.3.3", + "vue-select": "^3.10.3", "vuex": "^3.3.0" }, "devDependencies": { diff --git a/src/nginxconfig/scss/style.scss b/src/nginxconfig/scss/style.scss index 8e82709..87b057a 100644 --- a/src/nginxconfig/scss/style.scss +++ b/src/nginxconfig/scss/style.scss @@ -22,6 +22,8 @@ $highlight: #f2c94c; $pretty--color-dark: $primary; $pretty--color-default: $primary; @import "~pretty-checkbox/src/pretty-checkbox"; + $vs-state-active-bg: $primary; + @import "~vue-select/src/scss/vue-select"; .tabs { ul { @@ -141,9 +143,11 @@ $highlight: #f2c94c; .is-changed { input { - &, - &:focus { - background: rgba($highlight, .35); + &:not(.vs__search) { + &, + &:focus { + background: rgba($highlight, .35); + } } } @@ -158,6 +162,12 @@ $highlight: #f2c94c; padding: .25rem .5rem; } } + + .v-select { + .vs__dropdown-toggle { + background: rgba($highlight, .35); + } + } } label { @@ -228,4 +238,61 @@ $highlight: #f2c94c; } } } + + .v-select { + &.vs--open { + > ul { + opacity: 1; + } + + .vs__dropdown-toggle { + border-color: $primary; + box-shadow: 0 0 2px rgba($success, .5); + + .vs__selected { + top: .75em; + } + } + } + + > ul { + display: block !important; + margin: 0; + opacity: 0; + transition: opacity $transition; + } + + .vs__dropdown-toggle { + border: 1px solid $border; + box-shadow: none; + padding: 0 16px; + transition: border $transition, box-shadow $transition; + + .vs__selected-options { + padding: 0; + + .vs__selected { + margin: 0; + padding: 0; + transition: opacity $transition; + } + + .vs__search { + &, + &:focus { + background: none; + border: 0; + box-shadow: none; + margin: 0; + padding: 0; + width: 0; + } + } + } + + .vs__actions { + padding: 0; + } + } + } } diff --git a/src/nginxconfig/templates/global_sections/index.js b/src/nginxconfig/templates/global_sections/index.js index 96c4b33..9f99235 100644 --- a/src/nginxconfig/templates/global_sections/index.js +++ b/src/nginxconfig/templates/global_sections/index.js @@ -1 +1,2 @@ export { default as HTTPS } from './https'; +export { default as Security } from './security'; diff --git a/src/nginxconfig/templates/global_sections/security.vue b/src/nginxconfig/templates/global_sections/security.vue new file mode 100644 index 0000000..4326ec4 --- /dev/null +++ b/src/nginxconfig/templates/global_sections/security.vue @@ -0,0 +1,138 @@ +<template> + <div> + <div class="field is-horizontal"> + <div class="field-label"> + <label class="label">Referrer-Policy</label> + </div> + <div class="field-body"> + <div class="field"> + <div :class="`control${referrerPolicyChanged ? ' is-changed' : ''}`"> + <VueSelect v-model="referrerPolicy" :options="$props.data.referrerPolicy.options" :clearable="false"></VueSelect> + </div> + </div> + </div> + </div> + + <div class="field is-horizontal"> + <div class="field-label"> + <label class="label">Content-Security-Policy</label> + </div> + <div class="field-body"> + <div class="field"> + <div :class="`control${contentSecurityPolicyChanged ? ' is-changed' : ''}`"> + <input v-model="contentSecurityPolicy" + class="input" + type="text" + :placeholder="$props.data.contentSecurityPolicy.default" + /> + </div> + </div> + </div> + </div> + + <div class="field is-horizontal"> + <div class="field-label"> + <label class="label">server_tokens</label> + </div> + <div class="field-body"> + <div class="field"> + <div :class="`control${serverTokensChanged ? ' is-changed' : ''}`"> + <div class="checkbox"> + <PrettyCheck v-model="serverTokens" class="p-default p-curve p-fill p-icon"> + <i slot="extra" class="icon fas fa-check"></i> + enable + </PrettyCheck> + </div> + </div> + </div> + </div> + </div> + + <div class="field is-horizontal"> + <div class="field-label"> + <label class="label">limit_req</label> + </div> + <div class="field-body"> + <div class="field"> + <div :class="`control${limitReqChanged ? ' is-changed' : ''}`"> + <div class="checkbox"> + <PrettyCheck v-model="limitReq" class="p-default p-curve p-fill p-icon"> + <i slot="extra" class="icon fas fa-check"></i> + enable + </PrettyCheck> + </div> + </div> + </div> + </div> + </div> + </div> +</template> + +<script> + import PrettyCheck from 'pretty-checkbox-vue/check'; + import VueSelect from 'vue-select'; + import i18n from '../../i18n'; + import delegatedFromDefaults from '../../util/delegated_from_defaults'; + import computedFromDefaults from '../../util/computed_from_defaults'; + + const defaults = { + referrerPolicy: { + default: 'no-referrer-when-downgrade', + options: [ + 'no-referrer', + 'no-referrer-when-downgrade', + 'origin', + 'origin-when-cross-origin', + 'same-origin', + 'strict-origin', + 'strict-origin-when-cross-origin', + 'unsafe-url', + ], + enabled: true, + }, + contentSecurityPolicy: { + default: 'default-src \'self\' http: https: data: blob: \'unsafe-inline\'', + enabled: true, + }, + serverTokens: { + default: false, + enabled: true, + }, + limitReq: { + default: false, + enabled: true, + }, + }; + + export default { + name: 'GlobalSecurity', // Component name + display: 'Security', // Display name for tab + key: 'security', // Key for data in parent + delegated: delegatedFromDefaults(defaults), // Data the parent will present here + components: { + PrettyCheck, + VueSelect, + }, + props: { + data: Object, // Data delegated back to us from parent + }, + data () { + return { + i18n, + }; + }, + computed: computedFromDefaults(defaults, 'security'), // Getters & setters for the delegated data + watch: { + // Check referrer policy selection is valid + '$props.data.referrerPolicy': { + handler(data) { + // This might cause recursion, but seems not to + if (data.enabled) + if (!data.options.includes(data.computed)) + data.computed = data.default; + }, + deep: true, + }, + }, + }; +</script>