admin-general.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. <template lang='pug'>
  2. v-container(fluid, grid-list-lg)
  3. v-layout(row wrap)
  4. v-flex(xs12)
  5. .admin-header
  6. img.animated.fadeInUp(src='/svg/icon-categorize.svg', alt='General', style='width: 80px;')
  7. .admin-header-title
  8. .headline.primary--text.animated.fadeInLeft {{ $t('admin:general.title') }}
  9. .subtitle-1.grey--text.animated.fadeInLeft {{ $t('admin:general.subtitle') }}
  10. v-spacer
  11. v-btn.animated.fadeInDown(color='success', depressed, @click='save', large)
  12. v-icon(left) mdi-check
  13. span {{$t('common:actions.apply')}}
  14. v-form.pt-3
  15. v-layout(row wrap)
  16. v-flex(lg6 xs12)
  17. v-form
  18. v-card.animated.fadeInUp
  19. v-toolbar(color='primary', dark, dense, flat)
  20. v-toolbar-title.subtitle-1 {{ $t('admin:general.siteInfo') }}
  21. .overline.grey--text.pa-4 {{$t('admin:general.general')}}
  22. .px-3.pb-3
  23. v-text-field(
  24. outlined
  25. :label='$t(`admin:general.siteUrl`)'
  26. required
  27. :counter='255'
  28. v-model='config.host'
  29. prepend-icon='mdi-label-variant-outline'
  30. :hint='$t(`admin:general.siteUrlHint`)'
  31. persistent-hint
  32. )
  33. v-text-field.mt-3(
  34. outlined
  35. :label='$t(`admin:general.siteTitle`)'
  36. required
  37. :counter='50'
  38. v-model='config.title'
  39. prepend-icon='mdi-earth'
  40. :hint='$t(`admin:general.siteTitleHint`)'
  41. persistent-hint
  42. )
  43. v-divider
  44. .overline.grey--text.pa-4 {{$t('admin:general.logo')}} #[v-chip.ml-2(label, color='grey', small, outlined) coming soon]
  45. v-card-text.pb-4.pl-5
  46. v-layout.px-3(row, align-center)
  47. v-avatar(size='100', :color='$vuetify.theme.dark ? `grey darken-2` : `grey lighten-3`', :tile='config.logoIsSquare')
  48. .ml-4
  49. v-btn.mr-3(color='teal', depressed, disabled)
  50. v-icon(left) mdi-cloud-upload
  51. span {{$t('admin:general.uploadLogo')}}
  52. v-btn(color='teal', depressed, disabled)
  53. v-icon(left) mdi-close
  54. span {{$t('admin:general.uploadClear')}}
  55. .caption.mt-3.grey--text {{$t('admin:general.uploadSizeHint', { size: '120x120' })}}
  56. .caption.grey--text {{$t('admin:general.uploadTypesHint', { typeList: 'SVG, PNG', lastType: 'JPG' })}}.
  57. v-divider
  58. .overline.grey--text.pa-4 {{$t('admin:general.footerCopyright')}}
  59. .px-3.pb-3
  60. v-text-field(
  61. outlined
  62. :label='$t(`admin:general.companyName`)'
  63. v-model='config.company'
  64. :counter='255'
  65. prepend-icon='mdi-domain'
  66. persistent-hint
  67. :hint='$t(`admin:general.companyNameHint`)'
  68. )
  69. v-divider
  70. .overline.grey--text.pa-4 SEO
  71. .px-3.pb-3
  72. v-text-field(
  73. outlined
  74. :label='$t(`admin:general.siteDescription`)'
  75. :counter='255'
  76. v-model='config.description'
  77. prepend-icon='mdi-compass'
  78. :hint='$t(`admin:general.siteDescriptionHint`)'
  79. persistent-hint
  80. )
  81. v-select.mt-3(
  82. outlined
  83. :label='$t(`admin:general.metaRobots`)'
  84. multiple
  85. :items='metaRobots'
  86. v-model='config.robots'
  87. prepend-icon='mdi-compass'
  88. :return-object='false'
  89. :hint='$t(`admin:general.metaRobotsHint`)'
  90. persistent-hint
  91. )
  92. v-flex(lg6 xs12)
  93. v-card.animated.fadeInUp.wait-p4s
  94. v-toolbar(color='indigo', dark, dense, flat)
  95. v-toolbar-title.subtitle-1 Features
  96. v-spacer
  97. v-chip(label, color='white', small).indigo--text coming soon
  98. v-card-text
  99. v-switch(
  100. inset
  101. label='Asset Image Optimization'
  102. color='indigo'
  103. v-model='config.featureTinyPNG'
  104. persistent-hint
  105. hint='Image optimization tool to reduce filesize and bandwidth costs.'
  106. disabled
  107. )
  108. v-text-field.mt-3(
  109. outlined
  110. label='TinyPNG API Key'
  111. :counter='255'
  112. v-model='config.description'
  113. prepend-icon='mdi-subdirectory-arrow-right'
  114. hint='Get your API key at https://tinypng.com/developers'
  115. persistent-hint
  116. disabled
  117. )
  118. v-divider.mt-3
  119. v-switch(
  120. inset
  121. label='Page Ratings'
  122. color='indigo'
  123. v-model='config.featurePageRatings'
  124. persistent-hint
  125. hint='Allow users to rate pages.'
  126. disabled
  127. )
  128. v-divider.mt-3
  129. v-switch(
  130. inset
  131. label='Page Comments'
  132. color='indigo'
  133. v-model='config.featurePageComments'
  134. persistent-hint
  135. hint='Allow users to leave comments on pages.'
  136. disabled
  137. )
  138. v-divider.mt-3
  139. v-switch(
  140. inset
  141. label='Personal Wikis'
  142. color='indigo'
  143. v-model='config.featurePersonalWikis'
  144. persistent-hint
  145. hint='Allow users to have their own personal wiki.'
  146. disabled
  147. )
  148. v-card.mt-5.animated.fadeInUp.wait-p5s
  149. v-toolbar(color='red darken-2', dark, dense, flat)
  150. v-toolbar-title.subtitle-1 Security
  151. v-card-text
  152. v-alert(outlined, color='red darken-2', icon='mdi-information-outline').body-2 Make sure to understand the implications before turning on / off a security feature.
  153. v-switch.mt-3(
  154. inset
  155. label='Block IFrame Embedding'
  156. color='red darken-2'
  157. v-model='config.securityIframe'
  158. persistent-hint
  159. hint='Prevents other websites from embedding your wiki in an iframe. This provides clickjacking protection.'
  160. )
  161. v-divider.mt-3
  162. v-switch(
  163. inset
  164. label='Same Origin Referrer Policy'
  165. color='red darken-2'
  166. v-model='config.securityReferrerPolicy'
  167. persistent-hint
  168. hint='Limits the referrer header to same origin.'
  169. )
  170. v-divider.mt-3
  171. v-switch(
  172. inset
  173. label='Trust X-Forwarded-* Proxy Headers'
  174. color='red darken-2'
  175. v-model='config.securityTrustProxy'
  176. persistent-hint
  177. hint='Should be enabled when using a reverse-proxy like nginx, apache, CloudFlare, etc in front of Wiki.js. Turn off otherwise.'
  178. )
  179. v-divider.mt-3
  180. v-switch(
  181. inset
  182. label='Subresource Integrity (SRI)'
  183. color='red darken-2'
  184. v-model='config.securitySRI'
  185. persistent-hint
  186. hint='This ensure that resources such as CSS and JS files are not altered during delivery.'
  187. )
  188. v-divider.mt-3
  189. v-switch(
  190. inset
  191. label='Enforce HSTS'
  192. color='red darken-2'
  193. v-model='config.securityHSTS'
  194. persistent-hint
  195. hint='This ensures the connection cannot be established through an insecure HTTP connection.'
  196. )
  197. v-select.mt-5(
  198. outlined
  199. label='HSTS Max Age'
  200. :items='hstsDurations'
  201. v-model='config.securityHSTSDuration'
  202. prepend-icon='mdi-subdirectory-arrow-right'
  203. :disabled='!config.securityHSTS'
  204. hide-details
  205. style='max-width: 450px;'
  206. )
  207. .pl-11.mt-3
  208. .caption Defines the duration for which the server should only deliver content through HTTPS.
  209. .caption It's a good idea to start with small values and make sure that nothing breaks on your wiki before moving to longer values.
  210. v-divider.mt-3
  211. v-switch(
  212. inset
  213. label='Enforce CSP'
  214. color='red darken-2'
  215. v-model='config.securityCSP'
  216. persistent-hint
  217. hint='Restricts scripts to pre-approved content sources.'
  218. disabled
  219. )
  220. v-textarea.mt-5(
  221. label='CSP Directives'
  222. outlined
  223. v-model='config.securityCSPDirectives'
  224. prepend-icon='mdi-subdirectory-arrow-right'
  225. persistent-hint
  226. hint='One directive per line.'
  227. disabled
  228. )
  229. </template>
  230. <script>
  231. import _ from 'lodash'
  232. import { get, sync } from 'vuex-pathify'
  233. import siteConfigQuery from 'gql/admin/site/site-query-config.gql'
  234. import siteUpdateConfigMutation from 'gql/admin/site/site-mutation-save-config.gql'
  235. export default {
  236. data() {
  237. return {
  238. analyticsServices: [
  239. { text: 'None', value: '' },
  240. { text: 'Elasticsearch APM RUM', value: 'elk' },
  241. { text: 'Google Analytics', value: 'ga' },
  242. { text: 'Google Tag Manager', value: 'gtm' }
  243. ],
  244. config: {
  245. host: '',
  246. title: '',
  247. description: '',
  248. robots: [],
  249. analyticsService: '',
  250. analyticsId: '',
  251. company: '',
  252. hasLogo: false,
  253. logoIsSquare: false,
  254. featureAnalytics: false,
  255. featurePageRatings: false,
  256. featurePageComments: false,
  257. featurePersonalWikis: false,
  258. featureTinyPNG: false,
  259. securityIframe: true,
  260. securityReferrerPolicy: true,
  261. securityTrustProxy: true,
  262. securitySRI: true,
  263. securityHSTS: false,
  264. securityHSTSDuration: 0,
  265. securityCSP: false,
  266. securityCSPDirectives: ''
  267. },
  268. hstsDurations: [
  269. { value: 300, text: '5 minutes' },
  270. { value: 86400, text: '1 day' },
  271. { value: 604800, text: '1 week' },
  272. { value: 2592000, text: '1 month' },
  273. { value: 31536000, text: '1 year' },
  274. { value: 63072000, text: '2 years' }
  275. ],
  276. metaRobots: [
  277. { text: 'Index', value: 'index' },
  278. { text: 'Follow', value: 'follow' },
  279. { text: 'No Index', value: 'noindex' },
  280. { text: 'No Follow', value: 'nofollow' }
  281. ]
  282. }
  283. },
  284. computed: {
  285. darkMode: get('site/dark'),
  286. siteTitle: sync('site/title'),
  287. company: sync('site/company')
  288. },
  289. methods: {
  290. async save () {
  291. try {
  292. await this.$apollo.mutate({
  293. mutation: siteUpdateConfigMutation,
  294. variables: {
  295. host: _.get(this.config, 'host', ''),
  296. title: _.get(this.config, 'title', ''),
  297. description: _.get(this.config, 'description', ''),
  298. robots: _.get(this.config, 'robots', []),
  299. analyticsService: _.get(this.config, 'analyticsService', ''),
  300. analyticsId: _.get(this.config, 'analyticsId', ''),
  301. company: _.get(this.config, 'company', ''),
  302. hasLogo: _.get(this.config, 'hasLogo', false),
  303. logoIsSquare: _.get(this.config, 'logoIsSquare', false),
  304. featurePageRatings: _.get(this.config, 'featurePageRatings', false),
  305. featurePageComments: _.get(this.config, 'featurePageComments', false),
  306. featurePersonalWikis: _.get(this.config, 'featurePersonalWikis', false),
  307. securityIframe: _.get(this.config, 'securityIframe', false),
  308. securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false),
  309. securityTrustProxy: _.get(this.config, 'securityTrustProxy', false),
  310. securitySRI: _.get(this.config, 'securitySRI', false),
  311. securityHSTS: _.get(this.config, 'securityHSTS', false),
  312. securityHSTSDuration: _.get(this.config, 'securityHSTSDuration', 0),
  313. securityCSP: _.get(this.config, 'securityCSP', false),
  314. securityCSPDirectives: _.get(this.config, 'securityCSPDirectives', '')
  315. },
  316. watchLoading (isLoading) {
  317. this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-update')
  318. }
  319. })
  320. this.$store.commit('showNotification', {
  321. style: 'success',
  322. message: 'Configuration saved successfully.',
  323. icon: 'check'
  324. })
  325. this.siteTitle = this.config.title
  326. this.company = this.config.company
  327. } catch (err) {
  328. this.$store.commit('pushGraphError', err)
  329. }
  330. }
  331. },
  332. apollo: {
  333. config: {
  334. query: siteConfigQuery,
  335. fetchPolicy: 'network-only',
  336. update: (data) => _.cloneDeep(data.site.config),
  337. watchLoading (isLoading) {
  338. this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-refresh')
  339. }
  340. }
  341. }
  342. }
  343. </script>
  344. <style lang='scss'>
  345. </style>