diff --git a/server/app/data.yml b/server/app/data.yml
index 091e9fb6..cb66ecbd 100644
--- a/server/app/data.yml
+++ b/server/app/data.yml
@@ -33,7 +33,7 @@ defaults:
workers: 3
pollingCheck: 5
scheduledCheck: 300
- maxRetries: 5
+ maxRetries: 2
retryBackoff: 60
historyExpiration: 90000
# DB defaults
@@ -83,6 +83,13 @@ defaults:
search:
maxHits: 100
maintainerEmail: security@requarks.io
+editors:
+ code:
+ contentType: html
+ markdown:
+ contentType: markdown
+ wysiwyg:
+ contentType: html
groups:
defaultPermissions:
- 'read:pages'
diff --git a/server/controllers/common.js b/server/controllers/common.js
index 08e24179..34bacd88 100644
--- a/server/controllers/common.js
+++ b/server/controllers/common.js
@@ -528,9 +528,9 @@ router.get('/*', async (req, res, next) => {
// -> Build theme code injection
const injectCode = {
- css: WIKI.config.theming.injectCSS,
- head: WIKI.config.theming.injectHead,
- body: WIKI.config.theming.injectBody
+ css: '', // WIKI.config.theming.injectCSS,
+ head: '', // WIKI.config.theming.injectHead,
+ body: '' // WIKI.config.theming.injectBody
}
// Handle missing extra field
@@ -551,12 +551,12 @@ router.get('/*', async (req, res, next) => {
// -> Inject comments variables
const commentTmpl = {
- codeTemplate: WIKI.data.commentProvider.codeTemplate,
- head: WIKI.data.commentProvider.head,
- body: WIKI.data.commentProvider.body,
- main: WIKI.data.commentProvider.main
+ codeTemplate: '', // WIKI.data.commentProvider.codeTemplate,
+ head: '', // WIKI.data.commentProvider.head,
+ body: '', // WIKI.data.commentProvider.body,
+ main: '' // WIKI.data.commentProvider.main
}
- if (WIKI.config.features.featurePageComments && WIKI.data.commentProvider.codeTemplate) {
+ if (false && WIKI.config.features.featurePageComments && WIKI.data.commentProvider.codeTemplate) {
[
{ key: 'pageUrl', value: `${WIKI.config.host}/i/${page.id}` },
{ key: 'pageId', value: page.id }
@@ -568,13 +568,14 @@ router.get('/*', async (req, res, next) => {
}
// -> Render view
- res.render('page', {
- page,
- sidebar,
- injectCode,
- comments: commentTmpl,
- effectivePermissions
- })
+ res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
+ // res.render('page', {
+ // page,
+ // sidebar,
+ // injectCode,
+ // comments: commentTmpl,
+ // effectivePermissions
+ // })
} else if (pageArgs.path === 'home') {
res.redirect('/_welcome')
} else {
diff --git a/server/core/db.js b/server/core/db.js
index ac45cfb2..0b2143a4 100644
--- a/server/core/db.js
+++ b/server/core/db.js
@@ -21,7 +21,7 @@ module.exports = {
/**
* Initialize DB
*/
- init() {
+ init(workerMode = false) {
let self = this
WIKI.logger.info('Checking DB configuration...')
@@ -85,10 +85,14 @@ module.exports = {
connection: this.config,
searchPath: [WIKI.config.db.schemas.wiki],
pool: {
- ...WIKI.config.pool,
+ ...workerMode ? { min: 0, max: 1 } : WIKI.config.pool,
async afterCreate(conn, done) {
// -> Set Connection App Name
- await conn.query(`set application_name = 'Wiki.js - ${WIKI.INSTANCE_ID}:MAIN'`)
+ if (workerMode) {
+ await conn.query(`set application_name = 'Wiki.js - ${WIKI.INSTANCE_ID}'`)
+ } else {
+ await conn.query(`set application_name = 'Wiki.js - ${WIKI.INSTANCE_ID}:MAIN'`)
+ }
done()
}
},
@@ -145,7 +149,7 @@ module.exports = {
// Perform init tasks
- this.onReady = (async () => {
+ this.onReady = workerMode ? Promise.resolve() : (async () => {
await initTasks.connect()
await initTasks.migrateFromLegacy()
await initTasks.syncSchemas()
diff --git a/server/core/scheduler.js b/server/core/scheduler.js
index b26a9f59..323f38b8 100644
--- a/server/core/scheduler.js
+++ b/server/core/scheduler.js
@@ -4,6 +4,9 @@ const autoload = require('auto-load')
const path = require('node:path')
const cronparser = require('cron-parser')
const { DateTime } = require('luxon')
+const { v4: uuid } = require('uuid')
+const { createDeferred } = require('../helpers/common')
+const _ = require('lodash')
module.exports = {
workerPool: null,
@@ -12,6 +15,7 @@ module.exports = {
pollingRef: null,
scheduledRef: null,
tasks: null,
+ completionPromises: [],
async init () {
this.maxWorkers = WIKI.config.scheduler.workers === 'auto' ? (os.cpus().length - 1) : WIKI.config.scheduler.workers
if (this.maxWorkers < 1) { this.maxWorkers = 1 }
@@ -38,6 +42,20 @@ module.exports = {
}
break
}
+ case 'jobCompleted': {
+ const jobPromise = _.find(this.completionPromises, ['id', payload.id])
+ if (jobPromise) {
+ if (payload.state === 'success') {
+ jobPromise.resolve()
+ } else {
+ jobPromise.reject(new Error(payload.errorMessage))
+ }
+ setTimeout(() => {
+ _.remove(this.completionPromises, ['id', payload.id])
+ })
+ }
+ break
+ }
}
})
@@ -56,23 +74,52 @@ module.exports = {
WIKI.logger.info('Scheduler: [ STARTED ]')
},
- async addJob ({ task, payload, waitUntil, maxRetries, isScheduled = false, notify = true }) {
+ /**
+ * Add a job to the scheduler
+ * @param {Object} opts - Job options
+ * @param {string} opts.task - The task name to execute.
+ * @param {Object} [opts.payload={}] - An optional data object to pass to the job.
+ * @param {Date} [opts.waitUntil] - An optional datetime after which the task is allowed to run.
+ * @param {Number} [opts.maxRetries] - The number of times this job can be restarted upon failure. Uses server defaults if not provided.
+ * @param {Boolean} [opts.isScheduled=false] - Whether this is a scheduled job.
+ * @param {Boolean} [opts.notify=true] - Whether to notify all instances that a new job is available.
+ * @param {Boolean} [opts.promise=false] - Whether to return a promise property that resolves when the job completes.
+ * @returns {Promise}
+ */
+ async addJob ({ task, payload = {}, waitUntil, maxRetries, isScheduled = false, notify = true, promise = false }) {
try {
- await WIKI.db.knex('jobs').insert({
- task,
- useWorker: !(typeof this.tasks[task] === 'function'),
- payload,
- maxRetries: maxRetries ?? WIKI.config.scheduler.maxRetries,
- isScheduled,
- waitUntil,
- createdBy: WIKI.INSTANCE_ID
- })
+ const jobId = uuid()
+ const jobDefer = createDeferred()
+ if (promise) {
+ this.completionPromises.push({
+ id: jobId,
+ added: DateTime.utc(),
+ resolve: jobDefer.resolve,
+ reject: jobDefer.reject
+ })
+ }
+ await WIKI.db.knex('jobs')
+ .insert({
+ id: jobId,
+ task,
+ useWorker: !(typeof this.tasks[task] === 'function'),
+ payload,
+ maxRetries: maxRetries ?? WIKI.config.scheduler.maxRetries,
+ isScheduled,
+ waitUntil,
+ createdBy: WIKI.INSTANCE_ID
+ })
if (notify) {
WIKI.db.listener.publish('scheduler', {
source: WIKI.INSTANCE_ID,
- event: 'newJob'
+ event: 'newJob',
+ id: jobId
})
}
+ return {
+ id: jobId,
+ ...promise && { promise: jobDefer.promise }
+ }
} catch (err) {
WIKI.logger.warn(`Failed to add job to scheduler: ${err.message}`)
}
@@ -130,6 +177,12 @@ module.exports = {
completedAt: new Date()
})
WIKI.logger.info(`Completed job ${job.id}: ${job.task}`)
+ WIKI.db.listener.publish('scheduler', {
+ source: WIKI.INSTANCE_ID,
+ event: 'jobCompleted',
+ state: 'success',
+ id: job.id
+ })
} catch (err) {
WIKI.logger.warn(`Failed to complete job ${job.id}: ${job.task} [ FAILED ]`)
WIKI.logger.warn(err)
@@ -137,9 +190,17 @@ module.exports = {
await WIKI.db.knex('jobHistory').where({
id: job.id
}).update({
+ attempt: job.retries + 1,
state: 'failed',
lastErrorMessage: err.message
})
+ WIKI.db.listener.publish('scheduler', {
+ source: WIKI.INSTANCE_ID,
+ event: 'jobCompleted',
+ state: 'failed',
+ id: job.id,
+ errorMessage: err.message
+ })
// -> Reschedule for retry
if (job.retries < job.maxRetries) {
const backoffDelay = (2 ** job.retries) * WIKI.config.scheduler.retryBackoff
diff --git a/server/db/migrations/3.0.0.js b/server/db/migrations/3.0.0.js
index c873b02d..35c2af1a 100644
--- a/server/db/migrations/3.0.0.js
+++ b/server/db/migrations/3.0.0.js
@@ -243,7 +243,7 @@ exports.up = async knex => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('module').notNullable()
table.boolean('isEnabled').notNullable().defaultTo(false)
- table.jsonb('config')
+ table.jsonb('config').notNullable().defaultTo('{}')
})
// SETTINGS ----------------------------
.createTable('settings', table => {
@@ -370,9 +370,6 @@ exports.up = async knex => {
table.uuid('pageId').notNullable().references('id').inTable('pages').onDelete('CASCADE')
table.string('localeCode', 5).references('code').inTable('locales')
})
- .table('renderers', table => {
- table.uuid('siteId').notNullable().references('id').inTable('sites')
- })
.table('storage', table => {
table.uuid('siteId').notNullable().references('id').inTable('sites')
})
diff --git a/server/graph/resolvers/system.js b/server/graph/resolvers/system.js
index 6b5fbed8..36d777b3 100644
--- a/server/graph/resolvers/system.js
+++ b/server/graph/resolvers/system.js
@@ -79,6 +79,25 @@ module.exports = {
}
},
Mutation: {
+ async cancelJob (obj, args, context) {
+ WIKI.logger.info(`Admin requested cancelling job ${args.id}...`)
+ try {
+ const result = await WIKI.db.knex('jobs')
+ .where('id', args.id)
+ .del()
+ if (result === 1) {
+ WIKI.logger.info(`Cancelled job ${args.id} [ OK ]`)
+ } else {
+ throw new Error('Job has already entered active state or does not exist.')
+ }
+ return {
+ operation: graphHelper.generateSuccess('Cancelled job successfully.')
+ }
+ } catch (err) {
+ WIKI.logger.warn(err)
+ return graphHelper.generateError(err)
+ }
+ },
async disconnectWS (obj, args, context) {
WIKI.servers.ws.disconnectSockets(true)
WIKI.logger.info('All active websocket connections have been terminated.')
@@ -97,6 +116,39 @@ module.exports = {
return graphHelper.generateError(err)
}
},
+ async retryJob (obj, args, context) {
+ WIKI.logger.info(`Admin requested rescheduling of job ${args.id}...`)
+ try {
+ const job = await WIKI.db.knex('jobHistory')
+ .where('id', args.id)
+ .first()
+ if (!job) {
+ throw new Error('No such job found.')
+ } else if (job.state === 'interrupted') {
+ throw new Error('Cannot reschedule a task that has been interrupted. It will automatically be retried shortly.')
+ } else if (job.state === 'failed' && job.attempt < job.maxRetries) {
+ throw new Error('Cannot reschedule a task that has not reached its maximum retry attempts.')
+ }
+ await WIKI.db.knex('jobs')
+ .insert({
+ id: job.id,
+ task: job.task,
+ useWorker: job.useWorker,
+ payload: job.payload,
+ retries: job.attempt,
+ maxRetries: job.maxRetries,
+ isScheduled: job.wasScheduled,
+ createdBy: WIKI.INSTANCE_ID
+ })
+ WIKI.logger.info(`Job ${args.id} has been rescheduled [ OK ]`)
+ return {
+ operation: graphHelper.generateSuccess('Job rescheduled successfully.')
+ }
+ } catch (err) {
+ WIKI.logger.warn(err)
+ return graphHelper.generateError(err)
+ }
+ },
async updateSystemFlags (obj, args, context) {
WIKI.config.flags = _.transform(args.flags, (result, row) => {
_.set(result, row.key, row.value)
diff --git a/server/graph/schemas/system.graphql b/server/graph/schemas/system.graphql
index 8344d41c..c79428ae 100644
--- a/server/graph/schemas/system.graphql
+++ b/server/graph/schemas/system.graphql
@@ -16,12 +16,20 @@ extend type Query {
}
extend type Mutation {
+ cancelJob(
+ id: UUID!
+ ): DefaultResponse
+
disconnectWS: DefaultResponse
installExtension(
key: String!
): DefaultResponse
+ retryJob(
+ id: UUID!
+ ): DefaultResponse
+
updateSystemFlags(
flags: [SystemFlagInput]!
): DefaultResponse
diff --git a/server/helpers/common.js b/server/helpers/common.js
index 44fd39f1..205e0dfc 100644
--- a/server/helpers/common.js
+++ b/server/helpers/common.js
@@ -1,6 +1,34 @@
const _ = require('lodash')
module.exports = {
+ /* eslint-disable promise/param-names */
+ createDeferred () {
+ let result, resolve, reject
+ return {
+ resolve: function (value) {
+ if (resolve) {
+ resolve(value)
+ } else {
+ result = result || new Promise(function (r) { r(value) })
+ }
+ },
+ reject: function (reason) {
+ if (reject) {
+ reject(reason)
+ } else {
+ result = result || new Promise(function (x, j) { j(reason) })
+ }
+ },
+ promise: new Promise(function (r, j) {
+ if (result) {
+ r(result)
+ } else {
+ resolve = r
+ reject = j
+ }
+ })
+ }
+ },
/**
* Get default value of type
*
diff --git a/server/models/editors.js b/server/models/editors.js
deleted file mode 100644
index e24d00a2..00000000
--- a/server/models/editors.js
+++ /dev/null
@@ -1,41 +0,0 @@
-const Model = require('objection').Model
-
-/**
- * Editor model
- */
-module.exports = class Editor extends Model {
- static get tableName() { return 'editors' }
- 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 getEditors() {
- return WIKI.db.editors.query()
- }
-
- static async getDefaultEditor(contentType) {
- // TODO - hardcoded for now
- switch (contentType) {
- case 'markdown':
- return 'markdown'
- case 'html':
- return 'ckeditor'
- default:
- return 'code'
- }
- }
-}
diff --git a/server/models/pages.js b/server/models/pages.js
index 60c7095d..583a1777 100644
--- a/server/models/pages.js
+++ b/server/models/pages.js
@@ -34,7 +34,7 @@ module.exports = class Page extends Model {
required: ['path', 'title'],
properties: {
- id: {type: 'integer'},
+ id: {type: 'string'},
path: {type: 'string'},
hash: {type: 'string'},
title: {type: 'string'},
@@ -44,7 +44,7 @@ module.exports = class Page extends Model {
publishEndDate: {type: 'string'},
content: {type: 'string'},
contentType: {type: 'string'},
-
+ siteId: {type: 'string'},
createdAt: {type: 'string'},
updatedAt: {type: 'string'}
}
@@ -125,11 +125,11 @@ module.exports = class Page extends Model {
*/
static get cacheSchema() {
return new JSBinType({
- id: 'uint',
- authorId: 'uint',
+ id: 'string',
+ authorId: 'string',
authorName: 'string',
createdAt: 'string',
- creatorId: 'uint',
+ creatorId: 'string',
creatorName: 'string',
description: 'string',
editor: 'string',
@@ -137,6 +137,7 @@ module.exports = class Page extends Model {
publishEndDate: 'string',
publishStartDate: 'string',
render: 'string',
+ siteId: 'string',
tags: [
{
tag: 'string'
@@ -291,7 +292,7 @@ module.exports = class Page extends Model {
authorId: opts.user.id,
content: opts.content,
creatorId: opts.user.id,
- contentType: _.get(_.find(WIKI.data.editors, ['key', opts.editor]), `contentType`, 'text'),
+ contentType: _.get(WIKI.data.editors[opts.editor], 'contentType', 'text'),
description: opts.description,
editor: opts.editor,
hash: pageHelper.generateHash({ path: opts.path, locale: opts.locale }),
@@ -322,6 +323,9 @@ module.exports = class Page extends Model {
// -> Render page to HTML
await WIKI.db.pages.renderPage(page)
+ return page
+ // TODO: Handle remaining flow
+
// -> Rebuild page tree
await WIKI.db.pages.rebuildTree()
@@ -922,12 +926,15 @@ module.exports = class Page extends Model {
* @returns {Promise} Promise with no value
*/
static async renderPage(page) {
- const renderJob = await WIKI.scheduler.registerJob({
- name: 'render-page',
- immediate: true,
- worker: true
- }, page.id)
- return renderJob.finished
+ const renderJob = await WIKI.scheduler.addJob({
+ task: 'render-page',
+ payload: {
+ id: page.id
+ },
+ maxRetries: 0,
+ promise: true
+ })
+ return renderJob.promise
}
/**
@@ -963,7 +970,7 @@ module.exports = class Page extends Model {
* @returns {Promise} Promise of the Page Model Instance
*/
static async getPageFromDb(opts) {
- const queryModeID = _.isNumber(opts)
+ const queryModeID = typeof opts === 'string'
try {
return WIKI.db.pages.query()
.column([
@@ -985,6 +992,7 @@ module.exports = class Page extends Model {
'pages.localeCode',
'pages.authorId',
'pages.creatorId',
+ 'pages.siteId',
'pages.extra',
{
authorName: 'author.name',
@@ -1033,7 +1041,7 @@ module.exports = class Page extends Model {
id: page.id,
authorId: page.authorId,
authorName: page.authorName,
- createdAt: page.createdAt,
+ createdAt: page.createdAt.toISOString(),
creatorId: page.creatorId,
creatorName: page.creatorName,
description: page.description,
@@ -1042,14 +1050,15 @@ module.exports = class Page extends Model {
css: _.get(page, 'extra.css', ''),
js: _.get(page, 'extra.js', '')
},
- publishState: page.publishState,
- publishEndDate: page.publishEndDate,
- publishStartDate: page.publishStartDate,
+ publishState: page.publishState ?? '',
+ publishEndDate: page.publishEndDate ?? '',
+ publishStartDate: page.publishStartDate ?? '',
render: page.render,
+ siteId: page.siteId,
tags: page.tags.map(t => _.pick(t, ['tag'])),
title: page.title,
toc: _.isString(page.toc) ? page.toc : JSON.stringify(page.toc),
- updatedAt: page.updatedAt
+ updatedAt: page.updatedAt.toISOString()
}))
}
diff --git a/server/models/renderers.js b/server/models/renderers.js
index cad8226e..6e0cd037 100644
--- a/server/models/renderers.js
+++ b/server/models/renderers.js
@@ -55,19 +55,65 @@ module.exports = class Renderer extends Model {
}
static async refreshRenderersFromDisk() {
- // const dbRenderers = await WIKI.db.renderers.query()
+ try {
+ const dbRenderers = await WIKI.db.renderers.query()
+
+ // -> Fetch definitions from disk
+ await WIKI.db.renderers.fetchDefinitions()
+
+ // -> Insert new Renderers
+ const newRenderers = []
+ let updatedRenderers = 0
+ for (const renderer of WIKI.data.renderers) {
+ if (!_.some(dbRenderers, ['module', renderer.key])) {
+ newRenderers.push({
+ module: renderer.key,
+ isEnabled: renderer.enabledDefault ?? true,
+ config: _.transform(renderer.props, (result, value, key) => {
+ result[key] = value.default
+ return result
+ }, {})
+ })
+ } else {
+ const rendererConfig = _.get(_.find(dbRenderers, ['module', renderer.key]), 'config', {})
+ await WIKI.db.renderers.query().patch({
+ config: _.transform(renderer.props, (result, value, key) => {
+ if (!_.has(result, key)) {
+ result[key] = value.default
+ }
+ return result
+ }, rendererConfig)
+ }).where('module', renderer.key)
+ updatedRenderers++
+ }
+ }
+ if (newRenderers.length > 0) {
+ await WIKI.db.renderers.query().insert(newRenderers)
+ WIKI.logger.info(`Loaded ${newRenderers.length} new renderers: [ OK ]`)
+ }
- // -> Fetch definitions from disk
- await WIKI.db.renderers.fetchDefinitions()
+ if (updatedRenderers > 0) {
+ WIKI.logger.info(`Updated ${updatedRenderers} existing renderers: [ OK ]`)
+ }
- // TODO: Merge existing configs with updated modules
+ // -> Delete removed Renderers
+ for (const renderer of dbRenderers) {
+ if (!_.some(WIKI.data.renderers, ['key', renderer.module])) {
+ await WIKI.db.renderers.query().where('module', renderer.key).del()
+ WIKI.logger.info(`Removed renderer ${renderer.key} because it is no longer present in the modules folder: [ OK ]`)
+ }
+ }
+ } catch (err) {
+ WIKI.logger.error('Failed to import renderers: [ FAILED ]')
+ WIKI.logger.error(err)
+ }
}
static async getRenderingPipeline(contentType) {
const renderersDb = await WIKI.db.renderers.query().where('isEnabled', true)
if (renderersDb && renderersDb.length > 0) {
const renderers = renderersDb.map(rdr => {
- const renderer = _.find(WIKI.data.renderers, ['key', rdr.key])
+ const renderer = _.find(WIKI.data.renderers, ['key', rdr.module])
return {
...renderer,
config: rdr.config
diff --git a/server/modules/rendering/html-asciinema/definition.yml b/server/modules/rendering/html-asciinema/definition.yml
index 17358416..bda5496a 100644
--- a/server/modules/rendering/html-asciinema/definition.yml
+++ b/server/modules/rendering/html-asciinema/definition.yml
@@ -4,5 +4,5 @@ description: Embed asciinema players from compatible links
author: requarks.io
icon: mdi-theater
enabledDefault: false
-dependsOn: htmlCore
+dependsOn: html-core
props: {}
diff --git a/server/modules/rendering/html-blockquotes/definition.yml b/server/modules/rendering/html-blockquotes/definition.yml
index b24c8db6..5c43f825 100644
--- a/server/modules/rendering/html-blockquotes/definition.yml
+++ b/server/modules/rendering/html-blockquotes/definition.yml
@@ -4,5 +4,5 @@ description: Parse blockquotes box styling
author: requarks.io
icon: mdi-alpha-t-box-outline
enabledDefault: true
-dependsOn: htmlCore
+dependsOn: html-core
props: {}
diff --git a/server/modules/rendering/html-codehighlighter/definition.yml b/server/modules/rendering/html-codehighlighter/definition.yml
index 656931ff..075a56b8 100644
--- a/server/modules/rendering/html-codehighlighter/definition.yml
+++ b/server/modules/rendering/html-codehighlighter/definition.yml
@@ -4,6 +4,6 @@ description: Syntax detector for programming code
author: requarks.io
icon: mdi-code-braces
enabledDefault: true
-dependsOn: htmlCore
+dependsOn: html-core
step: pre
props: {}
diff --git a/server/modules/rendering/html-core/definition.yml b/server/modules/rendering/html-core/definition.yml
index 2fe4783c..3ef3dd1f 100644
--- a/server/modules/rendering/html-core/definition.yml
+++ b/server/modules/rendering/html-core/definition.yml
@@ -1,4 +1,4 @@
-key: htmlCore
+key: html-core
title: Core
description: Basic HTML Parser
author: requarks.io
diff --git a/server/modules/rendering/html-core/renderer.js b/server/modules/rendering/html-core/renderer.js
index 972ef83f..73d43bed 100644
--- a/server/modules/rendering/html-core/renderer.js
+++ b/server/modules/rendering/html-core/renderer.js
@@ -21,7 +21,7 @@ module.exports = {
// --------------------------------
for (let child of _.reject(this.children, ['step', 'post'])) {
- const renderer = require(`../${_.kebabCase(child.key)}/renderer.js`)
+ const renderer = require(`../${child.key}/renderer.js`)
await renderer.init($, child.config)
}
@@ -33,10 +33,7 @@ module.exports = {
const reservedPrefixes = /^\/[a-z]\//i
const exactReservedPaths = /^\/[a-z]$/i
- const isHostSet = WIKI.config.host.length > 7 && WIKI.config.host !== 'http://'
- if (!isHostSet) {
- WIKI.logger.warn('Host is not set. You must set the Site Host under General in the Administration Area!')
- }
+ const hasHostname = this.site.hostname !== '*'
$('a').each((i, elm) => {
let href = $(elm).attr('href')
@@ -48,8 +45,8 @@ module.exports = {
}
// -> Strip host from local links
- if (isHostSet && href.indexOf(`${WIKI.config.host}/`) === 0) {
- href = href.replace(WIKI.config.host, '')
+ if (hasHostname && href.indexOf(`${this.site.hostname}/`) === 0) {
+ href = href.replace(this.site.hostname, '')
}
// -> Assign local / external tag
@@ -68,7 +65,7 @@ module.exports = {
let pagePath = null
// -> Add locale prefix if using namespacing
- if (WIKI.config.lang.namespacing) {
+ if (this.site.config.localeNamespacing) {
// -> Reformat paths
if (href.indexOf('/') !== 0) {
if (this.config.absoluteLinks) {
diff --git a/server/modules/rendering/html-diagram/definition.yml b/server/modules/rendering/html-diagram/definition.yml
index f850c7f0..2c7eeaf9 100644
--- a/server/modules/rendering/html-diagram/definition.yml
+++ b/server/modules/rendering/html-diagram/definition.yml
@@ -4,5 +4,5 @@ description: HTML Processing for diagrams (draw.io)
author: requarks.io
icon: mdi-chart-multiline
enabledDefault: true
-dependsOn: htmlCore
+dependsOn: html-core
props: {}
diff --git a/server/modules/rendering/html-image-prefetch/definition.yml b/server/modules/rendering/html-image-prefetch/definition.yml
index bf7a65df..09ebb028 100644
--- a/server/modules/rendering/html-image-prefetch/definition.yml
+++ b/server/modules/rendering/html-image-prefetch/definition.yml
@@ -4,5 +4,5 @@ description: Prefetch remotely rendered images (korki/plantuml)
author: requarks.io
icon: mdi-cloud-download-outline
enabledDefault: false
-dependsOn: htmlCore
+dependsOn: html-core
props: {}
diff --git a/server/modules/rendering/html-mediaplayers/definition.yml b/server/modules/rendering/html-mediaplayers/definition.yml
index 4b0201d0..e37ae900 100644
--- a/server/modules/rendering/html-mediaplayers/definition.yml
+++ b/server/modules/rendering/html-mediaplayers/definition.yml
@@ -4,5 +4,5 @@ description: Embed players such as Youtube, Vimeo, Soundcloud, etc.
author: requarks.io
icon: mdi-video
enabledDefault: true
-dependsOn: htmlCore
+dependsOn: html-core
props: {}
diff --git a/server/modules/rendering/html-mermaid/definition.yml b/server/modules/rendering/html-mermaid/definition.yml
index 2119a6b6..a0608dfa 100644
--- a/server/modules/rendering/html-mermaid/definition.yml
+++ b/server/modules/rendering/html-mermaid/definition.yml
@@ -4,5 +4,5 @@ description: Generate flowcharts from Mermaid syntax
author: requarks.io
icon: mdi-arrow-decision-outline
enabledDefault: true
-dependsOn: htmlCore
+dependsOn: html-core
props: {}
diff --git a/server/modules/rendering/html-security/definition.yml b/server/modules/rendering/html-security/definition.yml
index 04067c58..bcfcd7bb 100644
--- a/server/modules/rendering/html-security/definition.yml
+++ b/server/modules/rendering/html-security/definition.yml
@@ -4,7 +4,7 @@ description: Filter and strips potentially dangerous content
author: requarks.io
icon: mdi-fire
enabledDefault: true
-dependsOn: htmlCore
+dependsOn: html-core
step: post
order: 99999
props:
diff --git a/server/modules/rendering/html-tabset/definition.yml b/server/modules/rendering/html-tabset/definition.yml
index 147b0128..281a8074 100644
--- a/server/modules/rendering/html-tabset/definition.yml
+++ b/server/modules/rendering/html-tabset/definition.yml
@@ -4,5 +4,5 @@ description: Transform headers into tabs
author: requarks.io
icon: mdi-tab
enabledDefault: true
-dependsOn: htmlCore
+dependsOn: html-core
props: {}
diff --git a/server/modules/rendering/html-twemoji/definition.yml b/server/modules/rendering/html-twemoji/definition.yml
index aedb9c0a..fe96ddc3 100644
--- a/server/modules/rendering/html-twemoji/definition.yml
+++ b/server/modules/rendering/html-twemoji/definition.yml
@@ -4,7 +4,7 @@ description: Apply Twitter Emojis to all Unicode emojis
author: requarks.io
icon: mdi-emoticon-happy-outline
enabledDefault: true
-dependsOn: htmlCore
+dependsOn: html-core
step: post
order: 10
props: {}
diff --git a/server/modules/rendering/markdown-abbr/definition.yml b/server/modules/rendering/markdown-abbr/definition.yml
index d51822e2..f64ab46c 100644
--- a/server/modules/rendering/markdown-abbr/definition.yml
+++ b/server/modules/rendering/markdown-abbr/definition.yml
@@ -4,5 +4,5 @@ description: Parse abbreviations into abbr tags
author: requarks.io
icon: mdi-contain-start
enabledDefault: true
-dependsOn: markdownCore
+dependsOn: markdown-core
props: {}
diff --git a/server/modules/rendering/markdown-core/definition.yml b/server/modules/rendering/markdown-core/definition.yml
index 7d3eea7b..17a35702 100644
--- a/server/modules/rendering/markdown-core/definition.yml
+++ b/server/modules/rendering/markdown-core/definition.yml
@@ -1,4 +1,4 @@
-key: markdownCore
+key: markdown-core
title: Core
description: Basic Markdown Parser
author: requarks.io
diff --git a/server/modules/rendering/markdown-core/renderer.js b/server/modules/rendering/markdown-core/renderer.js
index 42bd12be..8e686c6c 100644
--- a/server/modules/rendering/markdown-core/renderer.js
+++ b/server/modules/rendering/markdown-core/renderer.js
@@ -44,7 +44,7 @@ module.exports = {
})
for (let child of this.children) {
- const renderer = require(`../${_.kebabCase(child.key)}/renderer.js`)
+ const renderer = require(`../${child.key}/renderer.js`)
await renderer.init(mkdown, child.config)
}
diff --git a/server/modules/rendering/markdown-emoji/definition.yml b/server/modules/rendering/markdown-emoji/definition.yml
index c1b8f36c..716cddee 100644
--- a/server/modules/rendering/markdown-emoji/definition.yml
+++ b/server/modules/rendering/markdown-emoji/definition.yml
@@ -4,5 +4,5 @@ description: Convert tags to emojis
author: requarks.io
icon: mdi-sticker-emoji
enabledDefault: true
-dependsOn: markdownCore
+dependsOn: markdown-core
props: {}
diff --git a/server/modules/rendering/markdown-expandtabs/definition.yml b/server/modules/rendering/markdown-expandtabs/definition.yml
index 97b6990e..6531d2a8 100644
--- a/server/modules/rendering/markdown-expandtabs/definition.yml
+++ b/server/modules/rendering/markdown-expandtabs/definition.yml
@@ -4,7 +4,7 @@ description: Replace tabs with spaces in code blocks
author: requarks.io
icon: mdi-arrow-expand-horizontal
enabledDefault: true
-dependsOn: markdownCore
+dependsOn: markdown-core
props:
tabWidth:
type: Number
diff --git a/server/modules/rendering/markdown-footnotes/definition.yml b/server/modules/rendering/markdown-footnotes/definition.yml
index d34d798b..00876294 100644
--- a/server/modules/rendering/markdown-footnotes/definition.yml
+++ b/server/modules/rendering/markdown-footnotes/definition.yml
@@ -4,5 +4,5 @@ description: Parse footnotes references
author: requarks.io
icon: mdi-page-layout-footer
enabledDefault: true
-dependsOn: markdownCore
+dependsOn: markdown-core
props: {}
diff --git a/server/modules/rendering/markdown-imsize/definition.yml b/server/modules/rendering/markdown-imsize/definition.yml
index 6bafb563..603a3069 100644
--- a/server/modules/rendering/markdown-imsize/definition.yml
+++ b/server/modules/rendering/markdown-imsize/definition.yml
@@ -4,5 +4,5 @@ description: Adds dimensions attributes to images
author: requarks.io
icon: mdi-image-size-select-large
enabledDefault: true
-dependsOn: markdownCore
+dependsOn: markdown-core
props: {}
diff --git a/server/modules/rendering/markdown-katex/definition.yml b/server/modules/rendering/markdown-katex/definition.yml
index 87b3b218..0532ea75 100644
--- a/server/modules/rendering/markdown-katex/definition.yml
+++ b/server/modules/rendering/markdown-katex/definition.yml
@@ -4,7 +4,7 @@ description: LaTeX Math + Chemical Expression Typesetting Renderer
author: requarks.io
icon: mdi-math-integral
enabledDefault: true
-dependsOn: markdownCore
+dependsOn: markdown-core
props:
useInline:
type: Boolean
diff --git a/server/modules/rendering/markdown-kroki/definition.yml b/server/modules/rendering/markdown-kroki/definition.yml
index 09a77f49..b56d3785 100644
--- a/server/modules/rendering/markdown-kroki/definition.yml
+++ b/server/modules/rendering/markdown-kroki/definition.yml
@@ -4,7 +4,7 @@ description: Kroki Diagrams Parser
author: rlanyi (based on PlantUML renderer)
icon: mdi-sitemap
enabledDefault: false
-dependsOn: markdownCore
+dependsOn: markdown-core
props:
server:
type: String
diff --git a/server/modules/rendering/markdown-mathjax/definition.yml b/server/modules/rendering/markdown-mathjax/definition.yml
index bf2e6460..c0e95120 100644
--- a/server/modules/rendering/markdown-mathjax/definition.yml
+++ b/server/modules/rendering/markdown-mathjax/definition.yml
@@ -4,7 +4,7 @@ description: LaTeX Math + Chemical Expression Typesetting Renderer
author: requarks.io
icon: mdi-math-integral
enabledDefault: false
-dependsOn: markdownCore
+dependsOn: markdown-core
props:
useInline:
type: Boolean
diff --git a/server/modules/rendering/markdown-multi-table/definition.yml b/server/modules/rendering/markdown-multi-table/definition.yml
index d3cbe67a..57885052 100644
--- a/server/modules/rendering/markdown-multi-table/definition.yml
+++ b/server/modules/rendering/markdown-multi-table/definition.yml
@@ -4,7 +4,7 @@ description: Add MultiMarkdown table support
author: requarks.io
icon: mdi-table
enabledDefault: false
-dependsOn: markdownCore
+dependsOn: markdown-core
props:
multilineEnabled:
type: Boolean
diff --git a/server/modules/rendering/markdown-plantuml/definition.yml b/server/modules/rendering/markdown-plantuml/definition.yml
index e8b156fc..eb81d834 100644
--- a/server/modules/rendering/markdown-plantuml/definition.yml
+++ b/server/modules/rendering/markdown-plantuml/definition.yml
@@ -4,7 +4,7 @@ description: PlantUML Markdown Parser
author: ethanmdavidson
icon: mdi-sitemap
enabledDefault: true
-dependsOn: markdownCore
+dependsOn: markdown-core
props:
server:
type: String
diff --git a/server/modules/rendering/markdown-supsub/definition.yml b/server/modules/rendering/markdown-supsub/definition.yml
index f037d417..ffb63b21 100644
--- a/server/modules/rendering/markdown-supsub/definition.yml
+++ b/server/modules/rendering/markdown-supsub/definition.yml
@@ -4,7 +4,7 @@ description: Parse subscript and superscript tags
author: requarks.io
icon: mdi-format-superscript
enabledDefault: true
-dependsOn: markdownCore
+dependsOn: markdown-core
props:
subEnabled:
type: Boolean
diff --git a/server/modules/rendering/markdown-tasklists/definition.yml b/server/modules/rendering/markdown-tasklists/definition.yml
index aa2dae74..866ef99b 100644
--- a/server/modules/rendering/markdown-tasklists/definition.yml
+++ b/server/modules/rendering/markdown-tasklists/definition.yml
@@ -4,5 +4,5 @@ description: Parse task lists to checkboxes
author: requarks.io
icon: mdi-format-list-checks
enabledDefault: true
-dependsOn: markdownCore
+dependsOn: markdown-core
props: {}
diff --git a/server/tasks/workers/render-page.js b/server/tasks/workers/render-page.js
new file mode 100644
index 00000000..6c23cbf5
--- /dev/null
+++ b/server/tasks/workers/render-page.js
@@ -0,0 +1,92 @@
+const _ = require('lodash')
+const cheerio = require('cheerio')
+
+module.exports = async ({ payload }) => {
+ WIKI.logger.info(`Rendering page ${payload.id}...`)
+
+ try {
+ await WIKI.ensureDb()
+
+ const page = await WIKI.db.pages.getPageFromDb(payload.id)
+ if (!page) {
+ throw new Error('Invalid Page Id')
+ }
+
+ const site = await WIKI.db.sites.query().findById(page.siteId)
+
+ await WIKI.db.renderers.fetchDefinitions()
+
+ const pipeline = await WIKI.db.renderers.getRenderingPipeline(page.contentType)
+
+ let output = page.content
+
+ if (_.isEmpty(page.content)) {
+ WIKI.logger.warn(`Failed to render page ID ${payload.id} because content was empty: [ FAILED ]`)
+ }
+
+ for (let core of pipeline) {
+ const renderer = require(`../../modules/rendering/${core.key}/renderer.js`)
+ output = await renderer.render.call({
+ config: core.config,
+ children: core.children,
+ page,
+ site,
+ input: output
+ })
+ }
+
+ // Parse TOC
+ const $ = cheerio.load(output)
+ let isStrict = $('h1').length > 0 // <- Allows for documents using H2 as top level
+ let toc = { root: [] }
+
+ $('h1,h2,h3,h4,h5,h6').each((idx, el) => {
+ const depth = _.toSafeInteger(el.name.substring(1)) - (isStrict ? 1 : 2)
+ let leafPathError = false
+
+ const leafPath = _.reduce(_.times(depth), (curPath, curIdx) => {
+ if (_.has(toc, curPath)) {
+ const lastLeafIdx = _.get(toc, curPath).length - 1
+ if (lastLeafIdx >= 0) {
+ curPath = `${curPath}[${lastLeafIdx}].children`
+ } else {
+ leafPathError = true
+ }
+ }
+ return curPath
+ }, 'root')
+
+ if (leafPathError) { return }
+
+ const leafSlug = $('.toc-anchor', el).first().attr('href')
+ $('.toc-anchor', el).remove()
+
+ _.get(toc, leafPath).push({
+ title: _.trim($(el).text()),
+ anchor: leafSlug,
+ children: []
+ })
+ })
+
+ // Save to DB
+ await WIKI.db.pages.query()
+ .patch({
+ render: output,
+ toc: JSON.stringify(toc.root)
+ })
+ .where('id', payload.id)
+
+ // Save to cache
+ // await WIKI.db.pages.savePageToCache({
+ // ...page,
+ // render: output,
+ // toc: JSON.stringify(toc.root)
+ // })
+
+ WIKI.logger.info(`Rendered page ${payload.id}: [ COMPLETED ]`)
+ } catch (err) {
+ WIKI.logger.error(`Rendering page ${payload.id}: [ FAILED ]`)
+ WIKI.logger.error(err.message)
+ throw err
+ }
+}
diff --git a/server/views/page.pug b/server/views/page.pug
index 4cfd3eba..6fc2b4cc 100644
--- a/server/views/page.pug
+++ b/server/views/page.pug
@@ -5,8 +5,6 @@ block head
style(type='text/css')!= injectCode.css
if injectCode.head
!= injectCode.head
- if config.features.featurePageComments
- != comments.head
block body
#root
@@ -21,20 +19,9 @@ block body
author-name=page.authorName
:author-id=page.authorId
editor=page.editorKey
- :is-published=page.isPublished.toString()
- toc=Buffer.from(page.toc).toString('base64')
:page-id=page.id
- sidebar=Buffer.from(JSON.stringify(sidebar)).toString('base64')
- nav-mode=config.nav.mode
- comments-enabled=config.features.featurePageComments
- effective-permissions=Buffer.from(JSON.stringify(effectivePermissions)).toString('base64')
- comments-external=comments.codeTemplate
)
template(slot='contents')
div!= page.render
- template(slot='comments')
- div!= comments.main
if injectCode.body
!= injectCode.body
- if config.features.featurePageComments
- != comments.body
diff --git a/server/worker.js b/server/worker.js
index 1b8b4e28..71456a73 100644
--- a/server/worker.js
+++ b/server/worker.js
@@ -12,7 +12,23 @@ let WIKI = {
INSTANCE_ID: 'worker',
SERVERPATH: path.join(process.cwd(), 'server'),
Error: require('./helpers/error'),
- configSvc: require('./core/config')
+ configSvc: require('./core/config'),
+ ensureDb: async () => {
+ if (WIKI.db) { return true }
+
+ WIKI.db = require('./core/db').init(true)
+
+ try {
+ await WIKI.configSvc.loadFromDb()
+ await WIKI.configSvc.applyFlags()
+ } catch (err) {
+ WIKI.logger.error('Database Initialization Error: ' + err.message)
+ if (WIKI.IS_DEBUG) {
+ WIKI.logger.error(err)
+ }
+ process.exit(1)
+ }
+ }
}
global.WIKI = WIKI
diff --git a/ux/src/components/PageTags.vue b/ux/src/components/PageTags.vue
index 3bcc77ca..733d1adf 100644
--- a/ux/src/components/PageTags.vue
+++ b/ux/src/components/PageTags.vue
@@ -1,21 +1,21 @@
.q-gutter-xs
- template(v-if='tags && tags.length > 0')
+ template(v-if='pageStore.tags && pageStore.tags.length > 0')
q-chip(
square
color='secondary'
text-color='white'
dense
clickable
- :removable='edit'
+ :removable='props.edit'
@remove='removeTag(tag)'
- v-for='tag of tags'
+ v-for='tag of pageStore.tags'
:key='`tag-` + tag'
)
q-icon.q-mr-xs(name='las la-tag', size='14px')
span.text-caption {{tag}}
q-chip(
- v-if='!edit && tags.length > 1'
+ v-if='!props.edit && pageStore.tags.length > 1'
square
color='secondary'
text-color='white'
@@ -24,36 +24,51 @@
)
q-icon(name='las la-tags', size='14px')
q-input.q-mt-md(
- v-if='edit'
+ v-if='props.edit'
outlined
- v-model='newTag'
+ v-model='state.newTag'
dense
placeholder='Add new tag...'
)
-
diff --git a/ux/src/components/SocialSharingMenu.vue b/ux/src/components/SocialSharingMenu.vue
index af9603a4..858f9843 100644
--- a/ux/src/components/SocialSharingMenu.vue
+++ b/ux/src/components/SocialSharingMenu.vue
@@ -11,125 +11,146 @@ q-menu(
q-item-section.items-center(avatar)
q-icon(color='grey', name='las la-clipboard', size='sm')
q-item-section.q-pr-md Copy URL
- q-item(clickable, tag='a', :href='`mailto:?subject=` + encodeURIComponent(title) + `&body=` + encodeURIComponent(urlFormatted) + `%0D%0A%0D%0A` + encodeURIComponent(description)', target='_blank')
+ q-item(clickable, tag='a', :href='`mailto:?subject=` + encodeURIComponent(props.title) + `&body=` + encodeURIComponent(urlFormatted) + `%0D%0A%0D%0A` + encodeURIComponent(props.description)', target='_blank')
q-item-section.items-center(avatar)
q-icon(color='grey', name='las la-envelope', size='sm')
q-item-section.q-pr-md Email
- q-item(clickable, @click='openSocialPop(`https://www.facebook.com/sharer/sharer.php?u=` + encodeURIComponent(urlFormatted) + `&title=` + encodeURIComponent(title) + `&description=` + encodeURIComponent(description))')
+ q-item(clickable, @click='openSocialPop(`https://www.facebook.com/sharer/sharer.php?u=` + encodeURIComponent(urlFormatted) + `&title=` + encodeURIComponent(props.title) + `&description=` + encodeURIComponent(props.description))')
q-item-section.items-center(avatar)
q-icon(color='grey', name='lab la-facebook', size='sm')
q-item-section.q-pr-md Facebook
- q-item(clickable, @click='openSocialPop(`https://www.linkedin.com/shareArticle?mini=true&url=` + encodeURIComponent(urlFormatted) + `&title=` + encodeURIComponent(title) + `&summary=` + encodeURIComponent(description))')
+ q-item(clickable, @click='openSocialPop(`https://www.linkedin.com/shareArticle?mini=true&url=` + encodeURIComponent(urlFormatted) + `&title=` + encodeURIComponent(props.title) + `&summary=` + encodeURIComponent(props.description))')
q-item-section.items-center(avatar)
q-icon(color='grey', name='lab la-linkedin', size='sm')
q-item-section.q-pr-md LinkedIn
- q-item(clickable, @click='openSocialPop(`https://www.reddit.com/submit?url=` + encodeURIComponent(urlFormatted) + `&title=` + encodeURIComponent(title))')
+ q-item(clickable, @click='openSocialPop(`https://www.reddit.com/submit?url=` + encodeURIComponent(urlFormatted) + `&title=` + encodeURIComponent(props.title))')
q-item-section.items-center(avatar)
q-icon(color='grey', name='lab la-reddit', size='sm')
q-item-section.q-pr-md Reddit
- q-item(clickable, @click='openSocialPop(`https://t.me/share/url?url=` + encodeURIComponent(urlFormatted) + `&text=` + encodeURIComponent(title))')
+ q-item(clickable, @click='openSocialPop(`https://t.me/share/url?url=` + encodeURIComponent(urlFormatted) + `&text=` + encodeURIComponent(props.title))')
q-item-section.items-center(avatar)
q-icon(color='grey', name='lab la-telegram', size='sm')
q-item-section.q-pr-md Telegram
- q-item(clickable, @click='openSocialPop(`https://twitter.com/intent/tweet?url=` + encodeURIComponent(urlFormatted) + `&text=` + encodeURIComponent(title))')
+ q-item(clickable, @click='openSocialPop(`https://twitter.com/intent/tweet?url=` + encodeURIComponent(urlFormatted) + `&text=` + encodeURIComponent(props.title))')
q-item-section.items-center(avatar)
q-icon(color='grey', name='lab la-twitter', size='sm')
q-item-section.q-pr-md Twitter
- q-item(clickable, :href='`viber://forward?text=` + encodeURIComponent(urlFormatted) + ` ` + encodeURIComponent(description)')
+ q-item(clickable, :href='`viber://forward?text=` + encodeURIComponent(urlFormatted) + ` ` + encodeURIComponent(props.description)')
q-item-section.items-center(avatar)
q-icon(color='grey', name='lab la-viber', size='sm')
q-item-section.q-pr-md Viber
- q-item(clickable, @click='openSocialPop(`http://service.weibo.com/share/share.php?url=` + encodeURIComponent(urlFormatted) + `&title=` + encodeURIComponent(title))')
+ q-item(clickable, @click='openSocialPop(`http://service.weibo.com/share/share.php?url=` + encodeURIComponent(urlFormatted) + `&title=` + encodeURIComponent(props.title))')
q-item-section.items-center(avatar)
q-icon(color='grey', name='lab la-weibo', size='sm')
q-item-section.q-pr-md Weibo
- q-item(clickable, @click='openSocialPop(`https://api.whatsapp.com/send?text=` + encodeURIComponent(title) + `%0D%0A` + encodeURIComponent(urlFormatted))')
+ q-item(clickable, @click='openSocialPop(`https://api.whatsapp.com/send?text=` + encodeURIComponent(props.title) + `%0D%0A` + encodeURIComponent(urlFormatted))')
q-item-section.items-center(avatar)
q-icon(color='grey', name='lab la-whatsapp', size='sm')
q-item-section.q-pr-md Whatsapp
-
diff --git a/ux/src/i18n/locales/en.json b/ux/src/i18n/locales/en.json
index fbbe1fa6..1114ccf8 100644
--- a/ux/src/i18n/locales/en.json
+++ b/ux/src/i18n/locales/en.json
@@ -1540,5 +1540,9 @@
"admin.instances.lastSeen": "Last Seen",
"admin.instances.firstSeen": "First Seen",
"admin.instances.activeListeners": "Active Listeners",
- "admin.instances.activeConnections": "Active Connections"
+ "admin.instances.activeConnections": "Active Connections",
+ "admin.scheduler.cancelJob": "Cancel Job",
+ "admin.scheduler.cancelJobSuccess": "Job cancelled successfully.",
+ "admin.scheduler.retryJob": "Retry Job",
+ "admin.scheduler.retryJobSuccess": "Job has been rescheduled and will execute shortly."
}
diff --git a/ux/src/layouts/AdminLayout.vue b/ux/src/layouts/AdminLayout.vue
index 2129e9ff..8974e191 100644
--- a/ux/src/layouts/AdminLayout.vue
+++ b/ux/src/layouts/AdminLayout.vue
@@ -3,7 +3,7 @@ q-layout.admin(view='hHh Lpr lff')
q-header.bg-black.text-white
.row.no-wrap
q-toolbar(style='height: 64px;', dark)
- q-btn(dense, flat, href='/')
+ q-btn(dense, flat, to='/')
q-avatar(size='34px', square)
img(src='/_assets/logo-wikijs.svg')
q-toolbar-title.text-h6 Wiki.js
@@ -102,10 +102,6 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-tree-structure.svg')
q-item-section {{ t('admin.navigation.title') }}
- q-item(:to='`/_admin/` + adminStore.currentSiteId + `/rendering`', v-ripple, active-class='bg-primary text-white', disabled)
- q-item-section(avatar)
- q-icon(name='img:/_assets/icons/fluent-rich-text-converter.svg')
- q-item-section {{ t('admin.rendering.title') }}
q-item(:to='`/_admin/` + adminStore.currentSiteId + `/storage`', v-ripple, active-class='bg-primary text-white')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-ssd.svg')
@@ -156,6 +152,10 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section {{ t('admin.mail.title') }}
q-item-section(side)
status-light(:color='adminStore.info.isMailConfigured ? `positive` : `warning`')
+ q-item(to='/_admin/rendering', v-ripple, active-class='bg-primary text-white', disabled)
+ q-item-section(avatar)
+ q-icon(name='img:/_assets/icons/fluent-rich-text-converter.svg')
+ q-item-section {{ t('admin.rendering.title') }}
q-item(to='/_admin/scheduler', v-ripple, active-class='bg-primary text-white')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-bot.svg')
diff --git a/ux/src/layouts/MainLayout.vue b/ux/src/layouts/MainLayout.vue
index 25f7f5dc..d6824d96 100644
--- a/ux/src/layouts/MainLayout.vue
+++ b/ux/src/layouts/MainLayout.vue
@@ -2,7 +2,7 @@
q-layout(view='hHh Lpr lff')
header-nav
q-drawer.bg-sidebar(
- v-model='showSideNav'
+ v-model='siteStore.showSideNav'
show-if-above
:width='255'
)
@@ -81,46 +81,64 @@ q-layout(view='hHh Lpr lff')
span(style='font-size: 11px;') © Cyberdyne Systems Corp. 2020 | Powered by #[strong Wiki.js]
-