123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507 |
- <template lang='pug'>
- v-card
- v-toolbar(flat, color='primary', dark, dense)
- .subtitle-1 {{ $t('admin:utilities.importv1Title') }}
- v-card-text
- .text-center
- img.animated.fadeInUp.wait-p1s(src='/_assets/svg/icon-software.svg')
- .body-2 Import from Wiki.js 1.x
- v-divider.my-4
- .body-2 Data from a Wiki.js 1.x installation can easily be imported using this tool. What do you want to import?
- v-checkbox(
- label='Content + Uploads'
- value='content'
- color='deep-orange darken-2'
- v-model='importFilters'
- hide-details
- )
- template(v-slot:label)
- strong.deep-orange--text.text--darken-2 Content + Uploads
- .pl-8(v-if='wantContent')
- v-radio-group(v-model='contentMode', hide-details)
- v-radio(
- value='git'
- color='primary'
- )
- template(v-slot:label)
- div
- span Import from Git Connection
- .caption: em #[strong.primary--text Recommended] | The Git storage module will also be configured for you.
- .pl-8.mt-5(v-if='needGit')
- v-row
- v-col(cols='8')
- v-select(
- label='Authentication Mode'
- :items='gitAuthModes'
- v-model='gitAuthMode'
- outlined
- hide-details
- )
- v-col(cols='4')
- v-switch(
- label='Verify SSL Certificate'
- v-model='gitVerifySSL'
- hide-details
- color='primary'
- )
- v-col(cols='8')
- v-text-field(
- outlined
- label='Repository URL'
- :placeholder='(gitAuthMode === `ssh`) ? `e.g. git@github.com:orgname/repo.git` : `e.g. https://github.com/orgname/repo.git`'
- hide-details
- v-model='gitRepoUrl'
- )
- v-col(cols='4')
- v-text-field(
- label='Branch'
- placeholder='e.g. master'
- v-model='gitRepoBranch'
- outlined
- hide-details
- )
- v-col(v-if='gitAuthMode === `ssh`', cols='12')
- v-textarea(
- outlined
- label='Private Key Contents'
- placeholder='-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----'
- hide-details
- v-model='gitPrivKey'
- )
- template(v-else-if='gitAuthMode === `basic`')
- v-col(cols='6')
- v-text-field(
- label='Username'
- v-model='gitUsername'
- outlined
- hide-details
- )
- v-col(cols='6')
- v-text-field(
- type='password'
- label='Password / PAT'
- v-model='gitPassword'
- outlined
- hide-details
- )
- v-col(cols='6')
- v-text-field(
- label='Default Author Email'
- placeholder='e.g. name@company.com'
- v-model='gitUserEmail'
- outlined
- hide-details
- )
- v-col(cols='6')
- v-text-field(
- label='Default Author Name'
- placeholder='e.g. John Smith'
- v-model='gitUserName'
- outlined
- hide-details
- )
- v-col(cols='12')
- v-text-field(
- label='Local Repository Path'
- placeholder='e.g. ./data/repo'
- v-model='gitRepoPath'
- outlined
- hide-details
- )
- .caption.mt-2 This folder should be empty or not exist yet. #[strong.deep-orange--text.text--darken-2 DO NOT] point to your existing Wiki.js 1.x repository folder. In most cases, it should be left to the default value.
- v-alert(color='deep-orange', outlined, icon='mdi-alert', prominent)
- .body-2 - Note that if you already configured the git storage module, its configuration will be replaced with the above.
- .body-2 - Although both v1 and v2 installations can use the same remote git repository, you shouldn't make edits to the same pages simultaneously.
- v-radio-group(v-model='contentMode', hide-details)
- v-divider
- v-radio.mt-3(
- value='disk'
- color='primary'
- )
- template(v-slot:label)
- div
- span Import from local folder
- .caption: em Choose this option only if you didn't have git configured in your Wiki.js 1.x installation.
- .pl-8.mt-5(v-if='needDisk')
- v-text-field(
- outlined
- label='Content Repo Path'
- hint='The absolute path to where the Wiki.js 1.x content is stored on disk.'
- persistent-hint
- v-model='contentPath'
- )
- v-checkbox(
- label='Users'
- value='users'
- color='deep-orange darken-2'
- v-model='importFilters'
- hide-details
- )
- template(v-slot:label)
- strong.deep-orange--text.text--darken-2 Users
- .pl-8.mt-5(v-if='wantUsers')
- v-text-field(
- outlined
- label='MongoDB Connection String'
- hint='The connection string to connect to the Wiki.js 1.x MongoDB database.'
- persistent-hint
- v-model='dbConnStr'
- )
- v-radio-group(v-model='groupMode', hide-details, mandatory)
- v-radio(
- value='MULTI'
- color='primary'
- )
- template(v-slot:label)
- div
- span Create groups for each unique user permissions configuration
- .caption: em #[strong.primary--text Recommended] | Users having identical permission sets will be assigned to the same group. Note that this can potentially result in a large amount of groups being created.
- v-divider
- v-radio.mt-3(
- value='SINGLE'
- color='primary'
- )
- template(v-slot:label)
- div
- span Create a single group with all imported users
- .caption: em The new group will have read permissions enabled by default.
- v-divider
- v-radio.mt-3(
- value='NONE'
- color='primary'
- )
- template(v-slot:label)
- div
- span Don't create any group
- .caption: em Users will not be able to access your wiki until they are assigned to a group.
- v-alert.mt-5(color='deep-orange', outlined, icon='mdi-alert', prominent)
- .body-2 Note that any user that already exists in this installation will not be imported. A list of skipped users will be displayed upon completion.
- .caption.grey--text You must first delete from this installation any user you want to migrate over from the old installation.
- v-card-chin
- v-btn.px-3(depressed, color='deep-orange darken-2', :disabled='!wantUsers && !wantContent', @click='startImport').ml-0
- v-icon(left, color='white') mdi-database-import
- span.white--text Start Import
- v-dialog(
- v-model='isLoading'
- persistent
- max-width='350'
- )
- v-card(color='deep-orange darken-2', dark)
- v-card-text.pa-10.text-center
- semipolar-spinner.animated.fadeIn(
- :animation-duration='1500'
- :size='65'
- color='#FFF'
- style='margin: 0 auto;'
- )
- .mt-5.body-1.white--text Importing from Wiki.js 1.x...
- .caption Please wait
- v-progress-linear.mt-5(
- color='white'
- :value='progress'
- stream
- rounded
- :buffer-value='0'
- )
- v-dialog(
- v-model='isSuccess'
- persistent
- max-width='350'
- )
- v-card(color='green darken-2', dark)
- v-card-text.pa-10.text-center
- v-icon(size='60') mdi-check-circle-outline
- .my-5.body-1.white--text Import completed
- template(v-if='wantUsers')
- .body-2
- span #[strong {{successUsers}}] users imported
- v-btn.text-none.ml-3(
- v-if='failedUsers.length > 0'
- text
- color='white'
- dark
- @click='showFailedUsers = true'
- )
- v-icon(left) mdi-alert
- span {{failedUsers.length}} failed
- .body-2 #[strong {{successGroups}}] groups created
- v-card-actions.green.darken-1
- v-spacer
- v-btn.px-5(
- color='white'
- outlined
- @click='isSuccess = false'
- ) Close
- v-spacer
- v-dialog(
- v-model='showFailedUsers'
- persistent
- max-width='800'
- )
- v-card(color='red darken-2', dark)
- v-toolbar(color='red darken-2', dense)
- v-icon mdi-alert
- .body-2.pl-3 Failed User Imports
- v-spacer
- v-btn.px-5(
- color='white'
- text
- @click='showFailedUsers = false'
- ) Close
- v-simple-table(dense, fixed-header, height='300px')
- template(v-slot:default)
- thead
- tr
- th Provider
- th Email
- th Error
- tbody
- tr(v-for='(fusr, idx) in failedUsers', :key='`fusr-` + idx')
- td {{fusr.provider}}
- td {{fusr.email}}
- td {{fusr.error}}
- </template>
- <script>
- import _ from 'lodash'
- import { SemipolarSpinner } from 'epic-spinners'
- import utilityImportv1UsersMutation from 'gql/admin/utilities/utilities-mutation-importv1-users.gql'
- import storageTargetsQuery from 'gql/admin/storage/storage-query-targets.gql'
- import storageStatusQuery from 'gql/admin/storage/storage-query-status.gql'
- import targetExecuteActionMutation from 'gql/admin/storage/storage-mutation-executeaction.gql'
- import targetsSaveMutation from 'gql/admin/storage/storage-mutation-save-targets.gql'
- export default {
- components: {
- SemipolarSpinner
- },
- data() {
- return {
- importFilters: ['content', 'users'],
- groupMode: 'MULTI',
- contentMode: 'git',
- dbConnStr: 'mongodb://',
- contentPath: '/wiki-v1/repo',
- isLoading: false,
- isSuccess: false,
- gitAuthMode: 'ssh',
- gitAuthModes: [
- { text: 'SSH', value: 'ssh' },
- { text: 'Basic', value: 'basic' }
- ],
- gitVerifySSL: true,
- gitRepoUrl: '',
- gitRepoBranch: 'master',
- gitPrivKey: '',
- gitUsername: '',
- gitPassword: '',
- gitUserEmail: '',
- gitUserName: '',
- gitRepoPath: './data/repo',
- progress: 0,
- successGroups: 0,
- successUsers: 0,
- successPages: 0,
- showFailedUsers: false,
- failedUsers: []
- }
- },
- computed: {
- wantContent () {
- return this.importFilters.indexOf('content') >= 0
- },
- wantUsers () {
- return this.importFilters.indexOf('users') >= 0
- },
- needDisk () {
- return this.contentMode === `disk`
- },
- needGit () {
- return this.contentMode === `git`
- }
- },
- methods: {
- async startImport () {
- this.isLoading = true
- this.progress = 0
- this.failedUsers = []
- _.delay(async () => {
- // -> Import Users
- if (this.wantUsers) {
- try {
- const resp = await this.$apollo.mutate({
- mutation: utilityImportv1UsersMutation,
- variables: {
- mongoDbConnString: this.dbConnStr,
- groupMode: this.groupMode
- }
- })
- const respObj = _.get(resp, 'data.system.importUsersFromV1', {})
- if (!_.get(respObj, 'responseResult.succeeded', false)) {
- throw new Error(_.get(respObj, 'responseResult.message', 'An unexpected error occurred'))
- }
- this.successUsers = _.get(respObj, 'usersCount', 0)
- this.successGroups = _.get(respObj, 'groupsCount', 0)
- this.failedUsers = _.get(respObj, 'failed', [])
- this.progress += 50
- } catch (err) {
- this.$store.commit('pushGraphError', err)
- this.isLoading = false
- return
- }
- }
- // -> Import Content
- if (this.wantContent) {
- try {
- const resp = await this.$apollo.query({
- query: storageTargetsQuery,
- fetchPolicy: 'network-only'
- })
- if (_.has(resp, 'data.storage.targets')) {
- this.progress += 10
- let targets = resp.data.storage.targets.map(str => {
- let nStr = {
- ...str,
- config: _.sortBy(str.config.map(cfg => ({
- ...cfg,
- value: JSON.parse(cfg.value)
- })), [t => t.value.order])
- }
- // -> Setup Git Module
- if (this.contentMode === 'git' && nStr.key === 'git') {
- nStr.isEnabled = true
- nStr.mode = 'sync'
- nStr.syncInterval = 'PT5M'
- nStr.config = [
- { key: 'authType', value: { value: this.gitAuthMode } },
- { key: 'repoUrl', value: { value: this.gitRepoUrl } },
- { key: 'branch', value: { value: this.gitRepoBranch } },
- { key: 'sshPrivateKeyMode', value: { value: 'contents' } },
- { key: 'sshPrivateKeyPath', value: { value: '' } },
- { key: 'sshPrivateKeyContent', value: { value: this.gitPrivKey } },
- { key: 'verifySSL', value: { value: this.gitVerifySSL } },
- { key: 'basicUsername', value: { value: this.gitUsername } },
- { key: 'basicPassword', value: { value: this.gitPassword } },
- { key: 'defaultEmail', value: { value: this.gitUserEmail } },
- { key: 'defaultName', value: { value: this.gitUserName } },
- { key: 'localRepoPath', value: { value: this.gitRepoPath } },
- { key: 'gitBinaryPath', value: { value: '' } }
- ]
- }
- // -> Setup Disk Module
- if (this.contentMode === 'disk' && nStr.key === 'disk') {
- nStr.isEnabled = true
- nStr.mode = 'push'
- nStr.syncInterval = 'P0D'
- nStr.config = [
- { key: 'path', value: { value: this.contentPath } },
- { key: 'createDailyBackups', value: { value: false } }
- ]
- }
- return nStr
- })
- // -> Save storage modules configuration
- const respSv = await this.$apollo.mutate({
- mutation: targetsSaveMutation,
- variables: {
- targets: targets.map(tgt => _.pick(tgt, [
- 'isEnabled',
- 'key',
- 'config',
- 'mode',
- 'syncInterval'
- ])).map(str => ({...str, config: str.config.map(cfg => ({...cfg, value: JSON.stringify({ v: cfg.value.value })}))}))
- }
- })
- const respObj = _.get(respSv, 'data.storage.updateTargets', {})
- if (!_.get(respObj, 'responseResult.succeeded', false)) {
- throw new Error(_.get(respObj, 'responseResult.message', 'An unexpected error occurred'))
- }
- this.progress += 10
- // -> Wait for success sync
- let statusAttempts = 0
- while (statusAttempts < 10) {
- statusAttempts++
- const respStatus = await this.$apollo.query({
- query: storageStatusQuery,
- fetchPolicy: 'network-only'
- })
- if (_.has(respStatus, 'data.storage.status[0]')) {
- const st = _.find(respStatus.data.storage.status, ['key', this.contentMode])
- if (!st) {
- throw new Error('Storage target could not be configured.')
- }
- switch (st.status) {
- case 'pending':
- if (statusAttempts >= 10) {
- throw new Error('Storage target is stuck in pending state. Try again.')
- } else {
- continue
- }
- case 'operational':
- statusAttempts = 10
- break
- case 'error':
- throw new Error(st.message)
- }
- } else {
- throw new Error('Failed to fetch storage sync status.')
- }
- }
- this.progress += 15
- // -> Perform import all
- const respImport = await this.$apollo.mutate({
- mutation: targetExecuteActionMutation,
- variables: {
- targetKey: this.contentMode,
- handler: 'importAll'
- }
- })
- const respImportObj = _.get(respImport, 'data.storage.executeAction', {})
- if (!_.get(respImportObj, 'responseResult.succeeded', false)) {
- throw new Error(_.get(respImportObj, 'responseResult.message', 'An unexpected error occurred'))
- }
- this.progress += 15
- } else {
- throw new Error('Failed to fetch storage targets.')
- }
- } catch (err) {
- this.$store.commit('pushGraphError', err)
- this.isLoading = false
- return
- }
- }
- this.isLoading = false
- this.isSuccess = true
- }, 1500)
- }
- }
- }
- </script>
- <style lang='scss'>
- </style>
|