admin-general.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  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')}}
  45. .pt-2.pb-7.pl-10.pr-3
  46. .d-flex.align-center
  47. v-avatar(size='100', tile)
  48. v-img(
  49. :src='config.logoUrl'
  50. lazy-src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNcWQ8AAdcBKrJda2oAAAAASUVORK5CYII='
  51. aspect-ratio='1'
  52. )
  53. .ml-4(style='flex: 1 1 auto;')
  54. v-text-field(
  55. outlined
  56. :label='$t(`admin:general.logoUrl`)'
  57. v-model='config.logoUrl'
  58. :hint='$t(`admin:general.logoUrlHint`)'
  59. persistent-hint
  60. append-icon='mdi-folder-image'
  61. @click:append='browseLogo'
  62. @keyup.enter='refreshLogo'
  63. )
  64. v-divider
  65. .overline.grey--text.pa-4 {{$t('admin:general.footerCopyright')}}
  66. .px-3.pb-3
  67. v-text-field(
  68. outlined
  69. :label='$t(`admin:general.companyName`)'
  70. v-model='config.company'
  71. :counter='255'
  72. prepend-icon='mdi-domain'
  73. persistent-hint
  74. :hint='$t(`admin:general.companyNameHint`)'
  75. )
  76. v-divider
  77. .overline.grey--text.pa-4 SEO
  78. .px-3.pb-3
  79. v-text-field(
  80. outlined
  81. :label='$t(`admin:general.siteDescription`)'
  82. :counter='255'
  83. v-model='config.description'
  84. prepend-icon='mdi-compass'
  85. :hint='$t(`admin:general.siteDescriptionHint`)'
  86. persistent-hint
  87. )
  88. v-select.mt-3(
  89. outlined
  90. :label='$t(`admin:general.metaRobots`)'
  91. multiple
  92. :items='metaRobots'
  93. v-model='config.robots'
  94. prepend-icon='mdi-compass'
  95. :return-object='false'
  96. :hint='$t(`admin:general.metaRobotsHint`)'
  97. persistent-hint
  98. )
  99. v-flex(lg6 xs12)
  100. v-card.animated.fadeInUp.wait-p4s
  101. v-toolbar(color='indigo', dark, dense, flat)
  102. v-toolbar-title.subtitle-1 Features
  103. v-spacer
  104. v-chip(label, color='white', small).indigo--text coming soon
  105. v-card-text
  106. v-switch(
  107. inset
  108. label='Asset Image Optimization'
  109. color='indigo'
  110. v-model='config.featureTinyPNG'
  111. persistent-hint
  112. hint='Image optimization tool to reduce filesize and bandwidth costs.'
  113. disabled
  114. )
  115. v-text-field.mt-3(
  116. outlined
  117. label='TinyPNG API Key'
  118. :counter='255'
  119. v-model='config.description'
  120. prepend-icon='mdi-subdirectory-arrow-right'
  121. hint='Get your API key at https://tinypng.com/developers'
  122. persistent-hint
  123. disabled
  124. )
  125. v-divider.mt-3
  126. v-switch(
  127. inset
  128. label='Page Ratings'
  129. color='indigo'
  130. v-model='config.featurePageRatings'
  131. persistent-hint
  132. hint='Allow users to rate pages.'
  133. disabled
  134. )
  135. v-divider.mt-3
  136. v-switch(
  137. inset
  138. label='Page Comments'
  139. color='indigo'
  140. v-model='config.featurePageComments'
  141. persistent-hint
  142. hint='Allow users to leave comments on pages.'
  143. disabled
  144. )
  145. v-divider.mt-3
  146. v-switch(
  147. inset
  148. label='Personal Wikis'
  149. color='indigo'
  150. v-model='config.featurePersonalWikis'
  151. persistent-hint
  152. hint='Allow users to have their own personal wiki.'
  153. disabled
  154. )
  155. v-card.mt-5.animated.fadeInUp.wait-p5s
  156. v-toolbar(color='red darken-2', dark, dense, flat)
  157. v-toolbar-title.subtitle-1 Security
  158. v-card-text
  159. 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.
  160. v-switch.mt-3(
  161. inset
  162. label='Block IFrame Embedding'
  163. color='red darken-2'
  164. v-model='config.securityIframe'
  165. persistent-hint
  166. hint='Prevents other websites from embedding your wiki in an iframe. This provides clickjacking protection.'
  167. )
  168. v-divider.mt-3
  169. v-switch(
  170. inset
  171. label='Same Origin Referrer Policy'
  172. color='red darken-2'
  173. v-model='config.securityReferrerPolicy'
  174. persistent-hint
  175. hint='Limits the referrer header to same origin.'
  176. )
  177. v-divider.mt-3
  178. v-switch(
  179. inset
  180. label='Trust X-Forwarded-* Proxy Headers'
  181. color='red darken-2'
  182. v-model='config.securityTrustProxy'
  183. persistent-hint
  184. hint='Should be enabled when using a reverse-proxy like nginx, apache, CloudFlare, etc in front of Wiki.js. Turn off otherwise.'
  185. )
  186. v-divider.mt-3
  187. v-switch(
  188. inset
  189. label='Subresource Integrity (SRI)'
  190. color='red darken-2'
  191. v-model='config.securitySRI'
  192. persistent-hint
  193. hint='This ensure that resources such as CSS and JS files are not altered during delivery.'
  194. )
  195. v-divider.mt-3
  196. v-switch(
  197. inset
  198. label='Enforce HSTS'
  199. color='red darken-2'
  200. v-model='config.securityHSTS'
  201. persistent-hint
  202. hint='This ensures the connection cannot be established through an insecure HTTP connection.'
  203. )
  204. v-select.mt-5(
  205. outlined
  206. label='HSTS Max Age'
  207. :items='hstsDurations'
  208. v-model='config.securityHSTSDuration'
  209. prepend-icon='mdi-subdirectory-arrow-right'
  210. :disabled='!config.securityHSTS'
  211. hide-details
  212. style='max-width: 450px;'
  213. )
  214. .pl-11.mt-3
  215. .caption Defines the duration for which the server should only deliver content through HTTPS.
  216. .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.
  217. v-divider.mt-3
  218. v-switch(
  219. inset
  220. label='Enforce CSP'
  221. color='red darken-2'
  222. v-model='config.securityCSP'
  223. persistent-hint
  224. hint='Restricts scripts to pre-approved content sources.'
  225. disabled
  226. )
  227. v-textarea.mt-5(
  228. label='CSP Directives'
  229. outlined
  230. v-model='config.securityCSPDirectives'
  231. prepend-icon='mdi-subdirectory-arrow-right'
  232. persistent-hint
  233. hint='One directive per line.'
  234. disabled
  235. )
  236. component(:is='activeModal')
  237. </template>
  238. <script>
  239. import _ from 'lodash'
  240. import { get, sync } from 'vuex-pathify'
  241. import siteConfigQuery from 'gql/admin/site/site-query-config.gql'
  242. import siteUpdateConfigMutation from 'gql/admin/site/site-mutation-save-config.gql'
  243. import editorStore from '../../store/editor'
  244. /* global WIKI */
  245. WIKI.$store.registerModule('editor', editorStore)
  246. export default {
  247. i18nOptions: { namespaces: 'editor' },
  248. components: {
  249. editorModalMedia: () => import(/* webpackChunkName: "editor", webpackMode: "lazy" */ '../editor/editor-modal-media.vue')
  250. },
  251. data() {
  252. return {
  253. analyticsServices: [
  254. { text: 'None', value: '' },
  255. { text: 'Elasticsearch APM RUM', value: 'elk' },
  256. { text: 'Google Analytics', value: 'ga' },
  257. { text: 'Google Tag Manager', value: 'gtm' }
  258. ],
  259. config: {
  260. host: '',
  261. title: '',
  262. description: '',
  263. robots: [],
  264. analyticsService: '',
  265. analyticsId: '',
  266. company: '',
  267. logoUrl: '',
  268. featureAnalytics: false,
  269. featurePageRatings: false,
  270. featurePageComments: false,
  271. featurePersonalWikis: false,
  272. featureTinyPNG: false,
  273. securityIframe: true,
  274. securityReferrerPolicy: true,
  275. securityTrustProxy: true,
  276. securitySRI: true,
  277. securityHSTS: false,
  278. securityHSTSDuration: 0,
  279. securityCSP: false,
  280. securityCSPDirectives: ''
  281. },
  282. hstsDurations: [
  283. { value: 300, text: '5 minutes' },
  284. { value: 86400, text: '1 day' },
  285. { value: 604800, text: '1 week' },
  286. { value: 2592000, text: '1 month' },
  287. { value: 31536000, text: '1 year' },
  288. { value: 63072000, text: '2 years' }
  289. ],
  290. metaRobots: [
  291. { text: 'Index', value: 'index' },
  292. { text: 'Follow', value: 'follow' },
  293. { text: 'No Index', value: 'noindex' },
  294. { text: 'No Follow', value: 'nofollow' }
  295. ]
  296. }
  297. },
  298. computed: {
  299. darkMode: get('site/dark'),
  300. siteTitle: sync('site/title'),
  301. logoUrl: sync('site/logoUrl'),
  302. company: sync('site/company'),
  303. activeModal: sync('editor/activeModal')
  304. },
  305. methods: {
  306. async save () {
  307. try {
  308. await this.$apollo.mutate({
  309. mutation: siteUpdateConfigMutation,
  310. variables: {
  311. host: _.get(this.config, 'host', ''),
  312. title: _.get(this.config, 'title', ''),
  313. description: _.get(this.config, 'description', ''),
  314. robots: _.get(this.config, 'robots', []),
  315. analyticsService: _.get(this.config, 'analyticsService', ''),
  316. analyticsId: _.get(this.config, 'analyticsId', ''),
  317. company: _.get(this.config, 'company', ''),
  318. logoUrl: _.get(this.config, 'logoUrl', ''),
  319. featurePageRatings: _.get(this.config, 'featurePageRatings', false),
  320. featurePageComments: _.get(this.config, 'featurePageComments', false),
  321. featurePersonalWikis: _.get(this.config, 'featurePersonalWikis', false),
  322. securityIframe: _.get(this.config, 'securityIframe', false),
  323. securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false),
  324. securityTrustProxy: _.get(this.config, 'securityTrustProxy', false),
  325. securitySRI: _.get(this.config, 'securitySRI', false),
  326. securityHSTS: _.get(this.config, 'securityHSTS', false),
  327. securityHSTSDuration: _.get(this.config, 'securityHSTSDuration', 0),
  328. securityCSP: _.get(this.config, 'securityCSP', false),
  329. securityCSPDirectives: _.get(this.config, 'securityCSPDirectives', '')
  330. },
  331. watchLoading (isLoading) {
  332. this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-update')
  333. }
  334. })
  335. this.$store.commit('showNotification', {
  336. style: 'success',
  337. message: 'Configuration saved successfully.',
  338. icon: 'check'
  339. })
  340. this.siteTitle = this.config.title
  341. this.company = this.config.company
  342. this.logoUrl = this.config.logoUrl
  343. } catch (err) {
  344. this.$store.commit('pushGraphError', err)
  345. }
  346. },
  347. browseLogo () {
  348. this.$store.set('editor/editorKey', 'common')
  349. this.activeModal = 'editorModalMedia'
  350. },
  351. refreshLogo () {
  352. this.$forceUpdate()
  353. }
  354. },
  355. mounted () {
  356. this.$root.$on('editorInsert', opts => {
  357. this.config.logoUrl = opts.path
  358. })
  359. },
  360. beforeDestroy() {
  361. this.$root.$off('editorInsert')
  362. },
  363. apollo: {
  364. config: {
  365. query: siteConfigQuery,
  366. fetchPolicy: 'network-only',
  367. update: (data) => _.cloneDeep(data.site.config),
  368. watchLoading (isLoading) {
  369. this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-refresh')
  370. }
  371. }
  372. }
  373. }
  374. </script>
  375. <style lang='scss'>
  376. </style>