From f74c0caac2b11d2ed78c9e8c61f953d20f6d4d4e Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 3 Feb 2019 17:08:06 -0500 Subject: [PATCH] feat: storage modes + git improvements --- .../admin/admin-groups-edit-permissions.vue | 2 +- client/components/admin/admin-storage.vue | 61 +++++++--- .../admin/storage/storage-query-targets.gql | 2 + package.json | 4 +- server/core/kernel.js | 1 + server/graph/schemas/storage.graphql | 2 + server/helpers/common.js | 3 +- server/models/pages.js | 4 +- server/models/storage.js | 16 +-- server/modules/storage/disk/definition.yml | 9 ++ server/modules/storage/disk/storage.js | 31 +++-- server/modules/storage/git/definition.yml | 12 ++ server/modules/storage/git/storage.js | 110 +++++++++--------- yarn.lock | 35 ++---- 14 files changed, 172 insertions(+), 120 deletions(-) diff --git a/client/components/admin/admin-groups-edit-permissions.vue b/client/components/admin/admin-groups-edit-permissions.vue index f948da1f..e140b35a 100644 --- a/client/components/admin/admin-groups-edit-permissions.vue +++ b/client/components/admin/admin-groups-edit-permissions.vue @@ -19,7 +19,7 @@ ) This is a system group. Some permissions cannot be modified. v-container.px-3.pb-3.pt-0(fluid, grid-list-md) v-layout(row, wrap) - v-flex(xs12, md6, lg4, v-for='pmGroup in permissions') + v-flex(xs12, md6, lg4, v-for='pmGroup in permissions', :key='pmGroup.category') v-card.md2(flat, :class='$vuetify.dark ? "grey darken-3-d5" : "white"') v-subheader {{pmGroup.category}} v-card-text.pt-0 diff --git a/client/components/admin/admin-storage.vue b/client/components/admin/admin-storage.vue index e562ad34..5af7f099 100644 --- a/client/components/admin/admin-storage.vue +++ b/client/components/admin/admin-storage.vue @@ -20,18 +20,33 @@ v-tab(v-for='tgt in activeTargets', :key='tgt.key') {{ tgt.title }} v-tab-item(key='settings', :transition='false', :reverse-transition='false') - v-card.pa-3(flat, tile) - .body-2.grey--text.text--darken-1 Select which storage targets to enable: - .caption.grey--text.pb-2 Some storage targets require additional configuration in their dedicated tab (when selected). - v-form - v-checkbox.my-0( - v-for='tgt in targets' - v-model='tgt.isEnabled' - :key='tgt.key' - :label='tgt.title' - color='primary' - hide-details - ) + v-container.pa-3(fluid, grid-list-md) + v-layout(row, wrap) + v-flex(xs12, md6) + .body-2.grey--text.text--darken-1 Select which storage targets to enable: + .caption.grey--text.pb-2 Some storage targets require additional configuration in their dedicated tab (when selected). + v-form + v-checkbox.my-0( + :disabled='!tgt.isAvailable' + v-for='tgt in targets' + v-model='tgt.isEnabled' + :key='tgt.key' + :label='tgt.title' + color='primary' + hide-details + ) + v-flex(xs12, md6) + .pa-3.grey.radius-7(:class='$vuetify.dark ? "darken-4" : "lighten-5"') + .body-2.grey--text.text--darken-1 Advanced Settings + v-text-field.mt-3.md2( + v-model='syncInterval' + outline + background-color='grey lighten-2' + prepend-icon='schedule' + label='Synchronization Interval' + hint='For performance reasons, some storage targets synchronize changes on an interval-based schedule, instead of on every change. Define at which interval should the synchronization occur for all storage targets.' + persistent-hint + ) v-tab-item(v-for='(tgt, n) in activeTargets', :key='tgt.key', :transition='false', :reverse-transition='false') v-card.pa-3(flat, tile) @@ -58,7 +73,7 @@ persistent-hint :class='cfg.value.hint ? "mb-2" : ""' ) - v-switch( + v-switch.mb-3( v-else-if='cfg.value.type === "boolean"' :key='cfg.key' :label='cfg.value.title' @@ -89,23 +104,26 @@ label='Bi-directional' color='primary' value='sync' + :disabled='tgt.supportedModes.indexOf(`sync`) < 0' ) v-radio( label='Push to target' color='primary' value='push' + :disabled='tgt.supportedModes.indexOf(`push`) < 0' ) v-radio( label='Pull from target' color='primary' value='pull' + :disabled='tgt.supportedModes.indexOf(`pull`) < 0' ) .body-1.ml-3 - strong Bi-directional + strong Bi-directional #[em.red--text.text--lighten-2(v-if='tgt.supportedModes.indexOf(`sync`) < 0') Unsupported] .pb-3 In bi-directional mode, content is first pulled from the storage target. Any newer content overwrites local content. New content since last sync is then pushed to the storage target, overwriting any content on target if present. - strong Push to target - .pb-3 Content is always pushed to the storage target, overwriting any existing content. This is the default and safest choice for backup scenarios. - strong Pull from target + strong Push to target #[em.red--text.text--lighten-2(v-if='tgt.supportedModes.indexOf(`push`) < 0') Unsupported] + .pb-3 Content is always pushed to the storage target, overwriting any existing content. This is safest choice for backup scenarios. + strong Pull from target #[em.red--text.text--lighten-2(v-if='tgt.supportedModes.indexOf(`pull`) < 0') Unsupported] .pb-3 Content is always pulled from the storage target, overwriting any local content which already exists. This choice is usually reserved for single-use content import. Caution with this option as any local content will always be overwritten! @@ -121,6 +139,7 @@ export default { }, data() { return { + syncInterval: '5m', targets: [] } }, @@ -163,7 +182,13 @@ export default { targets: { query: targetsQuery, fetchPolicy: 'network-only', - update: (data) => _.cloneDeep(data.storage.targets).map(str => ({...str, config: str.config.map(cfg => ({...cfg, value: JSON.parse(cfg.value)}))})), + update: (data) => _.cloneDeep(data.storage.targets).map(str => ({ + ...str, + config: _.sortBy(str.config.map(cfg => ({ + ...cfg, + value: JSON.parse(cfg.value) + })), [t => t.value.order]) + })), watchLoading (isLoading) { this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-storage-refresh') } diff --git a/client/graph/admin/storage/storage-query-targets.gql b/client/graph/admin/storage/storage-query-targets.gql index 4b02edc5..219dcdcb 100644 --- a/client/graph/admin/storage/storage-query-targets.gql +++ b/client/graph/admin/storage/storage-query-targets.gql @@ -1,12 +1,14 @@ query { storage { targets(orderBy: "title ASC") { + isAvailable isEnabled key title description logo website + supportedModes mode config { key diff --git a/package.json b/package.json index a8bc148d..f5c4ddf4 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "i18next-localstorage-cache": "1.1.1", "i18next-node-fs-backend": "2.1.1", "image-size": "0.7.1", - "ioredis": "4.5.1", + "ioredis": "4.6.2", "js-base64": "2.5.1", "js-binary": "1.2.0", "js-yaml": "3.12.1", @@ -266,7 +266,7 @@ "vue-codemirror": "4.0.6", "vue-hot-reload-api": "2.3.1", "vue-loader": "15.6.2", - "vue-material-design-icons": "2.6.0", + "vue-material-design-icons": "3.0.0", "vue-moment": "4.0.0", "vue-router": "3.0.2", "vue-simple-breakpoints": "1.0.3", diff --git a/server/core/kernel.js b/server/core/kernel.js index accb8430..38f0806e 100644 --- a/server/core/kernel.js +++ b/server/core/kernel.js @@ -61,6 +61,7 @@ module.exports = { await WIKI.models.storage.refreshTargetsFromDisk() await WIKI.auth.activateStrategies() + await WIKI.models.storage.initTargets() await WIKI.queue.start() }, /** diff --git a/server/graph/schemas/storage.graphql b/server/graph/schemas/storage.graphql index a984995b..4a333aff 100644 --- a/server/graph/schemas/storage.graphql +++ b/server/graph/schemas/storage.graphql @@ -36,12 +36,14 @@ type StorageMutation { # ----------------------------------------------- type StorageTarget { + isAvailable: Boolean! isEnabled: Boolean! key: String! title: String! description: String logo: String website: String + supportedModes: [String] mode: String config: [KeyValuePair] } diff --git a/server/helpers/common.js b/server/helpers/common.js index cb3e84e1..c4319bd8 100644 --- a/server/helpers/common.js +++ b/server/helpers/common.js @@ -30,7 +30,8 @@ module.exports = { type: (value.type || value).toLowerCase(), title: value.title || _.startCase(key), hint: value.hint || false, - enum: value.enum || false + enum: value.enum || false, + order: value.order || 100 }) return result }, {}) diff --git a/server/models/pages.js b/server/models/pages.js index 8a846a6a..863df933 100644 --- a/server/models/pages.js +++ b/server/models/pages.js @@ -224,7 +224,9 @@ module.exports = class Page extends Model { 'pages.*', { authorName: 'author.name', - creatorName: 'creator.name' + authorEmail: 'author.email', + creatorName: 'creator.name', + creatorEmail: 'creator.email' } ]) .joinRelation('author') diff --git a/server/models/storage.js b/server/models/storage.js index 7b4ec069..9ed7d91d 100644 --- a/server/models/storage.js +++ b/server/models/storage.js @@ -48,6 +48,7 @@ module.exports = class Storage extends Model { } WIKI.data.storage = diskTargets.map(target => ({ ...target, + isAvailable: _.get(target, 'isAvailable', false), props: commonHelper.parseModuleProps(target.props) })) @@ -58,7 +59,7 @@ module.exports = class Storage extends Model { newTargets.push({ key: target.key, isEnabled: false, - mode: 'push', + mode: target.defaultMode || 'push', config: _.transform(target.props, (result, value, key) => { _.set(result, key, value.default) return result @@ -100,10 +101,9 @@ module.exports = class Storage extends Model { try { for(let target of targets) { target.fn = require(`../modules/storage/${target.key}/storage`) - await target.fn.init.call({ - config: target.config, - mode: target.mode - }) + target.fn.config = target.config + target.fn.mode = target.mode + await target.fn.init() } } catch (err) { WIKI.logger.warn(err) @@ -114,11 +114,7 @@ module.exports = class Storage extends Model { static async pageEvent({ event, page }) { try { for(let target of targets) { - await target.fn[event].call({ - config: target.config, - mode: target.mode, - page - }) + await target.fn[event](page) } } catch (err) { WIKI.logger.warn(err) diff --git a/server/modules/storage/disk/definition.yml b/server/modules/storage/disk/definition.yml index 54caf3c4..199cd724 100644 --- a/server/modules/storage/disk/definition.yml +++ b/server/modules/storage/disk/definition.yml @@ -4,10 +4,19 @@ description: Local storage on disk or network shares. author: requarks.io logo: https://static.requarks.io/logo/local-fs.svg website: https://wiki.js.org +isAvailable: true supportedModes: - push +defaultMode: push props: path: type: String title: Path hint: Absolute path without a trailing slash (e.g. /home/wiki/backup, C:\wiki\backup) + order: 1 + createDailyBackups: + type: Boolean + default: false + title: Create Daily Backups + hint: A tar.gz archive containing all content will be created daily in subfolder named _daily. Archives are kept for a month. + order: 2 diff --git a/server/modules/storage/disk/storage.js b/server/modules/storage/disk/storage.js index cb506731..8da32684 100644 --- a/server/modules/storage/disk/storage.js +++ b/server/modules/storage/disk/storage.js @@ -46,23 +46,32 @@ module.exports = { // not used }, async init() { + WIKI.logger.info('(STORAGE/DISK) Initializing...') await fs.ensureDir(this.config.path) + WIKI.logger.info('(STORAGE/DISK) Initialization completed.') }, - async created() { - const filePath = path.join(this.config.path, `${this.page.path}.${getFileExtension(this.page.contentType)}`) - await fs.outputFile(filePath, injectMetadata(this.page), 'utf8') + async sync() { + // not used }, - async updated() { - const filePath = path.join(this.config.path, `${this.page.path}.${getFileExtension(this.page.contentType)}`) - await fs.outputFile(filePath, injectMetadata(this.page), 'utf8') + async created(page) { + WIKI.logger.info(`(STORAGE/DISK) Creating file ${page.path}...`) + const filePath = path.join(this.config.path, `${page.path}.${getFileExtension(page.contentType)}`) + await fs.outputFile(filePath, injectMetadata(page), 'utf8') }, - async deleted() { - const filePath = path.join(this.config.path, `${this.page.path}.${getFileExtension(this.page.contentType)}`) + async updated(page) { + WIKI.logger.info(`(STORAGE/DISK) Updating file ${page.path}...`) + const filePath = path.join(this.config.path, `${page.path}.${getFileExtension(page.contentType)}`) + await fs.outputFile(filePath, injectMetadata(page), 'utf8') + }, + async deleted(page) { + WIKI.logger.info(`(STORAGE/DISK) Deleting file ${page.path}...`) + const filePath = path.join(this.config.path, `${page.path}.${getFileExtension(page.contentType)}`) await fs.unlink(filePath) }, - async renamed() { - const sourceFilePath = path.join(this.config.path, `${this.page.sourcePath}.${getFileExtension(this.page.contentType)}`) - const destinationFilePath = path.join(this.config.path, `${this.page.destinationPath}.${getFileExtension(this.page.contentType)}`) + async renamed(page) { + WIKI.logger.info(`(STORAGE/DISK) Renaming file ${page.sourcePath} to ${page.destinationPath}...`) + const sourceFilePath = path.join(this.config.path, `${page.sourcePath}.${getFileExtension(page.contentType)}`) + const destinationFilePath = path.join(this.config.path, `${page.destinationPath}.${getFileExtension(page.contentType)}`) await fs.move(sourceFilePath, destinationFilePath, { overwrite: true }) } } diff --git a/server/modules/storage/git/definition.yml b/server/modules/storage/git/definition.yml index db4f2ee3..fcbd0858 100644 --- a/server/modules/storage/git/definition.yml +++ b/server/modules/storage/git/definition.yml @@ -4,10 +4,12 @@ description: Git is a version control system for tracking changes in computer fi author: requarks.io logo: https://static.requarks.io/logo/git-alt.svg website: https://git-scm.com/ +isAvailable: true supportedModes: - sync - push - pull +defaultMode: sync props: authType: type: String @@ -17,42 +19,52 @@ props: enum: - 'basic' - 'ssh' + order: 1 repoUrl: type: String title: Repository URI hint: Git-compliant URI (e.g. git@github.com:org/repo.git for ssh, https://github.com/org/repo.git for basic) + order: 2 branch: type: String default: 'master' + order: 3 verifySSL: type: Boolean default: true title: Verify SSL Certificate hint: Some hosts requires SSL certificate checking to be disabled. Leave enabled for proper security. + order: 31 sshPrivateKeyPath: type: String title: SSH Private Key Path hint: SSH Authentication Only - Absolute path to the key. The key must NOT be passphrase-protected. + order: 10 basicUsername: type: String title: Username hint: Basic Authentication Only + order: 11 basicPassword: type: String title: Password / PAT hint: Basic Authentication Only + order: 12 localRepoPath: type: String title: Local Repository Path default: './data/repo' hint: 'Path where the local git repository will be created.' + order: 30 defaultEmail: type: String title: Default Author Email default: 'name@company.com' hint: 'Used as fallback in case the author of the change is not present.' + order: 20 defaultName: type: String title: Default Author Name default: 'John Smith' hint: 'Used as fallback in case the author of the change is not present.' + order: 21 diff --git a/server/modules/storage/git/storage.js b/server/modules/storage/git/storage.js index d6638040..56ddc0af 100644 --- a/server/modules/storage/git/storage.js +++ b/server/modules/storage/git/storage.js @@ -3,8 +3,6 @@ const sgit = require('simple-git/promise') const fs = require('fs-extra') const _ = require('lodash') -let repoPath = path.join(process.cwd(), 'data/repo') - /** * Get file extension based on content type */ @@ -43,72 +41,80 @@ const injectMetadata = (page) => { } module.exports = { + git: null, + repoPath: path.join(process.cwd(), 'data/repo'), async activated() { - + // not used }, async deactivated() { - + // not used }, async init() { WIKI.logger.info('(STORAGE/GIT) Initializing...') - repoPath = path.resolve(WIKI.ROOTPATH, this.config.localRepoPath) - await fs.ensureDir(repoPath) - const git = sgit(repoPath) + this.repoPath = path.resolve(WIKI.ROOTPATH, this.config.localRepoPath) + await fs.ensureDir(this.repoPath) + this.git = sgit(this.repoPath) // Initialize repo (if needed) WIKI.logger.info('(STORAGE/GIT) Checking repository state...') - const isRepo = await git.checkIsRepo() + const isRepo = await this.git.checkIsRepo() if (!isRepo) { WIKI.logger.info('(STORAGE/GIT) Initializing local repository...') - await git.init() + await this.git.init() } // Set default author - await git.raw(['config', '--local', 'user.email', this.config.defaultEmail]) - await git.raw(['config', '--local', 'user.name', this.config.defaultName]) + await this.git.raw(['config', '--local', 'user.email', this.config.defaultEmail]) + await this.git.raw(['config', '--local', 'user.name', this.config.defaultName]) // Purge existing remotes WIKI.logger.info('(STORAGE/GIT) Listing existing remotes...') - const remotes = await git.getRemotes() + const remotes = await this.git.getRemotes() if (remotes.length > 0) { WIKI.logger.info('(STORAGE/GIT) Purging existing remotes...') for(let remote of remotes) { - await git.removeRemote(remote.name) + await this.git.removeRemote(remote.name) } } // Add remote WIKI.logger.info('(STORAGE/GIT) Setting SSL Verification config...') - await git.raw(['config', '--local', '--bool', 'http.sslVerify', _.toString(this.config.verifySSL)]) + await this.git.raw(['config', '--local', '--bool', 'http.sslVerify', _.toString(this.config.verifySSL)]) switch (this.config.authType) { case 'ssh': WIKI.logger.info('(STORAGE/GIT) Setting SSH Command config...') - await git.addConfig('core.sshCommand', `ssh -i "${this.config.sshPrivateKeyPath}" -o StrictHostKeyChecking=no`) + await this.git.addConfig('core.sshCommand', `ssh -i "${this.config.sshPrivateKeyPath}" -o StrictHostKeyChecking=no`) WIKI.logger.info('(STORAGE/GIT) Adding origin remote via SSH...') - await git.addRemote('origin', this.config.repoUrl) + await this.git.addRemote('origin', this.config.repoUrl) break default: WIKI.logger.info('(STORAGE/GIT) Adding origin remote via HTTPS...') - await git.addRemote('origin', `https://${this.config.basicUsername}:${this.config.basicPassword}@${this.config.repoUrl}`) + await this.git.addRemote('origin', `https://${this.config.basicUsername}:${this.config.basicPassword}@${this.config.repoUrl}`) break } // Fetch updates for remote WIKI.logger.info('(STORAGE/GIT) Fetch updates from remote...') - await git.raw(['remote', 'update', 'origin']) + await this.git.raw(['remote', 'update', 'origin']) // Checkout branch - const branches = await git.branch() + const branches = await this.git.branch() if (!_.includes(branches.all, this.config.branch) && !_.includes(branches.all, `remotes/origin/${this.config.branch}`)) { throw new Error('Invalid branch! Make sure it exists on the remote first.') } WIKI.logger.info(`(STORAGE/GIT) Checking out branch ${this.config.branch}...`) - await git.checkout(this.config.branch) + await this.git.checkout(this.config.branch) + // Perform initial sync + await this.sync() + + WIKI.logger.info('(STORAGE/GIT) Initialization completed.') + }, + async sync() { // Pull rebase if (_.includes(['sync', 'pull'], this.mode)) { WIKI.logger.info(`(STORAGE/GIT) Performing pull rebase from origin on branch ${this.config.branch}...`) - await git.pull('origin', this.config.branch, ['--rebase']) + await this.git.pull('origin', this.config.branch, ['--rebase']) } // Push @@ -118,50 +124,48 @@ module.exports = { if (this.mode === 'push') { pushOpts.push('--force') } - await git.push('origin', this.config.branch, pushOpts) + await this.git.push('origin', this.config.branch, pushOpts) } - - WIKI.logger.info('(STORAGE/GIT) Initialization completed.') }, - async created() { - const fileName = `${this.page.path}.${getFileExtension(this.page.contentType)}` - const filePath = path.join(repoPath, fileName) - await fs.outputFile(filePath, injectMetadata(this.page), 'utf8') + async created(page) { + WIKI.logger.info(`(STORAGE/GIT) Committing new file ${page.path}...`) + const fileName = `${page.path}.${getFileExtension(page.contentType)}` + const filePath = path.join(this.repoPath, fileName) + await fs.outputFile(filePath, injectMetadata(page), 'utf8') - const git = sgit(repoPath) - await git.add(`./${fileName}`) - await git.commit(`docs: create ${this.page.path}`, fileName, { - '--author': `"${this.page.authorName} <${this.page.authorEmail}>"` + await this.git.add(`./${fileName}`) + await this.git.commit(`docs: create ${page.path}`, fileName, { + '--author': `"${page.authorName} <${page.authorEmail}>"` }) }, - async updated() { - const fileName = `${this.page.path}.${getFileExtension(this.page.contentType)}` - const filePath = path.join(repoPath, fileName) - await fs.outputFile(filePath, injectMetadata(this.page), 'utf8') + async updated(page) { + WIKI.logger.info(`(STORAGE/GIT) Committing updated file ${page.path}...`) + const fileName = `${page.path}.${getFileExtension(page.contentType)}` + const filePath = path.join(this.repoPath, fileName) + await fs.outputFile(filePath, injectMetadata(page), 'utf8') - const git = sgit(repoPath) - await git.add(`./${fileName}`) - await git.commit(`docs: update ${this.page.path}`, fileName, { - '--author': `"${this.page.authorName} <${this.page.authorEmail}>"` + await this.git.add(`./${fileName}`) + await this.git.commit(`docs: update ${page.path}`, fileName, { + '--author': `"${page.authorName} <${page.authorEmail}>"` }) }, - async deleted() { - const fileName = `${this.page.path}.${getFileExtension(this.page.contentType)}` + async deleted(page) { + WIKI.logger.info(`(STORAGE/GIT) Committing removed file ${page.path}...`) + const fileName = `${page.path}.${getFileExtension(page.contentType)}` - const git = sgit(repoPath) - await git.rm(`./${fileName}`) - await git.commit(`docs: delete ${this.page.path}`, fileName, { - '--author': `"${this.page.authorName} <${this.page.authorEmail}>"` + await this.git.rm(`./${fileName}`) + await this.git.commit(`docs: delete ${page.path}`, fileName, { + '--author': `"${page.authorName} <${page.authorEmail}>"` }) }, - async renamed() { - const sourceFilePath = `${this.page.sourcePath}.${getFileExtension(this.page.contentType)}` - const destinationFilePath = `${this.page.destinationPath}.${getFileExtension(this.page.contentType)}` + async renamed(page) { + WIKI.logger.info(`(STORAGE/GIT) Committing file move from ${page.sourcePath} to ${page.destinationPath}...`) + const sourceFilePath = `${page.sourcePath}.${getFileExtension(page.contentType)}` + const destinationFilePath = `${page.destinationPath}.${getFileExtension(page.contentType)}` - const git = sgit(repoPath) - await git.mv(`./${sourceFilePath}`, `./${destinationFilePath}`) - await git.commit(`docs: rename ${this.page.sourcePath} to ${destinationFilePath}`, destinationFilePath, { - '--author': `"${this.page.authorName} <${this.page.authorEmail}>"` + await this.git.mv(`./${sourceFilePath}`, `./${destinationFilePath}`) + await this.git.commit(`docs: rename ${page.sourcePath} to ${destinationFilePath}`, destinationFilePath, { + '--author': `"${page.authorName} <${page.authorEmail}>"` }) } } diff --git a/yarn.lock b/yarn.lock index 6c183bf6..0323427c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3431,16 +3431,21 @@ copy-webpack-plugin@4.6.0: p-limit "^1.0.0" serialize-javascript "^1.4.0" -core-js@2.6.3: +core-js@2.6.3, core-js@^2.4.0, core-js@^2.5.7: version "2.6.3" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.3.tgz#4b70938bdffdaf64931e66e2db158f0892289c49" integrity sha512-l00tmFFZOBHtYhN4Cz7k32VM7vTn3rE2ANjQDxdEN6zmXZ/xq1jQuutnmHvMG1ZJ7xd72+TA5YpUK8wz3rWsfQ== -core-js@3.0.0-beta.11, core-js@3.0.0-beta.3, core-js@^1.0.0, core-js@^2.4.0, core-js@^2.5.7: +core-js@3.0.0-beta.11, core-js@3.0.0-beta.3: version "3.0.0-beta.11" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.0.0-beta.11.tgz#dac9d000f562194cc8bc7fe142be0d70c8c910f8" integrity sha512-Q1gGAIqiFfR8ZqjrJw4gzjDrP2JsLacNQzUKUfqvcpg974bCQrPaT4a+HNbznQm5DabCIKw9fGQotj0dgdsMRg== +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -6290,23 +6295,7 @@ invert-kv@^2.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== -ioredis@4.5.1: - version "4.5.1" - resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.5.1.tgz#b1c1c1657697caa3a617acb9370e3c0694edb775" - integrity sha512-p1BblrFZdb5Oc5EBsEb4EoycDqn7xi/NTNT4bDvo/w6B08eMNO1E7RAOOEA1GAb65+8Hbs2LgUyz3cZOTiP3xg== - dependencies: - cluster-key-slot "^1.0.6" - debug "^3.1.0" - denque "^1.1.0" - flexbuffer "0.0.6" - lodash.defaults "^4.2.0" - lodash.flatten "^4.4.0" - redis-commands "1.4.0" - redis-errors "^1.2.0" - redis-parser "^3.0.0" - standard-as-callback "^1.0.0" - -ioredis@^4.5.1: +ioredis@4.6.2, ioredis@^4.5.1: version "4.6.2" resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.6.2.tgz#840847117fe0190a9309085847311a07183fc385" integrity sha512-zlc/LeoeriHTXm5z3rakPcfRcUV9x+xr0E+7/L7KH0D5z7sI5ngEQWR2RUxnwFcxUcCkvrXMztRIdBP3DhqMAQ== @@ -13159,10 +13148,10 @@ vue-loader@15.6.2: vue-hot-reload-api "^2.3.0" vue-style-loader "^4.1.0" -vue-material-design-icons@2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/vue-material-design-icons/-/vue-material-design-icons-2.6.0.tgz#44de179ea85a31424cea1b1da5415f79cfce2743" - integrity sha512-ueylNTZJk9jXGFSlSZ6iVIaw+r/uTMJDerG2X3ZAMz4yK7O/BAS1I/THVLA+mINyb4I7SPHmBt/msSYcdKSv0A== +vue-material-design-icons@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/vue-material-design-icons/-/vue-material-design-icons-3.0.0.tgz#db387aa604e12523180d17db315126f6311d3e13" + integrity sha512-Y9dLUDuU0X3BjdeYjaHaWQ1UX4LMKT1kPIGApVhJmIs+HKY6xf57JLuba82YK+8iRlkJ/QYXbSV9oLrugn6M3w== vue-moment@4.0.0: version "4.0.0"