From ca8451720f0bb86e990b5d77423fccc8c1eb747c Mon Sep 17 00:00:00 2001 From: NGPixel Date: Mon, 9 Oct 2017 00:16:08 -0400 Subject: [PATCH] feat: setup wizard cleanup + upgrade UI [ci skip] --- client/js/app.js | 12 +++ .../js/components/config-manager.component.js | 76 +-------------- server/app/data.yml | 4 + server/configure.js | 43 +-------- server/controllers/admin.js | 64 ++++++------- server/views/configure/index.pug | 96 ++++--------------- 6 files changed, 77 insertions(+), 218 deletions(-) diff --git a/client/js/app.js b/client/js/app.js index ced4fcbc..b3edc839 100644 --- a/client/js/app.js +++ b/client/js/app.js @@ -8,6 +8,7 @@ import CONSTANTS from './constants' import Vue from 'vue' import VueResource from 'vue-resource' import VueClipboards from 'vue-clipboards' +import VeeValidate from 'vee-validate' import { ApolloClient, createBatchingNetworkInterface } from 'apollo-client' import store from './store' @@ -83,6 +84,17 @@ Vue.use(VueResource) Vue.use(VueClipboards) Vue.use(localization.VueI18Next) Vue.use(helpers) +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 + } +}) // ==================================== // Register Vue Components diff --git a/client/js/components/config-manager.component.js b/client/js/components/config-manager.component.js index 50b8cedc..18c38143 100644 --- a/client/js/components/config-manager.component.js +++ b/client/js/components/config-manager.component.js @@ -1,22 +1,7 @@ -'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() { @@ -42,12 +27,12 @@ export default { results: [] }, conf: { + upgrade: false, 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), @@ -82,28 +67,19 @@ export default { perc = (this.syscheck.ok) ? '15%' : '5%' break case 'general': - perc = '20%' + perc = '25%' 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%' + perc = '50%' break case 'gitcheck': - perc = (this.gitcheck.ok) ? '75%' : '65%' + perc = (this.gitcheck.ok) ? '70%' : '55%' break case 'admin': - perc = '80%' + perc = '75%' break } return perc @@ -176,48 +152,6 @@ export default { 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' diff --git a/server/app/data.yml b/server/app/data.yml index a26eff66..2b0c5bed 100644 --- a/server/app/data.yml +++ b/server/app/data.yml @@ -26,6 +26,7 @@ defaults: readonly: false site: path: '' + lang: en title: Wiki.js configNamespaces: - auth @@ -86,6 +87,9 @@ langs: - id: ko name: Korean - 한국어 + - + id: fa + name: Persian (Fārsi) - فارسی - id: pt name: Portuguese - Português diff --git a/server/configure.js b/server/configure.js index 46ff4661..290a3dee 100644 --- a/server/configure.js +++ b/server/configure.js @@ -53,7 +53,9 @@ module.exports = () => { // ---------------------------------------- app.get('*', (req, res) => { - res.render('configure/index') + fs.readJsonAsync(path.join(wiki.ROOTPATH, 'package.json')).then(packageObj => { + res.render('configure/index', { packageObj }) + }) }) /** @@ -63,7 +65,7 @@ module.exports = () => { Promise.mapSeries([ () => { const semver = require('semver') - if (!semver.satisfies(semver.clean(process.version), '>=6.9.0')) { + if (!semver.satisfies(semver.clean(process.version), '>=6.11.1')) { throw new Error('Node.js version is too old. Minimum is 6.11.1.') } return 'Node.js ' + process.version + ' detected. Minimum is 6.11.1.' @@ -113,43 +115,6 @@ module.exports = () => { }) }) - /** - * Check the DB connection - */ - app.post('/dbcheck', (req, res) => { - let mongo = require('mongodb').MongoClient - let mongoURI = cfgHelper.parseConfigValue(req.body.db) - mongo.connect(mongoURI, { - autoReconnect: false, - reconnectTries: 2, - reconnectInterval: 1000, - connectTimeoutMS: 5000, - socketTimeoutMS: 5000 - }, (err, db) => { - if (err === null) { - // Try to create a test collection - db.createCollection('test', (err, results) => { - if (err === null) { - // Try to drop test collection - db.dropCollection('test', (err, results) => { - if (err === null) { - res.json({ ok: true }) - } else { - res.json({ ok: false, error: 'Unable to delete test collection. Verify permissions. ' + err.message }) - } - db.close() - }) - } else { - res.json({ ok: false, error: 'Unable to create test collection. Verify permissions. ' + err.message }) - db.close() - } - }) - } else { - res.json({ ok: false, error: err.message }) - } - }) - }) - /** * Check the Git connection */ diff --git a/server/controllers/admin.js b/server/controllers/admin.js index bc7554e0..962c6161 100644 --- a/server/controllers/admin.js +++ b/server/controllers/admin.js @@ -1,6 +1,6 @@ 'use strict' -/* global db, lang, rights, winston */ +/* global wiki */ var express = require('express') var router = express.Router() @@ -33,14 +33,14 @@ router.post('/profile', (req, res) => { return res.render('error-forbidden') } - return db.User.findById(req.user.id).then((usr) => { + return wiki.db.User.findById(req.user.id).then((usr) => { usr.name = _.trim(req.body.name) if (usr.provider === 'local' && req.body.password !== '********') { let nPwd = _.trim(req.body.password) if (nPwd.length < 6) { return Promise.reject(new Error('New Password too short!')) } else { - return db.User.hashPassword(nPwd).then((pwd) => { + return wiki.db.User.hashPassword(nPwd).then((pwd) => { usr.password = pwd return usr.save() }) @@ -61,9 +61,9 @@ router.get('/stats', (req, res) => { } Promise.all([ - db.Entry.count(), - db.UplFile.count(), - db.User.count() + wiki.db.Entry.count(), + wiki.db.UplFile.count(), + wiki.db.User.count() ]).spread((totalEntries, totalUploads, totalUsers) => { return res.render('pages/admin/stats', { totalEntries, totalUploads, totalUsers, adminTab: 'stats' @@ -78,7 +78,7 @@ router.get('/users', (req, res) => { return res.render('error-forbidden') } - db.User.find({}) + wiki.db.User.find({}) .select('-password -rights') .sort('name email') .exec().then((usrs) => { @@ -95,7 +95,7 @@ router.get('/users/:id', (req, res) => { return res.render('error-forbidden') } - db.User.findById(req.params.id) + wiki.db.User.findById(req.params.id) .select('-password -providerId') .exec().then((usr) => { let usrOpts = { @@ -137,12 +137,12 @@ router.post('/users/create', (req, res) => { return res.status(400).json({ msg: 'Name is missing' }) } - db.User.findOne({ email: nUsr.email, provider: nUsr.provider }).then(exUsr => { + wiki.db.User.findOne({ email: nUsr.email, provider: nUsr.provider }).then(exUsr => { if (exUsr) { return res.status(400).json({ msg: 'User already exists!' }) || true } - let pwdGen = (nUsr.provider === 'local') ? db.User.hashPassword(nUsr.password) : Promise.resolve(true) + let pwdGen = (nUsr.provider === 'local') ? wiki.db.User.hashPassword(nUsr.password) : Promise.resolve(true) return pwdGen.then(nPwd => { if (nUsr.provider !== 'local') { nUsr.password = '' @@ -158,37 +158,37 @@ router.post('/users/create', (req, res) => { deny: false }] - return db.User.create(nUsr).then(() => { + return wiki.db.User.create(nUsr).then(() => { return res.json({ ok: true }) }) }).catch(err => { - winston.warn(err) + wiki.logger.warn(err) return res.status(500).json({ msg: err }) }) }).catch(err => { - winston.warn(err) + wiki.logger.warn(err) return res.status(500).json({ msg: err }) }) }) router.post('/users/:id', (req, res) => { if (!res.locals.rights.manage) { - return res.status(401).json({ msg: lang.t('errors:unauthorized') }) + return res.status(401).json({ msg: wiki.lang.t('errors:unauthorized') }) } if (!validator.isMongoId(req.params.id)) { - return res.status(400).json({ msg: lang.t('errors:invaliduserid') }) + return res.status(400).json({ msg: wiki.lang.t('errors:invaliduserid') }) } - return db.User.findById(req.params.id).then((usr) => { + return wiki.db.User.findById(req.params.id).then((usr) => { usr.name = _.trim(req.body.name) usr.rights = JSON.parse(req.body.rights) if (usr.provider === 'local' && req.body.password !== '********') { let nPwd = _.trim(req.body.password) if (nPwd.length < 6) { - return Promise.reject(new Error(lang.t('errors:newpasswordtooshort'))) + return Promise.reject(new Error(wiki.lang.t('errors:newpasswordtooshort'))) } else { - return db.User.hashPassword(nPwd).then((pwd) => { + return wiki.db.User.hashPassword(nPwd).then((pwd) => { usr.password = pwd return usr.save() }) @@ -199,7 +199,7 @@ router.post('/users/:id', (req, res) => { }).then((usr) => { // Update guest rights for future requests if (usr.provider === 'local' && usr.email === 'guest') { - rights.guest = usr + wiki.rights.guest = usr } return usr }).then(() => { @@ -214,14 +214,14 @@ router.post('/users/:id', (req, res) => { */ router.delete('/users/:id', (req, res) => { if (!res.locals.rights.manage) { - return res.status(401).json({ msg: lang.t('errors:unauthorized') }) + return res.status(401).json({ msg: wiki.lang.t('errors:unauthorized') }) } if (!validator.isMongoId(req.params.id)) { - return res.status(400).json({ msg: lang.t('errors:invaliduserid') }) + return res.status(400).json({ msg: wiki.lang.t('errors:invaliduserid') }) } - return db.User.findByIdAndRemove(req.params.id).then(() => { + return wiki.db.User.findByIdAndRemove(req.params.id).then(() => { return res.json({ ok: true }) }).catch((err) => { res.status(500).json({ ok: false, msg: err.message }) @@ -249,7 +249,7 @@ router.get('/system', (req, res) => { cwd: process.cwd() } - fs.readJsonAsync(path.join(ROOTPATH, 'package.json')).then(packageObj => { + fs.readJsonAsync(path.join(wiki.ROOTPATH, 'package.json')).then(packageObj => { axios.get('https://api.github.com/repos/Requarks/wiki/releases/latest').then(resp => { let sysversion = { current: 'v' + packageObj.version, @@ -259,7 +259,7 @@ router.get('/system', (req, res) => { res.render('pages/admin/system', { adminTab: 'system', hostInfo, sysversion }) }).catch(err => { - winston.warn(err) + wiki.logger.warn(err) res.render('pages/admin/system', { adminTab: 'system', hostInfo, sysversion: { current: 'v' + packageObj.version } }) }) }) @@ -287,19 +287,19 @@ router.post('/theme', (req, res) => { return res.render('error-forbidden') } - if (!validator.isIn(req.body.primary, appdata.colors)) { + if (!validator.isIn(req.body.primary, wiki.data.colors)) { return res.status(406).json({ msg: 'Primary color is invalid.' }) - } else if (!validator.isIn(req.body.alt, appdata.colors)) { + } else if (!validator.isIn(req.body.alt, wiki.data.colors)) { return res.status(406).json({ msg: 'Alternate color is invalid.' }) - } else if (!validator.isIn(req.body.footer, appdata.colors)) { + } else if (!validator.isIn(req.body.footer, wiki.data.colors)) { return res.status(406).json({ msg: 'Footer color is invalid.' }) } - appconfig.theme.primary = req.body.primary - appconfig.theme.alt = req.body.alt - appconfig.theme.footer = req.body.footer - appconfig.theme.code.dark = req.body.codedark === 'true' - appconfig.theme.code.colorize = req.body.codecolorize === 'true' + wiki.config.theme.primary = req.body.primary + wiki.config.theme.alt = req.body.alt + wiki.config.theme.footer = req.body.footer + wiki.config.theme.code.dark = req.body.codedark === 'true' + wiki.config.theme.code.colorize = req.body.codecolorize === 'true' return res.json({ msg: 'OK' }) }) diff --git a/server/views/configure/index.pug b/server/views/configure/index.pug index fa14f3ea..9de80c12 100644 --- a/server/views/configure/index.pug +++ b/server/views/configure/index.pug @@ -23,6 +23,14 @@ block body .panel-content.is-text p This installation wizard will guide you through the steps needed to get your wiki up and running in no time! p Detailed information about installation and usage can be found on the #[a(href='https://docs.wiki.requarks.io/') official documentation site]. #[br] Should you have any question or would like to report something that doesn't look right, feel free to create a new issue on the #[a(href='https://github.com/Requarks/wiki/issues') GitHub project]. + .panel-content.form-sections + section + p #[i.nc-icon-outline.tech_cd-reader] You are about to install Wiki.js #[strong= packageObj.version]. + section + p.control.is-fullwidth + input#ipt-upgrade(type='checkbox', v-model='conf.upgrade', name='ipt-upgrade') + label.label(for='ipt-upgrade') Upgrade from Wiki.js 1.x + span.desc Check this box if you are upgrading from Wiki.js 1.x and wish to migrate your existing data. .panel-footer .progress-bar: div(v-bind:style='{width: currentProgress}') button.button.is-small.is-light-blue(v-on:click='proceedToSyscheck', v-bind:disabled='loading') Start @@ -49,6 +57,7 @@ block body .progress-bar: div(v-bind:style='{width: currentProgress}') button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToWelcome', v-bind:disabled='loading') Back button.button.is-small.is-teal(v-on:click='proceedToSyscheck', v-if='!loading && !syscheck.ok') Check Again + button.button.is-small.is-red.is-outlined(v-on:click='proceedToGeneral', v-if='!loading && !syscheck.ok') Continue Anyway button.button.is-small.is-light-blue(v-on:click='proceedToGeneral', v-if='loading || syscheck.ok', v-bind:disabled='loading') Continue //- ============================================== @@ -70,7 +79,7 @@ block body p.control.is-fullwidth label.label Host input(type='text', placeholder='http://', v-model='conf.host', data-vv-scope='general', name='ipt-host', v-validate='{ required: true, min: 4 }') - span.desc The full URL to your wiki, without the trailing slash. E.g.: http://wiki.domain.com. Note that sub-folders are #[u not supported]. + span.desc The full URL to your wiki, without the trailing slash, e.g.: http://wiki.domain.com. Make sure to include the port if different than 80/443. section p.control label.label Port @@ -79,10 +88,15 @@ block body section p.control label.label Site UI Language - select(v-model='conf.site.lang') + select(v-model='conf.lang') each lg in data.langs option(value=lg.id)= lg.name span.desc The language in which navigation, help and other UI elements will be displayed. + section + p.control.is-fullwidth + label.label Local Repository Path + input(type='text', placeholder='e.g. ./repo', v-model='conf.pathRepo', data-vv-scope='general', name='ipt-repopath', v-validate='{ required: true, min: 2 }') + span.desc The path where the local git repository will be created, used to store content in markdown files and uploads.#[br] #[strong It is recommended to leave the default value]. section p.control.is-fullwidth input#ipt-public(type='checkbox', v-model='conf.public', data-vv-scope='general', name='ipt-public') @@ -111,88 +125,18 @@ block body li - Do not rewrite URLs after the domain. This can cause unexpected issues in Wiki.js navigation. li - Do not remove or alter the client IP when proxying the requests. This can cause the authentication brute force protection to engage unexpectedly. template(v-if='considerations.https') - h3 The site will not be using HTTPS? #[i.icon-warning-outline.animated.fadeOut.infinite] + h3 The site will not be using HTTPS? #[i.nc-icon-outline.ui-3_alert.animated.fadeOut.infinite] p The host URL you specified is not HTTPS. It is highly recommended to use HTTPS. You must use a web server / proxy (e.g. nginx / apache / IIS) in front of Wiki.js to use HTTPS. Wiki.js does not provide HTTPS handling by itself. template(v-if='considerations.port') h3 You are using a non-standard port. p If you are not planning on using a web server / proxy in front of Wiki.js, be aware that users will need to specify the port when accessing the wiki. Make sure this is the intended behavior. Otherwise set a standard HTTP port such as 80. template(v-if='considerations.localhost') - h3 Are you sure you want to use localhost as the host base URL? #[i.icon-warning-outline.animated.fadeOut.infinite] + h3 Are you sure you want to use localhost as the host base URL? #[i.nc-icon-outline.ui-3_alert.animated.fadeOut.infinite] p The host URL you specified is localhost. Unless you are a developer running Wiki.js locally on your machine, this is not recommended! .panel-footer .progress-bar: div(v-bind:style='{width: currentProgress}') button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToGeneral', v-bind:disabled='loading') Back - button.button.is-small.is-light-blue(v-on:click='proceedToDb', v-bind:disabled='loading') Continue - - //- ============================================== - //- DATABASE - //- ============================================== - - template(v-else-if='state === "db"') - .panel - h2.panel-title.is-featured - span Database - i(v-if='loading') - .panel-content.is-text - p Wiki.js stores administrative data such as users, permissions and assets metadata in a MongoDB database. Article contents and uploads are not stored in the DB. Instead, they are stored on-disk and synced automatically with a remote git repository of your choice. - .panel-content.form-sections - section - p.control.is-fullwidth - label.label MongoDB Connection String - input(type='text', placeholder='e.g. mongodb://localhost:27017/wiki', v-model='conf.db', data-vv-scope='db', name='ipt-db', v-validate='{ required: true, min: 3 }') - span.desc The connection string to your MongoDB server. Leave the default localhost value if MongoDB is installed on the same server.
You can also specify an environment variable as the connection string, e.g. $(MONGO_URI). - .panel-footer - .progress-bar: div(v-bind:style='{width: currentProgress}') - button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToConsiderations', v-bind:disabled='loading') Back - button.button.is-small.is-light-blue(v-on:click='proceedToDbcheck', v-bind:disabled='loading || errors.any("db")') Connect - - //- ============================================== - //- DATABASE CHECK - //- ============================================== - - template(v-else-if='state === "dbcheck"') - .panel - h2.panel-title.is-featured - span Database Check - i(v-if='loading') - .panel-content.is-text - p(v-if='loading') #[i.icon-loader.animated.rotateIn.infinite] Testing the connection to MongoDB... - p(v-if='!loading && dbcheck.ok') - i.icon-check - strong Connected successfully! - p(v-if='!loading && !dbcheck.ok') #[i.icon-square-cross] Error: {{ dbcheck.error }} - .panel-footer - .progress-bar: div(v-bind:style='{width: currentProgress}') - button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToDb', v-bind:disabled='loading') Back - button.button.is-small.is-teal(v-on:click='proceedToDbcheck', v-if='!loading && !dbcheck.ok') Try Again - button.button.is-small.is-light-blue(v-on:click='proceedToPaths', v-if='loading || dbcheck.ok', v-bind:disabled='loading') Continue - - //- ============================================== - //- PATHS - //- ============================================== - - template(v-else-if='state === "paths"') - .panel - h2.panel-title.is-featured - span Paths - i(v-if='loading') - .panel-content.is-text - p It is recommended to leave the default values. - .panel-content.form-sections - section - p.control.is-fullwidth - label.label Local Data Path - input(type='text', placeholder='e.g. ./data', v-model='conf.pathData', data-vv-scope='paths', name='ipt-datapath', v-validate='{ required: true, min: 2 }') - span.desc The path where cache (processed content, thumbnails, search index, etc.) will be stored on disk. - section - p.control.is-fullwidth - label.label Local Repository Path - input(type='text', placeholder='e.g. ./repo', v-model='conf.pathRepo', data-vv-scope='paths', name='ipt-repopath', v-validate='{ required: true, min: 2 }') - span.desc The path where the local git repository will be created, used to store content in markdown files and uploads. - .panel-footer - .progress-bar: div(v-bind:style='{width: currentProgress}') - button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToDb', v-bind:disabled='loading') Back - button.button.is-small.is-light-blue(v-on:click='proceedToGit', v-bind:disabled='loading || errors.any("paths")') Continue + button.button.is-small.is-light-blue(v-on:click='proceedToGit', v-bind:disabled='loading') Continue //- ============================================== //- GIT @@ -256,7 +200,7 @@ block body span.desc The default/fallback email to use when creating commits to the git repository. .panel-footer .progress-bar: div(v-bind:style='{width: currentProgress}') - button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToPaths', v-bind:disabled='loading') Back + button.button.is-small.is-light-blue.is-outlined(v-on:click='proceedToGeneral', v-bind:disabled='loading') Back button.button.is-small.is-light-blue.is-outlined(v-on:click='conf.gitUseRemote = false; proceedToGitCheck()', v-bind:disabled='loading') Skip this step button.button.is-small.is-light-blue(v-on:click='conf.gitUseRemote = true; proceedToGitCheck()', v-bind:disabled='loading || errors.any("git")') Continue