diff --git a/.nvmrc b/.nvmrc
index cc5875fa..f0da0944 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v10.15.3
+v10.16.0
diff --git a/client/components/admin.vue b/client/components/admin.vue
index 40c9bcfc..b198170b 100644
--- a/client/components/admin.vue
+++ b/client/components/admin.vue
@@ -50,6 +50,9 @@
template(v-if='hasPermission(`manage:system`)')
v-divider.my-2
v-subheader.pl-4 {{ $t('admin:nav.modules') }}
+ v-list-tile(to='/analytics')
+ v-list-tile-avatar: v-icon timeline
+ v-list-tile-title {{ $t('admin:analytics.title') }}
v-list-tile(to='/auth')
v-list-tile-avatar: v-icon lock_outline
v-list-tile-title {{ $t('admin:auth.title') }}
@@ -143,6 +146,7 @@ const router = new VueRouter({
{ path: '/groups/:id', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-groups-edit.vue') },
{ path: '/users', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-users.vue') },
{ path: '/users/:id', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-users-edit.vue') },
+ { path: '/analytics', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-analytics.vue') },
{ path: '/auth', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-auth.vue') },
{ path: '/rendering', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-rendering.vue') },
{ path: '/editor', component: () => import(/* webpackChunkName: "admin" */ './admin/admin-editor.vue') },
diff --git a/client/components/admin/admin-analytics.vue b/client/components/admin/admin-analytics.vue
new file mode 100644
index 00000000..800e5ed3
--- /dev/null
+++ b/client/components/admin/admin-analytics.vue
@@ -0,0 +1,178 @@
+
+ v-container(fluid, grid-list-lg)
+ v-layout(row, wrap)
+ v-flex(xs12)
+ .admin-header
+ img.animated.fadeInUp(src='/svg/icon-line-chart.svg', alt='Analytics', style='width: 80px;')
+ .admin-header-title
+ .headline.primary--text.animated.fadeInLeft {{ $t('admin:analytics.title') }}
+ .subheading.grey--text.animated.fadeInLeft.wait-p4s {{ $t('admin:analytics.subtitle') }}
+ v-spacer
+ v-btn.animated.fadeInDown.wait-p2s(outline, color='grey', @click='refresh', large)
+ v-icon refresh
+ v-btn.animated.fadeInDown(color='success', @click='save', depressed, large)
+ v-icon(left) check
+ span {{$t('common:actions.apply')}}
+
+ v-flex(lg3, xs12)
+ v-card.animated.fadeInUp
+ v-toolbar(flat, color='primary', dark, dense)
+ .subheading {{$t('admin:analytics.providers')}}
+ v-list(two-line, dense).py-0
+ template(v-for='(str, idx) in providers')
+ v-list-tile(:key='str.key', @click='selectedProvider = str.key', :disabled='!str.isAvailable')
+ v-list-tile-avatar
+ v-icon(color='grey', v-if='!str.isAvailable') indeterminate_check_box
+ v-icon(color='primary', v-else-if='str.isEnabled', v-ripple, @click='str.isEnabled = false') check_box
+ v-icon(color='grey', v-else, v-ripple, @click='str.isEnabled = true') check_box_outline_blank
+ v-list-tile-content
+ v-list-tile-title.body-2(:class='!str.isAvailable ? `grey--text` : (selectedProvider === str.key ? `primary--text` : ``)') {{ str.title }}
+ v-list-tile-sub-title.caption(:class='!str.isAvailable ? `grey--text text--lighten-1` : (selectedProvider === str.key ? `blue--text ` : ``)') {{ str.description }}
+ v-list-tile-avatar(v-if='selectedProvider === str.key')
+ v-icon.animated.fadeInLeft(color='primary') arrow_forward_ios
+ v-divider(v-if='idx < providers.length - 1')
+
+ v-flex(xs12, lg9)
+
+ v-card.wiki-form.animated.fadeInUp.wait-p2s
+ v-toolbar(color='primary', dense, flat, dark)
+ .subheading {{provider.title}}
+ v-card-text
+ v-form
+ .analytic-provider-logo
+ img(:src='provider.logo', :alt='provider.title')
+ .caption.pt-3 {{provider.description}}
+ .caption.pb-3: a(:href='provider.website') {{provider.website}}
+ v-divider.mt-3
+ v-subheader.pl-0 {{$t('admin:analytics.providerConfiguration')}}
+ .body-1.ml-3(v-if='!provider.config || provider.config.length < 1'): em {{$t('admin:analytics.providerNoConfiguration')}}
+ template(v-else, v-for='cfg in provider.config')
+ v-select(
+ v-if='cfg.value.type === "string" && cfg.value.enum'
+ outline
+ background-color='grey lighten-2'
+ :items='cfg.value.enum'
+ :key='cfg.key'
+ :label='cfg.value.title'
+ v-model='cfg.value.value'
+ prepend-icon='settings_applications'
+ :hint='cfg.value.hint ? cfg.value.hint : ""'
+ persistent-hint
+ :class='cfg.value.hint ? "mb-2" : ""'
+ )
+ v-switch.mb-3(
+ v-else-if='cfg.value.type === "boolean"'
+ :key='cfg.key'
+ :label='cfg.value.title'
+ v-model='cfg.value.value'
+ color='primary'
+ prepend-icon='settings_applications'
+ :hint='cfg.value.hint ? cfg.value.hint : ""'
+ persistent-hint
+ )
+ v-text-field(
+ v-else
+ outline
+ background-color='grey lighten-2'
+ :key='cfg.key'
+ :label='cfg.value.title'
+ v-model='cfg.value.value'
+ prepend-icon='settings_applications'
+ :hint='cfg.value.hint ? cfg.value.hint : ""'
+ persistent-hint
+ :class='cfg.value.hint ? "mb-2" : ""'
+ )
+
+
+
+
+
+
diff --git a/client/components/admin/admin-general.vue b/client/components/admin/admin-general.vue
index c8a2c689..ab1249ce 100644
--- a/client/components/admin/admin-general.vue
+++ b/client/components/admin/admin-general.vue
@@ -99,36 +99,6 @@
v-spacer
v-chip(label, color='white', small).primary--text coming soon
v-card-text
- v-switch(
- label='Analytics'
- color='primary'
- v-model='config.featureAnalytics'
- persistent-hint
- hint='Enable site analytics using service provider.'
- disabled
- )
- v-select.mt-3(
- outline
- label='Analytics Service Provider'
- :items='analyticsServices'
- v-model='config.analyticsService'
- prepend-icon='subdirectory_arrow_right'
- persistent-hint
- hint='Automatically add tracking code for services like Google Analytics.'
- disabled
- )
- v-text-field.mt-2(
- v-if='config.analyticsService !== ``'
- outline
- label='Property Tracking ID'
- :counter='255'
- v-model='config.analyticsId'
- prepend-icon='timeline'
- persistent-hint
- hint='A unique identifier provided by your analytics service provider.'
- )
-
- v-divider.mt-3
v-switch(
label='Asset Image Optimization'
color='primary'
@@ -191,7 +161,7 @@ export default {
return {
analyticsServices: [
{ text: 'None', value: '' },
- { text: 'Elasticsearch APM', value: 'elk' },
+ { text: 'Elasticsearch APM RUM', value: 'elk' },
{ text: 'Google Analytics', value: 'ga' },
{ text: 'Google Tag Manager', value: 'gtm' }
],
diff --git a/client/components/admin/admin-storage.vue b/client/components/admin/admin-storage.vue
index 7fa2d98e..43ca67e2 100644
--- a/client/components/admin/admin-storage.vue
+++ b/client/components/admin/admin-storage.vue
@@ -166,7 +166,7 @@
i18next.caption.mt-3(path='admin:storage.syncScheduleCurrent', tag='div')
strong(place='schedule') {{getDefaultSchedule(target.syncInterval)}}
i18next.caption(path='admin:storage.syncScheduleDefault', tag='div')
- strong {{getDefaultSchedule(target.syncIntervalDefault)}}
+ strong(place='schedule') {{getDefaultSchedule(target.syncIntervalDefault)}}
template(v-if='target.actions && target.actions.length > 0')
v-divider.mt-3
@@ -216,7 +216,9 @@ export default {
runningAction: false,
runningActionHandler: '',
selectedTarget: '',
- target: {},
+ target: {
+ supportedModes: []
+ },
targets: [],
status: []
}
@@ -265,6 +267,7 @@ export default {
this.$store.commit(`loadingStop`, 'admin-storage-savetargets')
},
getDefaultSchedule(val) {
+ if (!val) { return 'N/A' }
return moment.duration(val).format('y [years], M [months], d [days], h [hours], m [minutes]')
},
async executeAction(targetKey, handler) {
diff --git a/client/graph/admin/analytics/analytics-mutation-save-providers.gql b/client/graph/admin/analytics/analytics-mutation-save-providers.gql
new file mode 100644
index 00000000..09cc9a49
--- /dev/null
+++ b/client/graph/admin/analytics/analytics-mutation-save-providers.gql
@@ -0,0 +1,12 @@
+mutation($providers: [AnalyticsProviderInput]!) {
+ analytics {
+ updateProviders(providers: $providers) {
+ responseResult {
+ succeeded
+ errorCode
+ slug
+ message
+ }
+ }
+ }
+}
diff --git a/client/graph/admin/analytics/analytics-query-providers.gql b/client/graph/admin/analytics/analytics-query-providers.gql
new file mode 100644
index 00000000..4402fc9c
--- /dev/null
+++ b/client/graph/admin/analytics/analytics-query-providers.gql
@@ -0,0 +1,17 @@
+query {
+ analytics {
+ providers {
+ isEnabled
+ key
+ title
+ description
+ isAvailable
+ logo
+ website
+ config {
+ key
+ value
+ }
+ }
+ }
+}
diff --git a/client/static/svg/icon-line-chart.svg b/client/static/svg/icon-line-chart.svg
new file mode 100644
index 00000000..4e887fd0
--- /dev/null
+++ b/client/static/svg/icon-line-chart.svg
@@ -0,0 +1,29 @@
+
+
+
diff --git a/package.json b/package.json
index b3bb3f7c..55620e59 100644
--- a/package.json
+++ b/package.json
@@ -226,7 +226,7 @@
"graphql-voyager": "1.0.0-rc.27",
"hammerjs": "2.0.8",
"html-webpack-plugin": "3.2.0",
- "html-webpack-pug-plugin": "0.3.0",
+ "html-webpack-pug-plugin": "2.0.0",
"i18next-xhr-backend": "3.0.0",
"ignore-loader": "0.1.2",
"js-cookie": "2.2.0",
diff --git a/server/core/kernel.js b/server/core/kernel.js
index 32fac63d..f1f87860 100644
--- a/server/core/kernel.js
+++ b/server/core/kernel.js
@@ -63,6 +63,7 @@ module.exports = {
* Post-Master Boot Sequence
*/
async postBootMaster() {
+ await WIKI.models.analytics.refreshProvidersFromDisk()
await WIKI.models.authentication.refreshStrategiesFromDisk()
await WIKI.models.editors.refreshEditorsFromDisk()
await WIKI.models.loggers.refreshLoggersFromDisk()
diff --git a/server/db/migrations-sqlite/2.0.0-beta.205.js b/server/db/migrations-sqlite/2.0.0-beta.205.js
new file mode 100644
index 00000000..934abfbc
--- /dev/null
+++ b/server/db/migrations-sqlite/2.0.0-beta.205.js
@@ -0,0 +1,13 @@
+exports.up = knex => {
+ return knex.schema
+ .createTable('analytics', table => {
+ table.string('key').notNullable().primary()
+ table.boolean('isEnabled').notNullable().defaultTo(false)
+ table.json('config').notNullable()
+ })
+}
+
+exports.down = knex => {
+ return knex.schema
+ .dropTableIfExists('analytics')
+}
diff --git a/server/db/migrations/2.0.0-beta.205.js b/server/db/migrations/2.0.0-beta.205.js
new file mode 100644
index 00000000..371f58dc
--- /dev/null
+++ b/server/db/migrations/2.0.0-beta.205.js
@@ -0,0 +1,17 @@
+exports.up = knex => {
+ const dbCompat = {
+ charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
+ }
+ return knex.schema
+ .createTable('analytics', table => {
+ if (dbCompat.charset) { table.charset('utf8mb4') }
+ table.string('key').notNullable().primary()
+ table.boolean('isEnabled').notNullable().defaultTo(false)
+ table.json('config').notNullable()
+ })
+}
+
+exports.down = knex => {
+ return knex.schema
+ .dropTableIfExists('analytics')
+}
diff --git a/server/graph/resolvers/analytics.js b/server/graph/resolvers/analytics.js
new file mode 100644
index 00000000..f8dc2821
--- /dev/null
+++ b/server/graph/resolvers/analytics.js
@@ -0,0 +1,56 @@
+const _ = require('lodash')
+const graphHelper = require('../../helpers/graph')
+
+/* global WIKI */
+
+module.exports = {
+ Query: {
+ async analytics() { return {} }
+ },
+ Mutation: {
+ async analytics() { return {} }
+ },
+ AnalyticsQuery: {
+ async providers(obj, args, context, info) {
+ let providers = await WIKI.models.analytics.getProviders(args.isEnabled)
+ providers = providers.map(stg => {
+ const providerInfo = _.find(WIKI.data.analytics, ['key', stg.key]) || {}
+ return {
+ ...providerInfo,
+ ...stg,
+ config: _.sortBy(_.transform(stg.config, (res, value, key) => {
+ const configData = _.get(providerInfo.props, key, {})
+ res.push({
+ key,
+ value: JSON.stringify({
+ ...configData,
+ value
+ })
+ })
+ }, []), 'key')
+ }
+ })
+ return providers
+ }
+ },
+ AnalyticsMutation: {
+ async updateProviders(obj, args, context) {
+ try {
+ for (let str of args.providers) {
+ await WIKI.models.analytics.query().patch({
+ isEnabled: str.isEnabled,
+ config: _.reduce(str.config, (result, value, key) => {
+ _.set(result, `${value.key}`, _.get(JSON.parse(value.value), 'v', null))
+ return result
+ }, {})
+ }).where('key', str.key)
+ }
+ return {
+ responseResult: graphHelper.generateSuccess('Providers updated successfully')
+ }
+ } catch (err) {
+ return graphHelper.generateError(err)
+ }
+ }
+ }
+}
diff --git a/server/graph/schemas/analytics.graphql b/server/graph/schemas/analytics.graphql
new file mode 100644
index 00000000..8783ca2d
--- /dev/null
+++ b/server/graph/schemas/analytics.graphql
@@ -0,0 +1,53 @@
+# ===============================================
+# ANALYTICS
+# ===============================================
+
+extend type Query {
+ analytics: AnalyticsQuery
+}
+
+extend type Mutation {
+ analytics: AnalyticsMutation
+}
+
+# -----------------------------------------------
+# QUERIES
+# -----------------------------------------------
+
+type AnalyticsQuery {
+ providers(
+ isEnabled: Boolean
+ ): [AnalyticsProvider]
+}
+
+# -----------------------------------------------
+# MUTATIONS
+# -----------------------------------------------
+
+type AnalyticsMutation {
+ updateProviders(
+ providers: [AnalyticsProviderInput]!
+ ): DefaultResponse @auth(requires: ["manage:system"])
+}
+
+# -----------------------------------------------
+# TYPES
+# -----------------------------------------------
+
+type AnalyticsProvider {
+ isEnabled: Boolean!
+ key: String!
+ props: [String]
+ title: String!
+ description: String
+ isAvailable: Boolean
+ logo: String
+ website: String
+ icon: String
+ config: [KeyValuePair] @auth(requires: ["manage:system"])
+}
+input AnalyticsProviderInput {
+ isEnabled: Boolean!
+ key: String!
+ config: [KeyValuePairInput]
+}
diff --git a/server/models/analytics.js b/server/models/analytics.js
new file mode 100644
index 00000000..6f1b8962
--- /dev/null
+++ b/server/models/analytics.js
@@ -0,0 +1,96 @@
+const Model = require('objection').Model
+const fs = require('fs-extra')
+const path = require('path')
+const _ = require('lodash')
+const yaml = require('js-yaml')
+const commonHelper = require('../helpers/common')
+
+/* global WIKI */
+
+/**
+ * Analytics model
+ */
+module.exports = class Analytics extends Model {
+ static get tableName() { return 'analytics' }
+ static get idColumn() { return 'key' }
+
+ static get jsonSchema () {
+ return {
+ type: 'object',
+ required: ['key', 'isEnabled'],
+
+ properties: {
+ key: {type: 'string'},
+ isEnabled: {type: 'boolean'}
+ }
+ }
+ }
+
+ static get jsonAttributes() {
+ return ['config']
+ }
+
+ static async getProviders(isEnabled) {
+ const providers = await WIKI.models.analytics.query().where(_.isBoolean(isEnabled) ? { isEnabled } : {})
+ return _.sortBy(providers, ['key'])
+ }
+
+ static async refreshProvidersFromDisk() {
+ let trx
+ try {
+ const dbProviders = await WIKI.models.analytics.query()
+
+ // -> Fetch definitions from disk
+ const analyticsDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/analytics'))
+ let diskProviders = []
+ for (let dir of analyticsDirs) {
+ const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/analytics', dir, 'definition.yml'), 'utf8')
+ diskProviders.push(yaml.safeLoad(def))
+ }
+ WIKI.data.analytics = diskProviders.map(provider => ({
+ ...provider,
+ props: commonHelper.parseModuleProps(provider.props)
+ }))
+
+ let newProviders = []
+ for (let provider of WIKI.data.analytics) {
+ if (!_.some(dbProviders, ['key', provider.key])) {
+ newProviders.push({
+ key: provider.key,
+ isEnabled: false,
+ config: _.transform(provider.props, (result, value, key) => {
+ _.set(result, key, value.default)
+ return result
+ }, {})
+ })
+ } else {
+ const providerConfig = _.get(_.find(dbProviders, ['key', provider.key]), 'config', {})
+ await WIKI.models.analytics.query().patch({
+ config: _.transform(provider.props, (result, value, key) => {
+ if (!_.has(result, key)) {
+ _.set(result, key, value.default)
+ }
+ return result
+ }, providerConfig)
+ }).where('key', provider.key)
+ }
+ }
+ if (newProviders.length > 0) {
+ trx = await WIKI.models.Objection.transaction.start(WIKI.models.knex)
+ for (let provider of newProviders) {
+ await WIKI.models.analytics.query(trx).insert(provider)
+ }
+ await trx.commit()
+ WIKI.logger.info(`Loaded ${newProviders.length} new analytics providers: [ OK ]`)
+ } else {
+ WIKI.logger.info(`No new analytics providers found: [ SKIPPED ]`)
+ }
+ } catch (err) {
+ WIKI.logger.error(`Failed to scan or load new analytics providers: [ FAILED ]`)
+ WIKI.logger.error(err)
+ if (trx) {
+ trx.rollback()
+ }
+ }
+ }
+}
diff --git a/server/modules/analytics/azureinsights/definition.yml b/server/modules/analytics/azureinsights/definition.yml
new file mode 100644
index 00000000..4d7b39ef
--- /dev/null
+++ b/server/modules/analytics/azureinsights/definition.yml
@@ -0,0 +1,23 @@
+key: azureinsights
+title: Azure Application Insights
+description: Application Insights is an extensible Application Performance Management (APM) service for web developers on multiple platforms.
+author: requarks.io
+logo: https://static.requarks.io/logo/azure.svg
+website: https://azure.microsoft.com/en-us/services/monitor/
+isAvailable: true
+props:
+ instrumentationKey:
+ type: String
+ title: Instrumentation Key
+ hint: Found in the Azure Portal in your Application Insights resource panel
+ order: 1
+codeHead: |
+
diff --git a/server/modules/analytics/countly/definition.yml b/server/modules/analytics/countly/definition.yml
new file mode 100644
index 00000000..163ebef7
--- /dev/null
+++ b/server/modules/analytics/countly/definition.yml
@@ -0,0 +1,45 @@
+key: countly
+title: Countly
+description: Countly is the best analytics platform to understand and enhance customer journeys in web, desktop and mobile applications.
+author: requarks.io
+logo: https://static.requarks.io/logo/countly.svg
+website: https://count.ly/
+isAvailable: true
+props:
+ appKey:
+ type: String
+ title: App Key
+ hint: The App Key found under Management > Applications
+ order: 1
+ serverUrl:
+ type: String
+ title: Server URL
+ hint: The Count.ly server to report to. e.g. https://us-example.count.ly
+ order: 2
+codeHead: |
+
diff --git a/server/modules/analytics/elasticapm/definition.yml b/server/modules/analytics/elasticapm/definition.yml
new file mode 100644
index 00000000..c3907812
--- /dev/null
+++ b/server/modules/analytics/elasticapm/definition.yml
@@ -0,0 +1,36 @@
+key: elasticapm
+title: Elasticsearch APM RUM
+description: Real User Monitoring captures user interaction with clients such as web browsers.
+author: requarks.io
+logo: https://static.requarks.io/logo/elasticsearch-apm.svg
+website: https://www.elastic.co/solutions/apm
+isAvailable: true
+props:
+ serverUrl:
+ type: String
+ title: APM Server URL
+ hint: The full URL to your APM server, including the port
+ default: http://apm.example.com:8200
+ order: 1
+ serviceName:
+ type: String
+ title: Service Name
+ hint: The name of the client reported to APM
+ default: wiki-js
+ order: 2
+ environment:
+ type: String
+ title: Environment
+ hint: e.g. production/development/test
+ default: ''
+ order: 3
+codeHead: |
+
+
+
diff --git a/server/modules/analytics/fathom/definition.yml b/server/modules/analytics/fathom/definition.yml
new file mode 100644
index 00000000..d193b9eb
--- /dev/null
+++ b/server/modules/analytics/fathom/definition.yml
@@ -0,0 +1,34 @@
+key: fathom
+title: Fathom
+description: Fathom Analytics provides simple, useful website stats without tracking or storing personal data of your users.
+author: requarks.io
+logo: https://static.requarks.io/logo/fathom.svg
+website: https://usefathom.com/
+isAvailable: true
+props:
+ host:
+ type: String
+ title: Fathom Server Host
+ hint: The hostname / ip adress where Fathom is installed, without the trailing slash. e.g. https://fathom.example.com
+ order: 1
+ siteId:
+ type: String
+ title: Site ID
+ hint: The alphanumeric identifier of your site
+ order: 2
+codeHead: |
+
+
+
diff --git a/server/modules/analytics/fullstory/definition.yml b/server/modules/analytics/fullstory/definition.yml
new file mode 100644
index 00000000..b76b6833
--- /dev/null
+++ b/server/modules/analytics/fullstory/definition.yml
@@ -0,0 +1,31 @@
+key: fullstory
+title: FullStory
+description: FullStory is your digital experience analytics platform for on-the-fly funnels, pixel-perfect replay, custom events, heat maps, advanced search, Dev Tools, and more.
+author: requarks.io
+logo: https://static.requarks.io/logo/fullstory.svg
+website: https://www.fullstory.com
+isAvailable: true
+props:
+ org:
+ type: String
+ title: Organization ID
+ hint: A 5 alphanumeric identifier, e.g. XXXXX
+ order: 1
+codeHead: |
+
diff --git a/server/modules/analytics/google/definition.yml b/server/modules/analytics/google/definition.yml
new file mode 100644
index 00000000..a6907f9d
--- /dev/null
+++ b/server/modules/analytics/google/definition.yml
@@ -0,0 +1,23 @@
+key: google
+title: Google Analytics
+description: Google specializes in Internet-related services and products, which include online advertising technologies, search engine, cloud computing, software, and hardware.
+author: requarks.io
+logo: https://static.requarks.io/logo/google-analytics.svg
+website: https://analytics.google.com/
+isAvailable: true
+props:
+ propertyTrackingId:
+ type: String
+ title: Property Tracking ID
+ hint: UA-XXXXXXX-X
+ order: 1
+codeHead: |
+
+
+
diff --git a/server/modules/analytics/gtm/definition.yml b/server/modules/analytics/gtm/definition.yml
new file mode 100644
index 00000000..51fcb3b9
--- /dev/null
+++ b/server/modules/analytics/gtm/definition.yml
@@ -0,0 +1,26 @@
+key: gtm
+title: Google Tag Manager
+description: Google specializes in Internet-related services and products, which include online advertising technologies, search engine, cloud computing, software, and hardware.
+author: requarks.io
+logo: https://static.requarks.io/logo/google-tag-manager.svg
+website: https://tagmanager.google.com
+isAvailable: true
+props:
+ containerTrackingId:
+ type: String
+ title: Container Tracking ID
+ hint: GTM-XXXXXXX
+ order: 1
+codeHead: |
+
+
+
+codeBodyStart: |
+
+
+
diff --git a/server/modules/analytics/hotjar/definition.yml b/server/modules/analytics/hotjar/definition.yml
new file mode 100644
index 00000000..f3a452b8
--- /dev/null
+++ b/server/modules/analytics/hotjar/definition.yml
@@ -0,0 +1,25 @@
+key: hotjar
+title: Hotjar
+description: Hotjar is the fast & visual way to understand your users, providing everything your team needs to uncover insights and make the right changes to your site.
+author: requarks.io
+logo: https://static.requarks.io/logo/hotjar.svg
+website: https://www.hotjar.com
+isAvailable: true
+props:
+ siteId:
+ type: String
+ title: Site ID
+ hint: A numeric identifier of your site
+ order: 1
+codeHead: |
+
+
diff --git a/server/views/master.pug b/server/views/master.pug
index fd8d6810..6014d3bf 100644
--- a/server/views/master.pug
+++ b/server/views/master.pug
@@ -7,9 +7,9 @@ html
meta(name='theme-color', content='#333333')
meta(name='msapplication-TileColor', content='#333333')
meta(name='msapplication-TileImage', content='/favicons/ms-icon-144x144.png')
-
+
title= pageMeta.title + ' | ' + config.title
-
+
//- SEO / OpenGraph
meta(name='description', content=pageMeta.description)
meta(property='og:title', content=pageMeta.title)
@@ -18,7 +18,7 @@ html
meta(property='og:image', content=pageMeta.image)
meta(property='og:url', content=pageMeta.url)
meta(property='og:site_name', content=config.title)
-
+
//- Favicon
each favsize in [57, 60, 72, 76, 114, 120, 144, 152, 180]
link(rel='apple-touch-icon', sizes=favsize + 'x' + favsize, href='/favicons/apple-icon-' + favsize + 'x' + favsize + '.png')
@@ -26,32 +26,32 @@ html
each favsize in [32, 96, 16]
link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize, href='/favicons/favicon-' + favsize + 'x' + favsize + '.png')
link(rel='manifest', href='/manifest.json')
-
+
//- Site Properties
script.
var siteConfig = !{JSON.stringify(siteConfig)}; var siteLangs = !{JSON.stringify(langs)}
-
- //- CSS
+ //- CSS
- //- JS
-
+ //- JS
+
+
script(
type='text/javascript'
src='/js/runtime.js'
)
-
-
-
+
+
+
script(
type='text/javascript'
src='/js/app.js'
)
-
-
+
+
block head
-
+
body
block body
diff --git a/server/views/setup.pug b/server/views/setup.pug
index a28a97b4..80e6da6f 100644
--- a/server/views/setup.pug
+++ b/server/views/setup.pug
@@ -8,7 +8,7 @@ html
meta(name='msapplication-TileColor', content='#333333')
meta(name='msapplication-TileImage', content='/favicons/ms-icon-144x144.png')
title Wiki.js Setup
-
+
//- Favicon
each favsize in [57, 60, 72, 76, 114, 120, 144, 152, 180]
link(rel='apple-touch-icon', sizes=favsize + 'x' + favsize, href='/favicons/apple-icon-' + favsize + 'x' + favsize + '.png')
@@ -16,31 +16,31 @@ html
each favsize in [32, 96, 16]
link(rel='icon', type='image/png', sizes=favsize + 'x' + favsize, href='/favicons/favicon-' + favsize + 'x' + favsize + '.png')
link(rel='manifest', href='/manifest.json')
-
+
//- Site Lang
script.
var siteConfig = !{JSON.stringify({ title: config.title })}
-
- //- CSS
+ //- CSS
- //- JS
-
+ //- JS
+
+
script(
type='text/javascript'
src='/js/runtime.js'
)
-
-
-
+
+
+
script(
type='text/javascript'
src='/js/setup.js'
)
-
-
+
+
body
#root
setup(telemetry-id=telemetryClientID, wiki-version=packageObj.version)