admin-general.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  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-select.mt-3(
  77. outlined
  78. :label='$t(`admin:general.contentLicense`)'
  79. :items='contentLicenses'
  80. v-model='config.contentLicense'
  81. prepend-icon='mdi-creative-commons'
  82. :return-object='false'
  83. :hint='$t(`admin:general.contentLicenseHint`)'
  84. persistent-hint
  85. )
  86. v-divider
  87. .overline.grey--text.pa-4 SEO
  88. .px-3.pb-3
  89. v-text-field(
  90. outlined
  91. :label='$t(`admin:general.siteDescription`)'
  92. :counter='255'
  93. v-model='config.description'
  94. prepend-icon='mdi-compass'
  95. :hint='$t(`admin:general.siteDescriptionHint`)'
  96. persistent-hint
  97. )
  98. v-select.mt-3(
  99. outlined
  100. :label='$t(`admin:general.metaRobots`)'
  101. multiple
  102. :items='metaRobots'
  103. v-model='config.robots'
  104. prepend-icon='mdi-compass'
  105. :return-object='false'
  106. :hint='$t(`admin:general.metaRobotsHint`)'
  107. persistent-hint
  108. )
  109. v-flex(lg6 xs12)
  110. v-card.animated.fadeInUp.wait-p4s
  111. v-toolbar(color='indigo', dark, dense, flat)
  112. v-toolbar-title.subtitle-1 Features
  113. v-spacer
  114. v-chip(label, color='white', small).indigo--text coming soon
  115. v-card-text
  116. v-switch(
  117. inset
  118. label='Asset Image Optimization'
  119. color='indigo'
  120. v-model='config.featureTinyPNG'
  121. persistent-hint
  122. hint='Image optimization tool to reduce filesize and bandwidth costs.'
  123. disabled
  124. )
  125. v-text-field.mt-3(
  126. outlined
  127. label='TinyPNG API Key'
  128. :counter='255'
  129. v-model='config.description'
  130. prepend-icon='mdi-subdirectory-arrow-right'
  131. hint='Get your API key at https://tinypng.com/developers'
  132. persistent-hint
  133. disabled
  134. )
  135. v-divider.mt-3
  136. v-switch(
  137. inset
  138. label='Page Ratings'
  139. color='indigo'
  140. v-model='config.featurePageRatings'
  141. persistent-hint
  142. hint='Allow users to rate pages.'
  143. disabled
  144. )
  145. v-divider.mt-3
  146. v-switch(
  147. inset
  148. label='Page Comments'
  149. color='indigo'
  150. v-model='config.featurePageComments'
  151. persistent-hint
  152. hint='Allow users to leave comments on pages.'
  153. disabled
  154. )
  155. v-divider.mt-3
  156. v-switch(
  157. inset
  158. label='Personal Wikis'
  159. color='indigo'
  160. v-model='config.featurePersonalWikis'
  161. persistent-hint
  162. hint='Allow users to have their own personal wiki.'
  163. disabled
  164. )
  165. v-card.mt-5.animated.fadeInUp.wait-p5s
  166. v-toolbar(color='red darken-2', dark, dense, flat)
  167. v-toolbar-title.subtitle-1 Security
  168. v-card-text
  169. 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.
  170. v-switch.mt-3(
  171. inset
  172. label='Block IFrame Embedding'
  173. color='red darken-2'
  174. v-model='config.securityIframe'
  175. persistent-hint
  176. hint='Prevents other websites from embedding your wiki in an iframe. This provides clickjacking protection.'
  177. )
  178. v-divider.mt-3
  179. v-switch(
  180. inset
  181. label='Same Origin Referrer Policy'
  182. color='red darken-2'
  183. v-model='config.securityReferrerPolicy'
  184. persistent-hint
  185. hint='Limits the referrer header to same origin.'
  186. )
  187. v-divider.mt-3
  188. v-switch(
  189. inset
  190. label='Trust X-Forwarded-* Proxy Headers'
  191. color='red darken-2'
  192. v-model='config.securityTrustProxy'
  193. persistent-hint
  194. hint='Should be enabled when using a reverse-proxy like nginx, apache, CloudFlare, etc in front of Wiki.js. Turn off otherwise.'
  195. )
  196. v-divider.mt-3
  197. v-switch(
  198. inset
  199. label='Subresource Integrity (SRI)'
  200. color='red darken-2'
  201. v-model='config.securitySRI'
  202. persistent-hint
  203. hint='This ensure that resources such as CSS and JS files are not altered during delivery.'
  204. disabled
  205. )
  206. v-divider.mt-3
  207. v-switch(
  208. inset
  209. label='Enforce HSTS'
  210. color='red darken-2'
  211. v-model='config.securityHSTS'
  212. persistent-hint
  213. hint='This ensures the connection cannot be established through an insecure HTTP connection.'
  214. )
  215. v-select.mt-5(
  216. outlined
  217. label='HSTS Max Age'
  218. :items='hstsDurations'
  219. v-model='config.securityHSTSDuration'
  220. prepend-icon='mdi-subdirectory-arrow-right'
  221. :disabled='!config.securityHSTS'
  222. hide-details
  223. style='max-width: 450px;'
  224. )
  225. .pl-11.mt-3
  226. .caption Defines the duration for which the server should only deliver content through HTTPS.
  227. .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.
  228. v-divider.mt-3
  229. v-switch(
  230. inset
  231. label='Enforce CSP'
  232. color='red darken-2'
  233. v-model='config.securityCSP'
  234. persistent-hint
  235. hint='Restricts scripts to pre-approved content sources.'
  236. disabled
  237. )
  238. v-textarea.mt-5(
  239. label='CSP Directives'
  240. outlined
  241. v-model='config.securityCSPDirectives'
  242. prepend-icon='mdi-subdirectory-arrow-right'
  243. persistent-hint
  244. hint='One directive per line.'
  245. disabled
  246. )
  247. component(:is='activeModal')
  248. </template>
  249. <script>
  250. import _ from 'lodash'
  251. import { sync } from 'vuex-pathify'
  252. import gql from 'graphql-tag'
  253. import editorStore from '../../store/editor'
  254. /* global WIKI */
  255. WIKI.$store.registerModule('editor', editorStore)
  256. export default {
  257. i18nOptions: { namespaces: 'editor' },
  258. components: {
  259. editorModalMedia: () => import(/* webpackChunkName: "editor", webpackMode: "lazy" */ '../editor/editor-modal-media.vue')
  260. },
  261. data() {
  262. return {
  263. analyticsServices: [
  264. { text: 'None', value: '' },
  265. { text: 'Elasticsearch APM RUM', value: 'elk' },
  266. { text: 'Google Analytics', value: 'ga' },
  267. { text: 'Google Tag Manager', value: 'gtm' }
  268. ],
  269. config: {
  270. host: '',
  271. title: '',
  272. description: '',
  273. robots: [],
  274. analyticsService: '',
  275. analyticsId: '',
  276. company: '',
  277. contentLicense: '',
  278. logoUrl: '',
  279. featureAnalytics: false,
  280. featurePageRatings: false,
  281. featurePageComments: false,
  282. featurePersonalWikis: false,
  283. featureTinyPNG: false,
  284. securityIframe: true,
  285. securityReferrerPolicy: true,
  286. securityTrustProxy: true,
  287. securitySRI: true,
  288. securityHSTS: false,
  289. securityHSTSDuration: 0,
  290. securityCSP: false,
  291. securityCSPDirectives: ''
  292. },
  293. hstsDurations: [
  294. { value: 300, text: '5 minutes' },
  295. { value: 86400, text: '1 day' },
  296. { value: 604800, text: '1 week' },
  297. { value: 2592000, text: '1 month' },
  298. { value: 31536000, text: '1 year' },
  299. { value: 63072000, text: '2 years' }
  300. ],
  301. metaRobots: [
  302. { text: 'Index', value: 'index' },
  303. { text: 'Follow', value: 'follow' },
  304. { text: 'No Index', value: 'noindex' },
  305. { text: 'No Follow', value: 'nofollow' }
  306. ]
  307. }
  308. },
  309. computed: {
  310. siteTitle: sync('site/title'),
  311. logoUrl: sync('site/logoUrl'),
  312. company: sync('site/company'),
  313. contentLicense: sync('site/contentLicense'),
  314. activeModal: sync('editor/activeModal'),
  315. contentLicenses () {
  316. return [
  317. { value: '', text: this.$t('common:license.none') },
  318. { value: 'alr', text: this.$t('common:license.alr') },
  319. { value: 'cc0', text: this.$t('common:license.cc0') },
  320. { value: 'ccby', text: this.$t('common:license.ccby') },
  321. { value: 'ccbysa', text: this.$t('common:license.ccbysa') },
  322. { value: 'ccbynd', text: this.$t('common:license.ccbynd') },
  323. { value: 'ccbync', text: this.$t('common:license.ccbync') },
  324. { value: 'ccbyncsa', text: this.$t('common:license.ccbyncsa') },
  325. { value: 'ccbyncnd', text: this.$t('common:license.ccbyncnd') }
  326. ]
  327. }
  328. },
  329. methods: {
  330. async save () {
  331. try {
  332. await this.$apollo.mutate({
  333. mutation: gql`
  334. mutation (
  335. $host: String!
  336. $title: String!
  337. $description: String!
  338. $robots: [String]!
  339. $analyticsService: String!
  340. $analyticsId: String!
  341. $company: String!
  342. $contentLicense: String!
  343. $logoUrl: String!
  344. $featurePageRatings: Boolean!
  345. $featurePageComments: Boolean!
  346. $featurePersonalWikis: Boolean!
  347. $securityIframe: Boolean!
  348. $securityReferrerPolicy: Boolean!
  349. $securityTrustProxy: Boolean!
  350. $securitySRI: Boolean!
  351. $securityHSTS: Boolean!
  352. $securityHSTSDuration: Int!
  353. $securityCSP: Boolean!
  354. $securityCSPDirectives: String!
  355. ) {
  356. site {
  357. updateConfig(
  358. host: $host,
  359. title: $title,
  360. description: $description,
  361. robots: $robots,
  362. analyticsService: $analyticsService,
  363. analyticsId: $analyticsId,
  364. company: $company,
  365. contentLicense: $contentLicense,
  366. logoUrl: $logoUrl,
  367. featurePageRatings: $featurePageRatings,
  368. featurePageComments: $featurePageComments,
  369. featurePersonalWikis: $featurePersonalWikis,
  370. securityIframe: $securityIframe,
  371. securityReferrerPolicy: $securityReferrerPolicy,
  372. securityTrustProxy: $securityTrustProxy,
  373. securitySRI: $securitySRI,
  374. securityHSTS: $securityHSTS,
  375. securityHSTSDuration: $securityHSTSDuration,
  376. securityCSP: $securityCSP,
  377. securityCSPDirectives: $securityCSPDirectives
  378. ) {
  379. responseResult {
  380. succeeded
  381. errorCode
  382. slug
  383. message
  384. }
  385. }
  386. }
  387. }
  388. `,
  389. variables: {
  390. host: _.get(this.config, 'host', ''),
  391. title: _.get(this.config, 'title', ''),
  392. description: _.get(this.config, 'description', ''),
  393. robots: _.get(this.config, 'robots', []),
  394. analyticsService: _.get(this.config, 'analyticsService', ''),
  395. analyticsId: _.get(this.config, 'analyticsId', ''),
  396. company: _.get(this.config, 'company', ''),
  397. contentLicense: _.get(this.config, 'contentLicense', ''),
  398. logoUrl: _.get(this.config, 'logoUrl', ''),
  399. featurePageRatings: _.get(this.config, 'featurePageRatings', false),
  400. featurePageComments: _.get(this.config, 'featurePageComments', false),
  401. featurePersonalWikis: _.get(this.config, 'featurePersonalWikis', false),
  402. securityIframe: _.get(this.config, 'securityIframe', false),
  403. securityReferrerPolicy: _.get(this.config, 'securityReferrerPolicy', false),
  404. securityTrustProxy: _.get(this.config, 'securityTrustProxy', false),
  405. securitySRI: _.get(this.config, 'securitySRI', false),
  406. securityHSTS: _.get(this.config, 'securityHSTS', false),
  407. securityHSTSDuration: _.get(this.config, 'securityHSTSDuration', 0),
  408. securityCSP: _.get(this.config, 'securityCSP', false),
  409. securityCSPDirectives: _.get(this.config, 'securityCSPDirectives', '')
  410. },
  411. watchLoading (isLoading) {
  412. this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-update')
  413. }
  414. })
  415. this.$store.commit('showNotification', {
  416. style: 'success',
  417. message: 'Configuration saved successfully.',
  418. icon: 'check'
  419. })
  420. this.siteTitle = this.config.title
  421. this.company = this.config.company
  422. this.contentLicense = this.config.contentLicense
  423. this.logoUrl = this.config.logoUrl
  424. } catch (err) {
  425. this.$store.commit('pushGraphError', err)
  426. }
  427. },
  428. browseLogo () {
  429. this.$store.set('editor/editorKey', 'common')
  430. this.activeModal = 'editorModalMedia'
  431. },
  432. refreshLogo () {
  433. this.$forceUpdate()
  434. }
  435. },
  436. mounted () {
  437. this.$root.$on('editorInsert', opts => {
  438. this.config.logoUrl = opts.path
  439. })
  440. },
  441. beforeDestroy() {
  442. this.$root.$off('editorInsert')
  443. },
  444. apollo: {
  445. config: {
  446. query: gql`
  447. {
  448. site {
  449. config {
  450. host
  451. title
  452. description
  453. robots
  454. analyticsService
  455. analyticsId
  456. company
  457. contentLicense
  458. logoUrl
  459. featurePageRatings
  460. featurePageComments
  461. featurePersonalWikis
  462. securityIframe
  463. securityReferrerPolicy
  464. securityTrustProxy
  465. securitySRI
  466. securityHSTS
  467. securityHSTSDuration
  468. securityCSP
  469. securityCSPDirectives
  470. }
  471. }
  472. }
  473. `,
  474. fetchPolicy: 'network-only',
  475. update: (data) => _.cloneDeep(data.site.config),
  476. watchLoading (isLoading) {
  477. this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-refresh')
  478. }
  479. }
  480. }
  481. }
  482. </script>
  483. <style lang='scss'>
  484. </style>