2
0
Эх сурвалжийг харах

feat: setup wizard cleanup + upgrade UI
[ci skip]

NGPixel 7 жил өмнө
parent
commit
ca8451720f

+ 12 - 0
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

+ 5 - 71
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'

+ 4 - 0
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

+ 4 - 39
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
    */

+ 32 - 32
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' })
 })

+ 20 - 76
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 <u>not</u> 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.<br />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