Commit c94e2d57 authored by NGPixel's avatar NGPixel

feat: config-manager component

parent c26fae2e
'use strict'
require('./scss/configure.scss')
require('./js/configure.js')
......@@ -52,6 +52,7 @@ import adminEditUserComponent from './pages/admin-edit-user.component.js'
import adminProfileComponent from './pages/admin-profile.component.js'
import adminSettingsComponent from './pages/admin-settings.component.js'
import adminThemeComponent from './pages/admin-theme.component.js'
import configManagerComponent from './components/config-manager.component.js'
import contentViewComponent from './pages/content-view.component.js'
import editorComponent from './components/editor.component.js'
import sourceViewComponent from './pages/source-view.component.js'
......@@ -94,6 +95,7 @@ Vue.component('adminSettings', adminSettingsComponent)
Vue.component('adminTheme', adminThemeComponent)
Vue.component('anchor', anchorComponent)
Vue.component('colorPicker', colorPickerComponent)
Vue.component('configManager', configManagerComponent)
Vue.component('contentView', contentViewComponent)
Vue.component('editor', editorComponent)
Vue.component('editorCodeblock', editorCodeblockComponent)
......
'use strict'
/* global siteConfig */
import VeeValidate from 'vee-validate'
import axios from 'axios'
Vue.use(VeeValidate, {
enableAutoClasses: true,
classNames: {
touched: 'is-touched', // the control has been blurred
untouched: 'is-untouched', // the control hasn't been blurred
valid: 'is-valid', // model is valid
invalid: 'is-invalid', // model is invalid
pristine: 'is-pristine', // control has not been interacted with
dirty: 'is-dirty' // control has been interacted with
}
})
export default {
name: 'configManager',
data() {
return {
loading: false,
state: 'welcome',
syscheck: {
ok: false,
error: '',
results: []
},
dbcheck: {
ok: false,
error: ''
},
gitcheck: {
ok: false,
error: ''
},
final: {
ok: false,
error: '',
results: []
},
conf: {
title: siteConfig.title || 'Wiki',
host: siteConfig.host || 'http://',
port: siteConfig.port || 80,
lang: siteConfig.lang || 'en',
public: (siteConfig.public === true),
db: siteConfig.db || 'mongodb://localhost:27017/wiki',
pathData: './data',
pathRepo: './repo',
gitUseRemote: (siteConfig.git !== false),
gitUrl: '',
gitBranch: 'master',
gitAuthType: 'ssh',
gitAuthSSHKey: '',
gitAuthUser: '',
gitAuthPass: '',
gitAuthSSL: true,
gitShowUserEmail: true,
gitServerEmail: '',
adminEmail: '',
adminPassword: '',
adminPasswordConfirm: ''
},
considerations: {
https: false,
port: false,
localhost: false
}
}
},
computed: {
currentProgress: function () {
let perc = '0%'
switch (this.state) {
case 'welcome':
perc = '0%'
break
case 'syscheck':
perc = (this.syscheck.ok) ? '15%' : '5%'
break
case 'general':
perc = '20%'
break
case 'considerations':
perc = '30%'
break
case 'db':
perc = '35%'
break
case 'dbcheck':
perc = (this.dbcheck.ok) ? '50%' : '40%'
break
case 'paths':
perc = '55%'
break
case 'git':
perc = '60%'
break
case 'gitcheck':
perc = (this.gitcheck.ok) ? '75%' : '65%'
break
case 'admin':
perc = '80%'
break
}
return perc
}
},
mounted: function () {
/* if (appconfig.paths) {
this.conf.pathData = appconfig.paths.data || './data'
this.conf.pathRepo = appconfig.paths.repo || './repo'
}
if (appconfig.git !== false && _.isPlainObject(appconfig.git)) {
this.conf.gitUrl = appconfig.git.url || ''
this.conf.gitBranch = appconfig.git.branch || 'master'
this.conf.gitShowUserEmail = (appconfig.git.showUserEmail !== false)
this.conf.gitServerEmail = appconfig.git.serverEmail || ''
if (_.isPlainObject(appconfig.git.auth)) {
this.conf.gitAuthType = appconfig.git.auth.type || 'ssh'
this.conf.gitAuthSSHKey = appconfig.git.auth.privateKey || ''
this.conf.gitAuthUser = appconfig.git.auth.username || ''
this.conf.gitAuthPass = appconfig.git.auth.password || ''
this.conf.gitAuthSSL = (appconfig.git.auth.sslVerify !== false)
}
} */
},
methods: {
proceedToWelcome: function (ev) {
this.state = 'welcome'
this.loading = false
},
proceedToSyscheck: function (ev) {
let self = this
this.state = 'syscheck'
this.loading = true
self.syscheck = {
ok: false,
error: '',
results: []
}
this.$helpers._.delay(() => {
axios.post('/syscheck').then(resp => {
if (resp.data.ok === true) {
self.syscheck.ok = true
self.syscheck.results = resp.data.results
} else {
self.syscheck.ok = false
self.syscheck.error = resp.data.error
}
self.loading = false
self.$nextTick()
}).catch(err => {
window.alert(err.message)
})
}, 1000)
},
proceedToGeneral: function (ev) {
let self = this
self.state = 'general'
self.loading = false
self.$nextTick(() => {
self.$validator.validateAll('general')
})
},
proceedToConsiderations: function (ev) {
this.considerations = {
https: !this.$helpers._.startsWith(this.conf.host, 'https'),
port: false, // TODO
localhost: this.$helpers._.includes(this.conf.host, 'localhost')
}
this.state = 'considerations'
this.loading = false
},
proceedToDb: function (ev) {
let self = this
self.state = 'db'
self.loading = false
self.$nextTick(() => {
self.$validator.validateAll('db')
})
},
proceedToDbcheck: function (ev) {
let self = this
this.state = 'dbcheck'
this.loading = true
self.dbcheck = {
ok: false,
error: ''
}
this.$helpers._.delay(() => {
axios.post('/dbcheck', {
db: self.conf.db
}).then(resp => {
if (resp.data.ok === true) {
self.dbcheck.ok = true
} else {
self.dbcheck.ok = false
self.dbcheck.error = resp.data.error
}
self.loading = false
self.$nextTick()
}).catch(err => {
window.alert(err.message)
})
}, 1000)
},
proceedToPaths: function (ev) {
let self = this
self.state = 'paths'
self.loading = false
self.$nextTick(() => {
self.$validator.validateAll('paths')
})
},
proceedToGit: function (ev) {
let self = this
self.state = 'git'
self.loading = false
self.$nextTick(() => {
self.$validator.validateAll('git')
})
},
proceedToGitCheck: function (ev) {
let self = this
this.state = 'gitcheck'
this.loading = true
self.gitcheck = {
ok: false,
results: [],
error: ''
}
this.$helpers._.delay(() => {
axios.post('/gitcheck', self.conf).then(resp => {
if (resp.data.ok === true) {
self.gitcheck.ok = true
self.gitcheck.results = resp.data.results
} else {
self.gitcheck.ok = false
self.gitcheck.error = resp.data.error
}
self.loading = false
self.$nextTick()
}).catch(err => {
window.alert(err.message)
})
}, 1000)
},
proceedToAdmin: function (ev) {
let self = this
self.state = 'admin'
self.loading = false
self.$nextTick(() => {
self.$validator.validateAll('admin')
})
},
proceedToFinal: function (ev) {
let self = this
self.state = 'final'
self.loading = true
self.final = {
ok: false,
error: '',
results: []
}
this.$helpers._.delay(() => {
axios.post('/finalize', self.conf).then(resp => {
if (resp.data.ok === true) {
self.final.ok = true
self.final.results = resp.data.results
} else {
self.final.ok = false
self.final.error = resp.data.error
}
self.loading = false
self.$nextTick()
}).catch(err => {
window.alert(err.message)
})
}, 1000)
},
finish: function (ev) {
let self = this
self.state = 'restart'
this.$helpers._.delay(() => {
axios.post('/restart', {}).then(resp => {
this.$helpers._.delay(() => {
window.location.assign(self.conf.host)
}, 30000)
}).catch(err => {
window.alert(err.message)
})
}, 1000)
}
}
}
......@@ -34,9 +34,9 @@
<script>
const videoRules = {
'youtube': new RegExp(/(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/, 'i'),
'vimeo': new RegExp(/vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^/]*)\/videos\/|album\/(?:\d+)\/video\/|)(\d+)(?:$|\/|\?)/, 'i'),
'dailymotion': new RegExp(/(?:dailymotion\.com(?:\/embed)?(?:\/video|\/hub)|dai\.ly)\/([0-9a-z]+)(?:[-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/, 'i')
'youtube': new RegExp('/(?:(?:youtu\\.be\\/|v\\/|vi\\/|u\\/\\w\\/|embed\\/)|(?:(?:watch)?\\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/', 'i'),
'vimeo': new RegExp('/vimeo.com\\/(?:channels\\/(?:\\w+\\/)?|groups\\/(?:[^/]*)\\/videos\\/|album\\/(?:\\d+)\\/video\\/|)(\\d+)(?:$|\\/|\\?)/', 'i'),
'dailymotion': new RegExp('/(?:dailymotion\\.com(?:\\/embed)?(?:\\/video|\\/hub)|dai\\.ly)\\/([0-9a-z]+)(?:[-_0-9a-zA-Z]+(?:#video=)?([a-z0-9]+)?)?/', 'i')
}
export default {
......
......@@ -15,6 +15,7 @@ $primary: 'indigo';
@import 'components/button';
@import 'components/collapsable-nav';
@import 'components/color-picker';
@import 'components/config-manager';
@import 'components/footer';
@import 'components/form';
@import 'components/grid';
......
......@@ -97,7 +97,11 @@
background-color: mc('grey', '300') !important;
color: mc('grey', '500') !important;
}
}
}
&.is-small {
height: 30px;
}
}
......
.config-manager {
.welcome {
text-align: center;
padding: 50px 0 15px 0;
color: mc('grey', '700');
h2 {
margin: 0;
}
}
i.icon-loader {
display: inline-block;
color: mc('indigo', '500')
}
i.icon-check {
color: mc('green', '500')
}
i.icon-square-cross {
color: mc('red', '500')
}
i.icon-warning-outline {
color: mc('orange', '500')
}
.tst-welcome-leave-active, .tst-welcome-enter-active {
transition: all .5s;
overflow-y: hidden;
}
.tst-welcome-leave, .tst-welcome-enter-to {
opacity: 1;
max-height: 200px;
}
.tst-welcome-leave-to, .tst-welcome-enter {
opacity: 0;
max-height: 0;
padding-top: 0;
}
.progress-bar {
width: 150px;
height: 10px;
background-color: mc('indigo', '50');
border:1px solid mc('indigo', '100');
border-radius: 3px;
position: absolute;
left: 15px;
top: 21px;
padding: 1px;
> div {
width: 5px;
height: 6px;
background-color: mc('indigo', '200');
border-radius: 2px;
transition: all 1s ease;
}
}
}
......@@ -24,6 +24,17 @@ defaults:
ha:
nodeuid: primary
readonly: false
site:
path: ''
title: Wiki.js
configNamespaces:
- auth
- features
- git
- logging
- site
- theme
- uploads
queues:
- gitSync
- uplClearTemp
......
'use strict'
const path = require('path')
module.exports = (port, spinner) => {
const path = require('path')
const ROOTPATH = process.cwd()
const SERVERPATH = path.join(ROOTPATH, 'server')
const IS_DEBUG = process.env.NODE_ENV === 'development'
/* global wiki */
module.exports = () => {
// ----------------------------------------
// Load modules
// ----------------------------------------
......@@ -26,28 +22,30 @@ module.exports = (port, spinner) => {
// Define Express App
// ----------------------------------------
var app = express()
let app = express()
app.use(compression())
var server
let server
// ----------------------------------------
// Public Assets
// ----------------------------------------
app.use(favicon(path.join(ROOTPATH, 'assets', 'favicon.ico')))
app.use(express.static(path.join(ROOTPATH, 'assets')))
app.use(favicon(path.join(wiki.ROOTPATH, 'assets', 'favicon.ico')))
app.use(express.static(path.join(wiki.ROOTPATH, 'assets')))
// ----------------------------------------
// View Engine Setup
// ----------------------------------------
app.set('views', path.join(SERVERPATH, 'views'))
app.set('views', path.join(wiki.SERVERPATH, 'views'))
app.set('view engine', 'pug')
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
app.locals.config = wiki.config
app.locals.data = wiki.data
app.locals._ = require('lodash')
// ----------------------------------------
......@@ -55,22 +53,7 @@ module.exports = (port, spinner) => {
// ----------------------------------------
app.get('*', (req, res) => {
let langs = []
let conf = {}
try {
langs = yaml.safeLoad(fs.readFileSync(path.join(SERVERPATH, 'app/data.yml'), 'utf8')).langs
conf = yaml.safeLoad(fs.readFileSync(path.join(ROOTPATH, 'config.yml'), 'utf8'))
} catch (err) {
console.error(err)
}
res.render('configure/index', {
langs,
conf,
runmode: {
staticPort: (process.env.WIKI_JS_HEROKU || process.env.WIKI_JS_DOCKER),
staticMongo: (!_.isNil(process.env.WIKI_JS_HEROKU))
}
})
res.render('configure/index')
})
/**
......@@ -81,14 +64,14 @@ module.exports = (port, spinner) => {
() => {
const semver = require('semver')
if (!semver.satisfies(semver.clean(process.version), '>=6.9.0')) {
throw new Error('Node.js version is too old. Minimum is v6.11.1.')
throw new Error('Node.js version is too old. Minimum is 6.11.1.')
}
return 'Node.js ' + process.version + ' detected.'
return 'Node.js ' + process.version + ' detected. Minimum is 6.11.1.'
},
() => {
return Promise.try(() => {
require('crypto')
}).catch(err => { // eslint-disable-line handle-callback-err
}).catch(err => {
throw new Error('Crypto Node.js module is not available.')
}).return('Node.js Crypto module is available.')
},
......@@ -102,9 +85,9 @@ module.exports = (port, spinner) => {
}
let gitver = _.head(stdout.match(/[\d]+\.[\d]+(\.[\d]+)?/gi))
if (!gitver || !semver.satisfies(semver.clean(gitver), '>=2.7.4')) {
reject(new Error('Git version is too old. Minimum is v2.7.4.'))
reject(new Error('Git version is too old. Minimum is 2.7.4.'))
}
resolve('Git v' + gitver + ' detected. Minimum is v2.7.4.')
resolve('Git ' + gitver + ' detected. Minimum is 2.7.4.')
})
})
},
......@@ -118,8 +101,8 @@ module.exports = (port, spinner) => {
() => {
let fs = require('fs')
return Promise.try(() => {
fs.accessSync(path.join(ROOTPATH, 'config.yml'), (fs.constants || fs).W_OK)
}).catch(err => { // eslint-disable-line handle-callback-err
fs.accessSync(path.join(wiki.ROOTPATH, 'config.yml'), (fs.constants || fs).W_OK)
}).catch(err => {
throw new Error('config.yml file is not writable by Node.js process or was not created properly.')
}).return('config.yml is writable by the setup process.')
}
......@@ -174,8 +157,8 @@ module.exports = (port, spinner) => {
const exec = require('execa')
const url = require('url')
const dataDir = path.resolve(ROOTPATH, cfgHelper.parseConfigValue(req.body.pathData))
const gitDir = path.resolve(ROOTPATH, cfgHelper.parseConfigValue(req.body.pathRepo))
const dataDir = path.resolve(wiki.ROOTPATH, cfgHelper.parseConfigValue(req.body.pathData))
const gitDir = path.resolve(wiki.ROOTPATH, cfgHelper.parseConfigValue(req.body.pathRepo))
let gitRemoteUrl = ''
......@@ -315,7 +298,7 @@ module.exports = (port, spinner) => {
}
})
}),
fs.readFileAsync(path.join(ROOTPATH, 'config.yml'), 'utf8').then(confRaw => {
fs.readFileAsync(path.join(wiki.ROOTPATH, 'config.yml'), 'utf8').then(confRaw => {
let conf = yaml.safeLoad(confRaw)
conf.title = req.body.title
conf.host = req.body.host
......@@ -356,12 +339,12 @@ module.exports = (port, spinner) => {
return crypto.randomBytesAsync(32).then(buf => {
conf.sessionSecret = buf.toString('hex')
confRaw = yaml.safeDump(conf)
return fs.writeFileAsync(path.join(ROOTPATH, 'config.yml'), confRaw)
return fs.writeFileAsync(path.join(wiki.ROOTPATH, 'config.yml'), confRaw)
})
})
).then(() => {
if (process.env.IS_HEROKU) {
return fs.outputJsonAsync(path.join(SERVERPATH, 'app/heroku.json'), { configured: true })
return fs.outputJsonAsync(path.join(wiki.SERVERPATH, 'app/heroku.json'), { configured: true })
} else {
return true
}
......@@ -377,7 +360,7 @@ module.exports = (port, spinner) => {
*/
app.post('/restart', (req, res) => {
res.status(204).end()
server.destroy(() => {
/* server.destroy(() => {
spinner.text = 'Setup wizard terminated. Restarting in normal mode...'
_.delay(() => {
const exec = require('execa')
......@@ -386,7 +369,7 @@ module.exports = (port, spinner) => {
process.exit(0)
})
}, 1000)
})
}) */
})
// ----------------------------------------
......@@ -403,9 +386,9 @@ module.exports = (port, spinner) => {
res.status(err.status || 500)
res.send({
message: err.message,
error: IS_DEBUG ? err : {}
error: wiki.IS_DEBUG ? err : {}
})
spinner.fail(err.message)
wiki.logger.error(err.message)
process.exit(1)
})
......@@ -413,11 +396,11 @@ module.exports = (port, spinner) => {
// Start HTTP server
// ----------------------------------------
spinner.text = 'Starting HTTP server...'
wiki.logger.info(`HTTP Server on port: ${wiki.config.port}`)
app.set('port', port)
app.set('port', wiki.config.port)
server = http.createServer(app)
server.listen(port)
server.listen(wiki.config.port)
var openConnections = []
......@@ -443,10 +426,10 @@ module.exports = (port, spinner) => {
switch (error.code) {
case 'EACCES':
spinner.fail('Listening on port ' + port + ' requires elevated privileges!')
wiki.logger.error('Listening on port ' + wiki.config.port + ' requires elevated privileges!')
return process.exit(1)
case 'EADDRINUSE':
spinner.fail('Port ' + port + ' is already in use!')
wiki.logger.error('Port ' + wiki.config.port + ' is already in use!')
return process.exit(1)
default:
throw error
......@@ -454,6 +437,6 @@ module.exports = (port, spinner) => {
})
server.on('listening', () => {
spinner.text = 'Browse to http://localhost:' + port + ' to configure Wiki.js!'
wiki.logger.info('HTTP Server: RUNNING')
})
}
'use strict'
/**
* Associate DB Model relations
*/
......
'use strict'
/**
* Comment schema
*/
......
'use strict'
/**
* Document schema
*/
......
'use strict'
/**
* File schema
*/
......
'use strict'
/**
* Folder schema
*/
......
'use strict'
/**
* Group schema
*/
......
'use strict'
/**
* Right schema
*/
......
'use strict'
/**
* Settings schema
*/
......
'use strict'
/**
* Tags schema
*/
......
'use strict'
/* global wiki */
const Promise = require('bluebird')
......
'use strict'
/* global wiki */
const _ = require('lodash')
......
'use strict'
/* global wiki */
const fs = require('fs')
......@@ -9,8 +7,6 @@ const path = require('path')
const cfgHelper = require('../helpers/config')
module.exports = {
SUBSETS: ['auth', 'features', 'git', 'logging', 'site', 'theme', 'uploads'],
/**
* Load root config from disk
*/
......@@ -64,7 +60,7 @@ module.exports = {
*/
loadFromDb(subsets) {
if (!_.isArray(subsets) || subsets.length === 0) {
subsets = this.SUBSETS
subsets = wiki.data.configNamespaces
}
return wiki.db.Setting.findAll({
......
......@@ -110,7 +110,7 @@ module.exports = {
// -> Sync DB Schemas
syncSchemas() {
return self.inst.sync({
force: true,
force: false,
logging: log => { wiki.logger.log('verbose', log) }
})
},
......
......@@ -7,7 +7,7 @@ html
meta(name='theme-color', content='#009688')
meta(name='msapplication-TileColor', content='#009688')
meta(name='msapplication-TileImage', content=config.site.path + '/favicons/ms-icon-144x144.png')
title= config.title
title= config.site.title
//- Favicon
each favsize in [57, 60, 72, 76, 114, 120, 144, 152, 180]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment