feat: import users from v1 - db + create users

pull/1080/head
Nick 5 years ago
parent 41716e52a6
commit 82ba7d0a36

@ -9,19 +9,124 @@
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'
label='Content + Uploads'
value='content'
color='deep-orange darken-2'
v-model='importFilters'
hide-details
)
v-checkbox(
label='Uploads'
value='uploads'
color='deep-orange darken-2'
v-model='importFilters'
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'
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='gitUserEmail'
outlined
hide-details
)
v-col(cols='6')
v-text-field(
type='password'
label='Password / PAT'
v-model='gitUserName'
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
)
v-radio-group(v-model='contentMode', hide-details)
v-divider
v-radio.mt-3(
value='local'
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'
@ -29,25 +134,46 @@
v-model='importFilters'
hide-details
)
v-divider.my-5
v-text-field.mt-3(
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-if='needDB'
)
v-text-field.mt-3(
outlined
label='Content Repo Path'
hint='The full path to where the Wiki.js 1.x content is stored on disk.'
persistent-hint
v-model='contentPath'
v-if='needDisk'
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 Note that this can 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 #[strong.primary--text Recommended] | 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-card-chin
v-btn.px-3(depressed, color='deep-orange darken-2', :disabled='!needDB && !needDisk', @click='startImport').ml-0
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(
@ -65,34 +191,170 @@
)
.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
template(v-if='wantContent')
.body-2 #[strong {{successPages}}] pages
.body-2 #[strong {{successAssets}}] assets
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'
export default {
components: {
SemipolarSpinner
},
data() {
return {
importFilters: ['content', 'uploads', 'users'],
importFilters: ['content', 'users'],
groupMode: 'SINGLE',
contentMode: 'git',
dbConnStr: 'mongodb://',
contentPath: '/wiki-v1/repo',
isLoading: false
isLoading: false,
isSuccess: false,
gitAuthMode: 'ssh',
gitAuthModes: [
{ text: 'SSH', value: 'ssh' },
{ text: 'Basic', value: 'basic' }
],
gitVerifySSL: true,
gitRepoUrl: '',
gitRepoBranch: 'master',
gitPrivKey: '',
gitUserEmail: '',
gitUserName: '',
gitRepoPath: './data/repo',
progress: 0,
successUsers: 0,
successPages: 0,
successGroups: 0,
successAssets: 0,
showFailedUsers: false,
failedUsers: []
}
},
computed: {
needDB() {
wantContent () {
return this.importFilters.indexOf('content') >= 0
},
wantUsers () {
return this.importFilters.indexOf('users') >= 0
},
needDisk () {
return this.importFilters.indexOf('content') >= 0 || this.importFilters.indexOf('uploads') >= 0
return this.contentMode === `local`
},
needGit () {
return this.contentMode === `git`
}
},
methods: {
async startImport () {
this.isLoading = true
this.progress = 0
this.failedUsers = []
// -> 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 occured'))
}
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) {
}
this.isLoading = false
this.isSuccess = true
}
}
}

@ -597,6 +597,7 @@ $editor-height-mobile: calc(100vh - 112px - 16px);
position: relative;
height: $editor-height;
overflow: hidden;
padding: 1rem;
@at-root .theme--dark & {
background-color: mc('grey', '900');
@ -622,8 +623,8 @@ $editor-height-mobile: calc(100vh - 112px - 16px);
&-content {
height: $editor-height;
overflow-y: scroll;
padding: 1rem 1rem 1rem 1rem;
width: calc(100% + 1rem + 17px);
padding: 0;
width: calc(100% + 17px);
// -ms-overflow-style: none;
// &::-webkit-scrollbar {
@ -701,6 +702,8 @@ $editor-height-mobile: calc(100vh - 112px - 16px);
.CodeMirror {
height: auto;
font-family: 'Roboto Mono', monospace;
font-size: .9rem;
.cm-header-1 {
font-size: 1.5rem;

@ -0,0 +1,19 @@
mutation($mongoDbConnString: String!, $groupMode: SystemImportUsersGroupMode!) {
system {
importUsersFromV1(mongoDbConnString: $mongoDbConnString, groupMode: $groupMode) {
responseResult {
succeeded
errorCode
slug
message
}
usersCount
groupsCount
failed {
provider
email
error
}
}
}
}

@ -196,7 +196,7 @@
// ---------------------------------
p {
padding: 1rem 24px 0 24px;
padding: 1rem 0 0 0;
margin: 0;
text-align: justify;
@ -319,7 +319,7 @@
// ---------------------------------
ol, ul {
padding: 1rem 24px 0 24px;
padding: 1rem 0 0 0;
list-style-position: inside;
li + li {
@ -396,7 +396,7 @@
}
&.grid-list {
margin: 1rem 24px 0 24px;
margin: 1rem 0 0 0;
background-color: #FFF;
border: 1px solid mc('grey', '200');
padding: 1px;
@ -476,7 +476,7 @@
box-shadow: initial;
background-color: mc('grey', '900');
padding: 1rem 1rem 1rem 3rem;
margin: 1rem 24px;
margin: 1rem 0;
@at-root .theme--dark & {
background-color: darken(mc('grey', '900'), 5%);
@ -604,12 +604,12 @@
border: 1px solid mc('grey', '400');
}
&.uml-diagram {
margin: 1rem;
margin: 1rem 0;
}
}
figure.image {
margin: 1rem 24px 0 24px;
margin: 1rem 0 0 0;
img {
margin: 0 auto;

@ -84,6 +84,56 @@ module.exports = {
} catch (err) {
return graphHelper.generateError(err)
}
},
async importUsersFromV1(obj, args, context) {
try {
const MongoClient = require('mongodb').MongoClient
if (args.mongoDbConnString && args.mongoDbConnString.length > 10) {
const client = await MongoClient.connect(args.mongoDbConnString, {
appname: `Wiki.js ${WIKI.version} Migration Tool`
})
const dbUsers = client.db().collection('users')
const userCursor = dbUsers.find({ email: { '$ne': 'guest' } })
let failed = []
let usersCount = 0
let groupsCount = 0
while (await userCursor.hasNext()) {
const usr = await userCursor.next()
try {
await WIKI.models.users.createNewUser({
providerKey: usr.provider,
email: usr.email,
name: usr.name,
passwordRaw: usr.password,
mustChangePassword: false,
sendWelcomeEmail: false
})
usersCount++
} catch (err) {
failed.push({
provider: usr.provider,
email: usr.email,
error: err.message
})
WIKI.logger.warn(`${usr.email}: ${err}`)
}
}
client.close()
return {
responseResult: graphHelper.generateSuccess('Import completed.'),
usersCount: usersCount,
groupsCount: groupsCount,
failed: failed
}
} else {
throw new Error('MongoDB Connection String is missing or invalid.')
}
} catch (err) {
return graphHelper.generateError(err)
}
}
},
SystemInfo: {

@ -35,6 +35,11 @@ type SystemMutation {
): DefaultResponse @auth(requires: ["manage:system"])
performUpgrade: DefaultResponse @auth(requires: ["manage:system"])
importUsersFromV1(
mongoDbConnString: String!
groupMode: SystemImportUsersGroupMode!
): SystemImportUsersResponse @auth(requires: ["manage:system"])
}
# -----------------------------------------------
@ -73,3 +78,22 @@ type SystemInfo {
usersTotal: Int @auth(requires: ["manage:system", "manage:navigation", "manage:groups", "write:groups", "manage:users", "write:users"])
workingDirectory: String @auth(requires: ["manage:system"])
}
enum SystemImportUsersGroupMode {
MULTI
SINGLE
NONE
}
type SystemImportUsersResponse {
responseResult: ResponseStatus
usersCount: Int
groupsCount: Int
failed: [SystemImportUsersResponseFailed]
}
type SystemImportUsersResponseFailed {
provider: String
email: String
error: String
}

Loading…
Cancel
Save