refactor: admin editors + flags page to comp api, server cleanup

pull/5698/head
NGPixel 2 years ago
parent 7b060bec5f
commit 027b1614ff
No known key found for this signature in database
GPG Key ID: 8FDA2F1757F60D63

@ -0,0 +1,16 @@
{
"folders": [
{
"path": "ux"
},
{
"path": "server"
}
],
"settings": {
"i18n-ally.localesPaths": [
"src/i18n",
"src/i18n/locales"
]
}
}

@ -34,32 +34,32 @@
"node": ">=16.0"
},
"dependencies": {
"@azure/storage-blob": "12.2.1",
"@azure/storage-blob": "12.9.0",
"@exlinc/keycloak-passport": "1.0.2",
"@graphql-tools/schema": "8.3.7",
"@graphql-tools/utils": "8.6.6",
"@joplin/turndown-plugin-gfm": "1.0.43",
"@root/csr": "0.8.1",
"@root/keypairs": "0.10.3",
"@root/pem": "1.0.4",
"acme": "3.0.3",
"akismet-api": "5.2.1",
"algoliasearch": "4.5.1",
"akismet-api": "5.3.0",
"apollo-fetch": "0.7.0",
"apollo-server": "2.25.2",
"apollo-server-express": "2.25.2",
"apollo-server": "3.6.7",
"apollo-server-express": "3.6.7",
"auto-load": "3.0.4",
"aws-sdk": "2.1111.0",
"azure-search-client": "3.1.5",
"bcryptjs-then": "1.0.1",
"bluebird": "3.7.2",
"body-parser": "1.19.1",
"chalk": "4.1.0",
"body-parser": "1.20.0",
"chalk": "4.1.2",
"cheerio": "1.0.0-rc.5",
"chokidar": "3.5.3",
"chromium-pickle-js": "0.2.0",
"clean-css": "4.2.3",
"command-exists": "1.2.9",
"compression": "1.7.4",
"connect-session-knex": "2.0.0",
"connect-session-knex": "2.1.1",
"cookie-parser": "1.4.6",
"cors": "2.8.5",
"cuint": "0.2.2",
@ -69,8 +69,6 @@
"diff2html": "3.1.14",
"dompurify": "2.2.7",
"dotize": "0.3.0",
"elasticsearch6": "npm:@elastic/elasticsearch@6",
"elasticsearch7": "npm:@elastic/elasticsearch@7",
"emoji-regex": "9.2.2",
"eventemitter2": "6.4.5",
"express": "4.17.3",
@ -80,11 +78,11 @@
"filesize": "6.1.0",
"fs-extra": "9.0.1",
"getos": "3.2.1",
"graphql": "15.3.0",
"graphql": "16.3.0",
"graphql-list-fields": "2.0.2",
"graphql-rate-limit-directive": "1.2.1",
"graphql-subscriptions": "1.1.0",
"graphql-tools": "7.0.0",
"graphql-rate-limit-directive": "2.0.2",
"graphql-tools": "8.2.5",
"graphql-upload": "13.0.0",
"he": "1.2.0",
"highlight.js": "10.3.1",
"i18next": "19.8.3",
@ -93,14 +91,14 @@
"image-size": "0.9.2",
"js-base64": "3.7.2",
"js-binary": "1.2.0",
"js-yaml": "3.14.0",
"js-yaml": "4.1.0",
"jsdom": "16.4.0",
"jsonwebtoken": "8.5.1",
"katex": "0.12.0",
"klaw": "3.0.0",
"klaw": "4.0.1",
"knex": "1.0.5",
"lodash": "4.17.21",
"luxon": "1.25.0",
"luxon": "2.3.1",
"markdown-it": "11.0.1",
"markdown-it-abbr": "1.0.4",
"markdown-it-attrs": "3.0.3",
@ -116,15 +114,15 @@
"markdown-it-sup": "1.0.0",
"markdown-it-task-lists": "2.1.1",
"mathjax": "3.1.2",
"mime-types": "2.1.34",
"moment": "2.29.1",
"mime-types": "2.1.35",
"moment": "2.29.2",
"moment-timezone": "0.5.31",
"ms": "2.1.3",
"multer": "1.4.4",
"nanoid": "3.2.0",
"node-2fa": "1.1.2",
"node-cache": "5.1.2",
"nodemailer": "6.7.2",
"nodemailer": "6.7.3",
"objection": "3.0.1",
"passport": "0.4.1",
"passport-auth0": "1.4.2",
@ -155,7 +153,7 @@
"pug": "3.0.2",
"punycode": "2.1.1",
"qr-image": "3.2.0",
"raven": "2.6.4",
"rate-limiter-flexible": "2.3.6",
"remove-markdown": "0.3.0",
"request": "2.88.2",
"request-promise": "4.2.6",
@ -165,18 +163,15 @@
"semver": "7.3.6",
"serve-favicon": "2.5.0",
"simple-git": "2.21.0",
"solr-node": "1.2.1",
"ssh2": "1.5.0",
"ssh2": "1.9.0",
"ssh2-promise": "1.0.2",
"striptags": "3.2.0",
"subscriptions-transport-ws": "0.9.18",
"tar-fs": "2.1.1",
"turndown": "7.1.1",
"twemoji": "13.1.0",
"uslug": "1.0.4",
"uuid": "8.3.2",
"validate.js": "0.13.1",
"winston": "3.7.2",
"xss": "1.0.11",
"yargs": "16.1.0"
},
@ -219,10 +214,10 @@
"babel-plugin-transform-imports": "2.0.0",
"cache-loader": "4.1.0",
"canvas-confetti": "1.3.1",
"cash-dom": "8.1.0",
"cash-dom": "8.1.1",
"chart.js": "2.9.4",
"clean-webpack-plugin": "3.0.0",
"clipboard": "2.0.8",
"clipboard": "2.0.10",
"codemirror": "5.58.2",
"copy-webpack-plugin": "6.2.1",
"core-js": "3.6.5",
@ -321,10 +316,6 @@
"xterm": "4.9.0",
"zxcvbn": "4.4.2"
},
"resolutions": {
"apollo-server-express/**/graphql-tools": "4.0.8",
"graphql": "15.3.0"
},
"browserslist": [
"> 1%",
"last 2 major versions",

@ -32,12 +32,12 @@ module.exports = {
let appdata = {}
try {
appconfig = yaml.safeLoad(
appconfig = yaml.load(
cfgHelper.parseConfigValue(
fs.readFileSync(confPaths.config, 'utf8')
)
)
appdata = yaml.safeLoad(fs.readFileSync(confPaths.data, 'utf8'))
appdata = yaml.load(fs.readFileSync(confPaths.data, 'utf8'))
appdata.regex = require(confPaths.dataRegex)
console.info(chalk.green.bold(`OK`))
} catch (err) {

@ -128,6 +128,8 @@ module.exports = {
},
// -> Migrate DB Schemas
async syncSchemas () {
WIKI.logger.info('Ensuring DB schema exists...')
await self.knex.raw(`CREATE SCHEMA IF NOT EXISTS ${WIKI.config.db.schemas.wiki}`)
WIKI.logger.info('Ensuring DB migrations have been applied...')
return self.knex.migrate.latest({
tableName: 'migrations',

@ -52,14 +52,9 @@ module.exports = {
*/
async bootMaster() {
try {
if (WIKI.config.setup) {
WIKI.logger.info('Starting setup wizard...')
require('../setup')()
} else {
await this.preBootMaster()
await require('../master')()
this.postBootMaster()
}
await this.preBootMaster()
await require('../master')()
this.postBootMaster()
} catch (err) {
WIKI.logger.error(err)
process.exit(1)
@ -73,16 +68,13 @@ module.exports = {
await WIKI.models.authentication.refreshStrategiesFromDisk()
await WIKI.models.commentProviders.refreshProvidersFromDisk()
await WIKI.models.editors.refreshEditorsFromDisk()
await WIKI.models.loggers.refreshLoggersFromDisk()
await WIKI.models.renderers.refreshRenderersFromDisk()
await WIKI.models.searchEngines.refreshSearchEnginesFromDisk()
await WIKI.models.storage.refreshTargetsFromDisk()
await WIKI.extensions.init()
await WIKI.auth.activateStrategies()
await WIKI.models.commentProviders.initProvider()
await WIKI.models.searchEngines.initEngine()
await WIKI.models.storage.initTargets()
WIKI.scheduler.start()

@ -1,44 +1,53 @@
// const _ = require('lodash')
const winston = require('winston')
const chalk = require('chalk')
const EventEmitter = require('events')
/* global WIKI */
module.exports = {
loggers: {},
init(uid) {
const loggerFormats = [
winston.format.label({ label: uid }),
winston.format.timestamp()
]
if (WIKI.config.logFormat === 'json') {
loggerFormats.push(winston.format.json())
} else {
loggerFormats.push(winston.format.colorize())
loggerFormats.push(winston.format.printf(info => `${info.timestamp} [${info.label}] ${info.level}: ${info.message}`))
}
const logger = winston.createLogger({
level: WIKI.config.logLevel,
format: winston.format.combine(...loggerFormats)
})
const LEVELS = ['error', 'warn', 'info', 'debug']
const LEVELSIGNORED = ['verbose', 'silly']
const LEVELCOLORS = {
error: 'red',
warn: 'yellow',
info: 'green',
debug: 'cyan'
}
class Logger extends EventEmitter {}
const primaryLogger = new Logger()
let ignoreNextLevels = false
// Init Console (default)
LEVELS.forEach(lvl => {
primaryLogger[lvl] = (...args) => {
primaryLogger.emit(lvl, ...args)
}
logger.add(new winston.transports.Console({
level: WIKI.config.logLevel,
prettyPrint: true,
colorize: true,
silent: false,
timestamp: true
}))
if (!ignoreNextLevels) {
primaryLogger.on(lvl, (msg) => {
if (WIKI.config.logFormat === 'json') {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
instance: WIKI.INSTANCE_ID,
level: lvl,
message: msg
}))
} else {
console.log(chalk`${new Date().toISOString()} {dim [${WIKI.INSTANCE_ID}]} {${LEVELCOLORS[lvl]}.bold ${lvl}}: ${msg}`)
}
})
}
if (lvl === WIKI.config.logLevel) {
ignoreNextLevels = true
}
})
// _.forOwn(_.omitBy(WIKI.config.logging.loggers, s => s.enabled === false), (loggerConfig, loggerKey) => {
// let loggerModule = require(`../modules/logging/${loggerKey}`)
// loggerModule.init(logger, loggerConfig)
// this.loggers[logger.key] = loggerModule
// })
LEVELSIGNORED.forEach(lvl => {
primaryLogger[lvl] = () => {}
})
return logger
module.exports = {
loggers: {},
init () {
return primaryLogger
}
}

@ -21,7 +21,6 @@ module.exports = {
async startHTTP () {
WIKI.logger.info(`HTTP Server on port: [ ${WIKI.config.port} ]`)
this.servers.http = http.createServer(WIKI.app)
this.servers.graph.installSubscriptionHandlers(this.servers.http)
this.servers.http.listen(WIKI.config.port, WIKI.config.bindIP)
this.servers.http.on('error', (error) => {
@ -83,7 +82,6 @@ module.exports = {
return process.exit(1)
}
this.servers.https = https.createServer(tlsOpts, WIKI.app)
this.servers.graph.installSubscriptionHandlers(this.servers.https)
this.servers.https.listen(WIKI.config.ssl.port, WIKI.config.bindIP)
this.servers.https.on('error', (error) => {
@ -121,15 +119,15 @@ module.exports = {
async startGraphQL () {
const graphqlSchema = require('../graph')
this.servers.graph = new ApolloServer({
...graphqlSchema,
schema: graphqlSchema,
uploads: false,
context: ({ req, res }) => ({ req, res }),
subscriptions: {
onConnect: (connectionParams, webSocket) => {
},
path: '/graphql-subscriptions'
}
plugins: [
// ApolloServerPluginDrainHttpServer({ httpServer: this.servers.http })
// ...(this.servers.https && ApolloServerPluginDrainHttpServer({ httpServer: this.servers.https }))
]
})
await this.servers.graph.start()
this.servers.graph.applyMiddleware({ app: WIKI.app, cors: false })
},
/**

@ -327,12 +327,6 @@ exports.up = async knex => {
// -> SYSTEM CONFIG
await knex('settings').insert([
{
key: 'update',
value: {
locales: true
}
},
{
key: 'mail',
value: {
@ -372,6 +366,18 @@ exports.up = async knex => {
uploadMaxFiles: 20,
uploadScanSVG: true
}
},
{
key: 'system',
value: {
sessionSecret: crypto.randomBytes(32).toString('hex')
}
},
{
key: 'update',
value: {
locales: true
}
}
])

@ -1,26 +1,22 @@
const _ = require('lodash')
const fs = require('fs')
// const gqlTools = require('graphql-tools')
const path = require('path')
const autoload = require('auto-load')
const PubSub = require('graphql-subscriptions').PubSub
const { LEVEL, MESSAGE } = require('triple-beam')
const Transport = require('winston-transport')
const { createRateLimitTypeDef } = require('graphql-rate-limit-directive')
// const { GraphQLUpload } = require('graphql-upload')
const { makeExecutableSchema } = require('@graphql-tools/schema')
const { rateLimitDirective } = require('graphql-rate-limit-directive')
const { GraphQLUpload } = require('graphql-upload')
const { rateLimitDirectiveTypeDefs, rateLimitDirectiveTransformer } = rateLimitDirective()
/* global WIKI */
WIKI.logger.info(`Loading GraphQL Schema...`)
// Init Subscription PubSub
WIKI.GQLEmitter = new PubSub()
// Schemas
let typeDefs = [createRateLimitTypeDef()]
let schemas = fs.readdirSync(path.join(WIKI.SERVERPATH, 'graph/schemas'))
const typeDefs = [
rateLimitDirectiveTypeDefs
]
const schemas = fs.readdirSync(path.join(WIKI.SERVERPATH, 'graph/schemas'))
schemas.forEach(schema => {
typeDefs.push(fs.readFileSync(path.join(WIKI.SERVERPATH, `graph/schemas/${schema}`), 'utf8'))
})
@ -28,47 +24,22 @@ schemas.forEach(schema => {
// Resolvers
let resolvers = {
// Upload: GraphQLUpload
Upload: GraphQLUpload
}
const resolversObj = _.values(autoload(path.join(WIKI.SERVERPATH, 'graph/resolvers')))
resolversObj.forEach(resolver => {
_.merge(resolvers, resolver)
})
// Directives
let schemaDirectives = {
...autoload(path.join(WIKI.SERVERPATH, 'graph/directives'))
}
// Live Trail Logger (admin)
class LiveTrailLogger extends Transport {
constructor(opts) {
super(opts)
// Make executable schema
this.name = 'liveTrailLogger'
this.level = 'debug'
}
log (info, callback = () => {}) {
WIKI.GQLEmitter.publish('livetrail', {
loggingLiveTrail: {
timestamp: new Date(),
level: info[LEVEL],
output: info[MESSAGE]
}
})
callback(null, true)
}
}
let schema = makeExecutableSchema({
typeDefs,
resolvers
})
WIKI.logger.add(new LiveTrailLogger({}))
schema = rateLimitDirectiveTransformer(schema)
WIKI.logger.info(`GraphQL Schema: [ OK ]`)
module.exports = {
typeDefs,
resolvers,
schemaDirectives
}
module.exports = schema

@ -1,64 +0,0 @@
const _ = require('lodash')
const graphHelper = require('../../helpers/graph')
/* global WIKI */
module.exports = {
Query: {
async logging() { return {} }
},
Mutation: {
async logging() { return {} }
},
Subscription: {
loggingLiveTrail: {
subscribe: () => WIKI.GQLEmitter.asyncIterator('livetrail')
}
},
LoggingQuery: {
async loggers(obj, args, context, info) {
let loggers = await WIKI.models.loggers.getLoggers()
loggers = loggers.map(logger => {
const loggerInfo = _.find(WIKI.data.loggers, ['key', logger.key]) || {}
return {
...loggerInfo,
...logger,
config: _.sortBy(_.transform(logger.config, (res, value, key) => {
const configData = _.get(loggerInfo.props, key, {})
res.push({
key,
value: JSON.stringify({
...configData,
value
})
})
}, []), 'key')
}
})
// if (args.filter) { loggers = graphHelper.filter(loggers, args.filter) }
if (args.orderBy) { loggers = _.sortBy(loggers, [args.orderBy]) }
return loggers
}
},
LoggingMutation: {
async updateLoggers(obj, args, context) {
try {
for (let logger of args.loggers) {
await WIKI.models.loggers.query().patch({
isEnabled: logger.isEnabled,
level: logger.level,
config: _.reduce(logger.config, (result, value, key) => {
_.set(result, `${value.key}`, value.value)
return result
}, {})
}).where('key', logger.key)
}
return {
responseResult: graphHelper.generateSuccess('Loggers updated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
}
}
}
}

@ -2,5 +2,5 @@
scalar Date
scalar JSON
# scalar Upload
scalar Upload
scalar UUID

@ -6,6 +6,12 @@
const path = require('path')
const { nanoid } = require('nanoid')
const { DateTime } = require('luxon')
const semver = require('semver')
if (!semver.satisfies(process.version, '>=16')) {
console.error('ERROR: Node.js 16.x or later required!')
process.exit(1)
}
let WIKI = {
IS_DEBUG: process.env.NODE_ENV === 'development',
@ -26,7 +32,7 @@ WIKI.configSvc.init()
// Init Logger
// ----------------------------------------
WIKI.logger = require('./core/logger').init('MASTER')
WIKI.logger = require('./core/logger').init()
// ----------------------------------------
// Start Kernel

@ -77,7 +77,7 @@ module.exports = async () => {
app.use(cookieParser())
app.use(session({
secret: WIKI.config.sessionSecret,
secret: WIKI.config.system.sessionSecret,
resave: false,
saveUninitialized: false,
store: new KnexSessionStore({

@ -17,11 +17,12 @@ module.exports = class Analytics extends Model {
static get jsonSchema () {
return {
type: 'object',
required: ['key', 'isEnabled'],
required: ['module', 'isEnabled'],
properties: {
key: {type: 'string'},
isEnabled: {type: 'boolean'}
id: { type: 'string' },
module: { type: 'string' },
isEnabled: { type: 'boolean', default: false }
}
}
}
@ -32,65 +33,27 @@ module.exports = class Analytics extends Model {
static async getProviders(isEnabled) {
const providers = await WIKI.models.analytics.query().where(_.isBoolean(isEnabled) ? { isEnabled } : {})
return _.sortBy(providers, ['key'])
return _.sortBy(providers, ['module'])
}
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) {
WIKI.data.analytics = []
for (const dir of analyticsDirs) {
const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/analytics', dir, 'definition.yml'), 'utf8')
diskProviders.push(yaml.safeLoad(def))
const defParsed = yaml.load(def)
defParsed.key = dir
defParsed.props = commonHelper.parseModuleProps(defParsed.props)
WIKI.data.analytics.push(defParsed)
WIKI.logger.debug(`Loaded analytics module definition ${dir}: [ OK ]`)
}
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 ]`)
}
WIKI.logger.info(`Loaded ${WIKI.data.analytics.length} analytics module definitions: [ OK ]`)
} catch (err) {
WIKI.logger.error(`Failed to scan or load new analytics providers: [ FAILED ]`)
WIKI.logger.error(err)
if (trx) {
trx.rollback()
}
}
}

@ -1,113 +0,0 @@
const Model = require('objection').Model
const path = require('path')
const fs = require('fs-extra')
const _ = require('lodash')
const yaml = require('js-yaml')
const commonHelper = require('../helpers/common')
/* global WIKI */
/**
* Logger model
*/
module.exports = class Logger extends Model {
static get tableName() { return 'loggers' }
static get idColumn() { return 'key' }
static get jsonSchema () {
return {
type: 'object',
required: ['key', 'isEnabled'],
properties: {
key: {type: 'string'},
isEnabled: {type: 'boolean'},
level: {type: 'string'}
}
}
}
static get jsonAttributes() {
return ['config']
}
static async getLoggers() {
return WIKI.models.loggers.query()
}
static async refreshLoggersFromDisk() {
let trx
try {
const dbLoggers = await WIKI.models.loggers.query()
// -> Fetch definitions from disk
const loggersDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/logging'))
let diskLoggers = []
for (let dir of loggersDirs) {
const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/logging', dir, 'definition.yml'), 'utf8')
diskLoggers.push(yaml.safeLoad(def))
}
WIKI.data.loggers = diskLoggers.map(logger => ({
...logger,
props: commonHelper.parseModuleProps(logger.props)
}))
// -> Insert new loggers
let newLoggers = []
for (let logger of WIKI.data.loggers) {
if (!_.some(dbLoggers, ['key', logger.key])) {
newLoggers.push({
key: logger.key,
isEnabled: (logger.key === 'console'),
level: logger.defaultLevel,
config: _.transform(logger.props, (result, value, key) => {
_.set(result, key, value.default)
return result
}, {})
})
} else {
const loggerConfig = _.get(_.find(dbLoggers, ['key', logger.key]), 'config', {})
await WIKI.models.loggers.query().patch({
config: _.transform(logger.props, (result, value, key) => {
if (!_.has(result, key)) {
_.set(result, key, value.default)
}
return result
}, loggerConfig)
}).where('key', logger.key)
}
}
if (newLoggers.length > 0) {
trx = await WIKI.models.Objection.transaction.start(WIKI.models.knex)
for (let logger of newLoggers) {
await WIKI.models.loggers.query(trx).insert(logger)
}
await trx.commit()
WIKI.logger.info(`Loaded ${newLoggers.length} new loggers: [ OK ]`)
} else {
WIKI.logger.info(`No new loggers found: [ SKIPPED ]`)
}
} catch (err) {
WIKI.logger.error(`Failed to scan or load new loggers: [ FAILED ]`)
WIKI.logger.error(err)
if (trx) {
trx.rollback()
}
}
}
static async pageEvent({ event, page }) {
const loggers = await WIKI.models.storage.query().where('isEnabled', true)
if (loggers && loggers.length > 0) {
_.forEach(loggers, logger => {
WIKI.queue.job.syncStorage.add({
event,
logger,
page
}, {
removeOnComplete: true
})
})
}
}
}

@ -1,125 +0,0 @@
const Model = require('objection').Model
const path = require('path')
const fs = require('fs-extra')
const _ = require('lodash')
const yaml = require('js-yaml')
const commonHelper = require('../helpers/common')
/* global WIKI */
/**
* SearchEngine model
*/
module.exports = class SearchEngine extends Model {
static get tableName() { return 'searchEngines' }
static get idColumn() { return 'key' }
static get jsonSchema () {
return {
type: 'object',
required: ['key', 'isEnabled'],
properties: {
key: {type: 'string'},
isEnabled: {type: 'boolean'},
level: {type: 'string'}
}
}
}
static get jsonAttributes() {
return ['config']
}
static async getSearchEngines() {
return WIKI.models.searchEngines.query()
}
static async refreshSearchEnginesFromDisk() {
let trx
try {
const dbSearchEngines = await WIKI.models.searchEngines.query()
// -> Fetch definitions from disk
const searchEnginesDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/search'))
let diskSearchEngines = []
for (let dir of searchEnginesDirs) {
const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/search', dir, 'definition.yml'), 'utf8')
diskSearchEngines.push(yaml.safeLoad(def))
}
WIKI.data.searchEngines = diskSearchEngines.map(searchEngine => ({
...searchEngine,
props: commonHelper.parseModuleProps(searchEngine.props)
}))
// -> Insert new searchEngines
let newSearchEngines = []
for (let searchEngine of WIKI.data.searchEngines) {
if (!_.some(dbSearchEngines, ['key', searchEngine.key])) {
newSearchEngines.push({
key: searchEngine.key,
isEnabled: false,
config: _.transform(searchEngine.props, (result, value, key) => {
_.set(result, key, value.default)
return result
}, {})
})
} else {
const searchEngineConfig = _.get(_.find(dbSearchEngines, ['key', searchEngine.key]), 'config', {})
await WIKI.models.searchEngines.query().patch({
config: _.transform(searchEngine.props, (result, value, key) => {
if (!_.has(result, key)) {
_.set(result, key, value.default)
}
return result
}, searchEngineConfig)
}).where('key', searchEngine.key)
}
}
if (newSearchEngines.length > 0) {
trx = await WIKI.models.Objection.transaction.start(WIKI.models.knex)
for (let searchEngine of newSearchEngines) {
await WIKI.models.searchEngines.query(trx).insert(searchEngine)
}
await trx.commit()
WIKI.logger.info(`Loaded ${newSearchEngines.length} new search engines: [ OK ]`)
} else {
WIKI.logger.info(`No new search engines found: [ SKIPPED ]`)
}
} catch (err) {
WIKI.logger.error(`Failed to scan or load new search engines: [ FAILED ]`)
WIKI.logger.error(err)
if (trx) {
trx.rollback()
}
}
}
static async initEngine({ activate = false } = {}) {
const searchEngine = await WIKI.models.searchEngines.query().findOne('isEnabled', true)
if (searchEngine) {
WIKI.data.searchEngine = require(`../modules/search/${searchEngine.key}/engine`)
WIKI.data.searchEngine.key = searchEngine.key
WIKI.data.searchEngine.config = searchEngine.config
if (activate) {
try {
await WIKI.data.searchEngine.activate()
} catch (err) {
// -> Revert to basic engine
if (err instanceof WIKI.Error.SearchActivationFailed) {
await WIKI.models.searchEngines.query().patch({ isEnabled: false }).where('key', searchEngine.key)
await WIKI.models.searchEngines.query().patch({ isEnabled: true }).where('key', 'db')
await WIKI.models.searchEngines.initEngine()
}
throw err
}
}
try {
await WIKI.data.searchEngine.init()
} catch (err) {
WIKI.logger.warn(err)
}
}
}
}

@ -30,6 +30,7 @@ module.exports = configure(function (/* ctx */) {
// https://v2.quasar.dev/quasar-cli/boot-files
boot: [
'apollo',
'components',
'i18n'
],

@ -0,0 +1,7 @@
import { boot } from 'quasar/wrappers'
import BlueprintIcon from '../components/BlueprintIcon.vue'
export default boot(({ app }) => {
app.component('BlueprintIcon', BlueprintIcon)
})

@ -98,6 +98,17 @@ body::-webkit-scrollbar-thumb {
}
}
// ------------------------------------------------------------------
// ICONS SIZE FIX
// ------------------------------------------------------------------
.q-btn .q-icon {
&.fa-solid,
&.fa-regular {
font-size: 1.3em;
}
}
.q-select__dropdown-icon {
font-size: 16px;
}

@ -9,6 +9,11 @@ q-layout.admin(view='hHh Lpr lff')
q-toolbar-title.text-h6.font-poppins Wiki.js
q-toolbar.gt-sm.justify-center(style='height: 64px;', dark)
.text-overline.text-uppercase.text-grey {{t('admin.adminArea')}}
q-badge.q-ml-sm(
label='v3 Preview'
color='pink'
outline
)
q-toolbar(style='height: 64px;', dark)
q-space
q-spinner-tail(

@ -4,8 +4,8 @@ q-page.admin-flags
.col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-cashbook.svg')
.col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.editors.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.editors.subtitle') }}
.text-h5.text-primary.animated.fadeInLeft {{ t('admin.editors.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.editors.subtitle') }}
.col-auto
q-btn.q-mr-sm.acrylic-btn(
icon='las la-question-circle'
@ -16,7 +16,7 @@ q-page.admin-flags
type='a'
)
q-btn.q-mr-sm.acrylic-btn(
icon='las la-redo-alt'
icon='fa-solid fa-rotate'
flat
color='secondary'
:loading='loading > 0'
@ -24,8 +24,8 @@ q-page.admin-flags
)
q-btn(
unelevated
icon='mdi-check'
:label='$t(`common.actions.apply`)'
icon='fa-solid fa-check'
:label='t(`common.actions.apply`)'
color='secondary'
@click='save'
:disabled='loading > 0'
@ -37,16 +37,16 @@ q-page.admin-flags
q-item(v-for='editor of editors', :key='editor.id')
blueprint-icon(:icon='editor.icon')
q-item-section
q-item-label: strong {{$t(`admin.editors.` + editor.id + `Name`)}}
q-item-label: strong {{t(`admin.editors.` + editor.id + `Name`)}}
q-item-label.flex.items-center(caption)
span {{$t(`admin.editors.` + editor.id + `Description`)}}
span {{t(`admin.editors.` + editor.id + `Description`)}}
template(v-if='editor.config')
q-item-section(
side
)
q-btn(
icon='las la-cog'
:label='$t(`admin.editors.configuration`)'
:label='t(`admin.editors.configuration`)'
:color='$q.dark.isActive ? `blue-grey-3` : `blue-grey-8`'
outline
no-caps
@ -59,69 +59,67 @@ q-page.admin-flags
:color='editor.isDisabled ? `grey` : `primary`'
checked-icon='las la-check'
unchecked-icon='las la-times'
:label='$t(`admin.sites.isActive`)'
:aria-label='$t(`admin.sites.isActive`)'
:label='t(`admin.sites.isActive`)'
:aria-label='t(`admin.sites.isActive`)'
:disabled='editor.isDisabled'
)
</template>
<script>
import { createMetaMixin } from 'quasar'
<script setup>
import { useMeta } from 'quasar'
import { useI18n } from 'vue-i18n'
import { defineAsyncComponent, onMounted, reactive, ref, watch } from 'vue'
export default {
mixins: [
createMetaMixin(function () {
return {
title: this.$t('admin.editors.title')
}
})
],
data () {
return {
loading: false,
editors: [
{
id: 'wysiwyg',
icon: 'google-presentation',
isActive: true
},
{
id: 'markdown',
icon: 'markdown',
config: {},
isActive: true
},
{
id: 'channel',
icon: 'chat',
isActive: true
},
{
id: 'blog',
icon: 'typewriter-with-paper',
isActive: true,
isDisabled: true
},
{
id: 'api',
icon: 'api',
isActive: true,
isDisabled: true
},
{
id: 'redirect',
icon: 'advance',
isActive: true
}
]
}
// STORES / ROUTERS / i18n
const { t } = useI18n()
// META
useMeta({
title: t('admin.editors.title')
})
const loading = ref(false)
const editors = reactive([
{
id: 'wysiwyg',
icon: 'google-presentation',
isActive: true
},
{
id: 'markdown',
icon: 'markdown',
config: {},
isActive: true
},
methods: {
async load () {},
save () {},
refresh () {}
{
id: 'channel',
icon: 'chat',
isActive: true
},
{
id: 'blog',
icon: 'typewriter-with-paper',
isActive: true,
isDisabled: true
},
{
id: 'api',
icon: 'api',
isActive: true,
isDisabled: true
},
{
id: 'redirect',
icon: 'advance',
isActive: true
}
}
])
const load = async () => {}
const save = () => {}
const refresh = () => {}
</script>
<style lang='scss'>

@ -4,8 +4,8 @@ q-page.admin-flags
.col-auto
img.admin-icon.animated.fadeInLeft(src='/_assets/icons/fluent-windsock.svg')
.col.q-pl-md
.text-h5.text-primary.animated.fadeInLeft {{ $t('admin.flags.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ $t('admin.flags.subtitle') }}
.text-h5.text-primary.animated.fadeInLeft {{ t('admin.flags.title') }}
.text-subtitle1.text-grey.animated.fadeInLeft.wait-p2s {{ t('admin.flags.subtitle') }}
.col-auto
q-btn.q-mr-sm.acrylic-btn(
icon='las la-question-circle'
@ -17,8 +17,8 @@ q-page.admin-flags
)
q-btn(
unelevated
icon='mdi-check'
:label='$t(`common.actions.apply`)'
icon='fa-solid fa-check'
:label='t(`common.actions.apply`)'
color='secondary'
@click='save'
:loading='loading'
@ -34,125 +34,129 @@ q-page.admin-flags
q-card-section.col-auto.q-pr-none
q-icon(name='las la-exclamation-triangle', size='sm')
q-card-section
span {{ $t('admin.flags.warn.label') }}
.text-caption.text-red-1 {{ $t('admin.flags.warn.hint') }}
span {{ t('admin.flags.warn.label') }}
.text-caption.text-red-1 {{ t('admin.flags.warn.hint') }}
q-item(tag='label', v-ripple)
blueprint-icon(icon='flag-filled')
q-item-section
q-item-label {{$t(`admin.flags.ldapdebug.label`)}}
q-item-label(caption) {{$t(`admin.flags.ldapdebug.hint`)}}
q-item-label {{t(`admin.flags.ldapdebug.label`)}}
q-item-label(caption) {{t(`admin.flags.ldapdebug.hint`)}}
q-item-section(avatar)
q-toggle(
v-model='flags.ldapdebug'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='$t(`admin.flags.ldapdebug.label`)'
:aria-label='t(`admin.flags.ldapdebug.label`)'
)
q-separator.q-my-sm(inset)
q-item(tag='label', v-ripple)
blueprint-icon(icon='flag-filled')
q-item-section
q-item-label {{$t(`admin.flags.sqllog.label`)}}
q-item-label(caption) {{$t(`admin.flags.sqllog.hint`)}}
q-item-label {{t(`admin.flags.sqllog.label`)}}
q-item-label(caption) {{t(`admin.flags.sqllog.hint`)}}
q-item-section(avatar)
q-toggle(
v-model='flags.sqllog'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='$t(`admin.flags.sqllog.label`)'
:aria-label='t(`admin.flags.sqllog.label`)'
)
q-card.shadow-1.q-py-sm.q-mt-md
q-item(tag='label', v-ripple)
blueprint-icon(icon='heart-outline')
q-item-section
q-item-label {{$t(`admin.flags.hidedonatebtn.label`)}}
q-item-label(caption) {{$t(`admin.flags.hidedonatebtn.hint`)}}
q-item-label {{t(`admin.flags.hidedonatebtn.label`)}}
q-item-label(caption) {{t(`admin.flags.hidedonatebtn.hint`)}}
q-item-section(avatar)
q-toggle(
v-model='flags.hidedonatebtn'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='$t(`admin.flags.hidedonatebtn.label`)'
:aria-label='t(`admin.flags.hidedonatebtn.label`)'
)
</template>
<script>
<script setup>
import gql from 'graphql-tag'
import { defineAsyncComponent, onMounted, reactive, ref, watch } from 'vue'
import _transform from 'lodash/transform'
import { createMetaMixin } from 'quasar'
import { useMeta } from 'quasar'
import { useI18n } from 'vue-i18n'
// STORES / ROUTERS / i18n
const { t } = useI18n()
// META
useMeta({
title: t('admin.flags.title')
})
const loading = ref(false)
const flags = reactive({
ldapdebug: false,
sqllog: false,
hidedonatebtn: false
})
const save = async () => {
export default {
mixins: [
createMetaMixin(function () {
return {
title: this.$t('admin.flags.title')
}
})
],
data () {
return {
loading: false,
flags: {
ldapdebug: false,
sqllog: false,
hidedonatebtn: false
}
}
},
methods: {
async save () {
try {
await this.$apollo.mutate({
mutation: gql`
mutation updateFlags (
$flags: [SystemFlagInput]!
) {
updateSystemFlags(
flags: $flags
) {
status {
succeeded
slug
message
}
}
}
`,
variables: {
flags: _transform(this.flags, (result, value, key) => {
result.push({ key, value })
}, [])
},
watchLoading (isLoading) {
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-flags-update')
}
})
this.$store.commit('showNotification', {
style: 'success',
message: 'Flags applied successfully.',
icon: 'check'
})
} catch (err) {
this.$store.commit('pushGraphError', err)
}
}
}
// apollo: {
// flags: {
// query: gql``,
// fetchPolicy: 'network-only',
// update: (data) => _transform(data.system.flags, (result, row) => {
// _set(result, row.key, row.value)
// }, {}),
// watchLoading (isLoading) {
// this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-flags-refresh')
// }
// }
// }
}
// methods: {
// async save () {
// try {
// await this.$apollo.mutate({
// mutation: gql`
// mutation updateFlags (
// $flags: [SystemFlagInput]!
// ) {
// updateSystemFlags(
// flags: $flags
// ) {
// status {
// succeeded
// slug
// message
// }
// }
// }
// `,
// variables: {
// flags: _transform(this.flags, (result, value, key) => {
// result.push({ key, value })
// }, [])
// },
// watchLoading (isLoading) {
// this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-flags-update')
// }
// })
// this.$store.commit('showNotification', {
// style: 'success',
// message: 'Flags applied successfully.',
// icon: 'check'
// })
// } catch (err) {
// this.$store.commit('pushGraphError', err)
// }
// }
// }
// apollo: {
// flags: {
// query: gql``,
// fetchPolicy: 'network-only',
// update: (data) => _transform(data.system.flags, (result, row) => {
// _set(result, row.key, row.value)
// }, {}),
// watchLoading (isLoading) {
// this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-flags-refresh')
// }
// }
// }
</script>
<style lang='scss'>

@ -28,11 +28,11 @@ const routes = [
component: () => import('../layouts/AdminLayout.vue'),
children: [
{ path: '', redirect: '/_admin/dashboard' },
{ path: 'dashboard', component: () => import('../pages/AdminDashboard.vue') }
{ path: 'dashboard', component: () => import('../pages/AdminDashboard.vue') },
// { path: 'sites', component: () => import('../pages/AdminSites.vue') },
// // -> Site
// { path: ':siteid/general', component: () => import('../pages/AdminGeneral.vue') },
// { path: ':siteid/editors', component: () => import('../pages/AdminEditors.vue') },
{ path: ':siteid/editors', component: () => import('../pages/AdminEditors.vue') },
// { path: ':siteid/locale', component: () => import('../pages/AdminLocale.vue') },
// { path: ':siteid/login', component: () => import('../pages/AdminLogin.vue') },
// { path: ':siteid/navigation', component: () => import('../pages/AdminNavigation.vue') },
@ -51,7 +51,7 @@ const routes = [
// { path: 'system', component: () => import('../pages/AdminSystem.vue') },
// { path: 'utilities', component: () => import('../pages/AdminUtilities.vue') },
// { path: 'webhooks', component: () => import('../pages/AdminWebhooks.vue') },
// { path: 'flags', component: () => import('../pages/AdminFlags.vue') }
{ path: 'flags', component: () => import('../pages/AdminFlags.vue') }
]
},
// {

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save