feat: vulnerability audit widget

This commit is contained in:
Guillaume Chau
2019-04-09 04:28:37 +02:00
parent c4bd1abea8
commit fbfbd29be5
10 changed files with 523 additions and 134 deletions

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="false" class="status-widget">
<div v-if="implemented" class="status-widget">
<div class="header">
<div class="icon-wrapper">
<ItemLogo
@@ -14,9 +14,10 @@
<div class="last-updated">
<template v-if="status.lastUpdate">
<div class="label">
{{ $t('org.vue.widgets.status-widget.last-updated') }}
{{ message || $t('org.vue.widgets.status-widget.last-updated') }}
</div>
<VueTimeago
v-if="!message"
:datetime="status.lastUpdate"
:auto-update="60"
/>
@@ -73,6 +74,17 @@ export default {
status: {
type: Object,
required: true
},
message: {
type: String,
default: null
},
// TODO remove
implemented: {
type: Boolean,
default: false
}
},

View File

@@ -2,12 +2,23 @@
<div class="vulnerability">
<StatusWidget
v-if="status"
:icon="icons[status.status]"
:icon-class="iconClasses[status.status]"
:icon="status.status === 'attention' ? severity.icon : icons[status.status]"
:icon-class="status.status === 'attention' ? severity.class : iconClasses[status.status]"
:title="$t(`org.vue.widgets.vulnerability.messages.${status.status}`, { n: status.count })"
:status="status"
:message="status.message"
implemented
@check="checkForUpdates()"
/>
>
<template #more-actions>
<VueButton
v-if="status.status !== 'loading'"
:label="$t('org.vue.widgets.vulnerability.recheck')"
icon-left="refresh"
@click="refresh()"
/>
</template>
</StatusWidget>
</div>
</template>
@@ -18,8 +29,28 @@ import StatusWidget from './StatusWidget.vue'
const UPDATES_ICONS = {
'ok': 'verified_user',
'loading': 'hourglass_full',
'attention': 'error'
'loading': 'hourglass_empty',
'attention': 'error',
'error': 'error'
}
export const SEVERITIES = {
critical: {
class: 'danger',
icon: 'new_releases'
},
high: {
class: 'danger',
icon: 'error'
},
moderate: {
class: 'warning',
icon: 'error'
},
low: {
class: '',
icon: 'error'
}
}
export default {
@@ -33,14 +64,20 @@ export default {
})
},
computed: {
severity () {
return this.status && SEVERITIES[this.status.severity]
}
},
created () {
this.icons = UPDATES_ICONS
this.iconClasses = UPDATES_ICON_CLASSES
},
methods: {
checkForUpdates () {
// TODO
refresh () {
this.$callPluginAction('org.vue.widgets.actions.check-vunerability')
}
}
}

View File

@@ -12,15 +12,48 @@
<div class="title">
{{ $t('org.vue.widgets.vulnerability.messages.attention', { n: details.vulnerabilities.length }) }}
</div>
<div class="summary">
<div
v-for="(severity, key) of severities"
:key="key"
:class="`severity-${severity.class}`"
class="summary-item"
>
<VueIcon
:icon="severity.icon"
:class="severity.class"
/>
<span class="count">{{ details.summary[key] }}</span>
{{ $t(`org.vue.widgets.vulnerability.severity.${key}`) }}
</div>
</div>
</div>
<div class="items">
<VulnerabilityItem
v-for="(item, index) of details.vulnerabilities"
:key="index"
:item="item"
/>
</div>
<transition name="vue-ui-fade">
<DynamicScroller
v-if="showList"
ref="scroller"
class="items"
:items="details.vulnerabilities"
:min-item-size="75"
v-slot="{ item, active }"
>
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[
showMoreParentsMap[item.id]
]"
>
<VulnerabilityItem
:item="item"
:show-more-parents="showMoreParentsMap[item.id]"
@toggle-more-parents="$set(showMoreParentsMap, item.id, !showMoreParentsMap[item.id])"
/>
</DynamicScrollerItem>
</DynamicScroller>
</transition>
</template>
</div>
</template>
@@ -28,6 +61,8 @@
<script>
import VulnerabilityItem from './VulnerabilityItem.vue'
import { SEVERITIES } from './Vulnerability.vue'
export default {
components: {
VulnerabilityItem
@@ -37,6 +72,24 @@ export default {
return mapSharedData('org.vue.widgets.vulnerability.', {
details: 'details'
})
},
data () {
return {
showList: false,
showMoreParentsMap: {}
}
},
created () {
this.severities = SEVERITIES
},
mounted () {
// Animation breaks scroller item sizes
setTimeout(() => {
this.showList = true
}, 200)
}
}
</script>
@@ -45,9 +98,32 @@ export default {
@import "~@vue/cli-ui/src/style/imports"
.vulnerability-details
overflow-x hidden
overflow-y auto
v-box()
.pane-toolbar
padding-bottom $padding-item
.summary
display flex
padding-right 12px
.summary-item
display flex
align-items center
margin-left 16px
.vue-ui-icon,
.count
margin-right 3px
.count
font-weight bold
.severity-danger
color $vue-ui-color-danger
.severity-warning
color $vue-ui-color-warning
.items
flex 1
</style>

View File

@@ -2,84 +2,114 @@
<div class="vulnerability-item list-item">
<div class="wrapper">
<ItemLogo
image="error"
:image="severity.icon"
:class="severity.class"
/>
<ListItemInfo
:link="item.moreInfo"
>
<template slot="name">
<span class="name">{{ item.name }}</span>
<span class="version">{{ item.version }}</span>
</template>
<template slot="description">
<span
class="severity"
:class="severity.class"
>
{{ $t(`org.vue.widgets.vulnerability.severity.${item.severity}`) }}
</span>
<span class="message">
{{ item.message }}
</span>
</template>
</ListItemInfo>
<div class="parents">
<div v-if="!item.parents" class="vue-ui-empty">
{{ $t('org.vue.widgets.vulnerability.direct-dep') }}
</div>
<template v-else>
<div
v-for="(parent, index) of item.parents"
:key="index"
class="parent"
>
<span class="name">{{ parent.name }}</span>
<span class="version">{{ parent.version }}</span>
<VueIcon
icon="chevron_right"
class="separator-icon medium"
/>
</div>
<div class="parent current">
<div class="main-infos">
<ListItemInfo
:link="item.moreInfo"
>
<template slot="name">
<span class="name">{{ item.name }}</span>
<span class="version">{{ item.version }}</span>
</template>
<template slot="description">
<span
class="severity"
:class="severity.class"
>
{{ $t(`org.vue.widgets.vulnerability.severity.${item.severity}`) }}
</span>
<span class="title" v-tooltip="item.message">
{{ item.title }}
</span>
</template>
</ListItemInfo>
<div class="info-versions">
<div class="info-version">
<VueIcon icon="error"/>
{{ $t('org.vue.widgets.vulnerability.versions.vulnerable') }}
<span class="version">{{ item.versions.vulnerable }}</span>
</div>
</template>
<div class="info-version">
<VueIcon icon="check_circle"/>
{{ $t('org.vue.widgets.vulnerability.versions.patched') }}
<span class="version">{{ item.versions.patched }}</span>
</div>
</div>
<div class="recommendation vue-ui-text success banner">
<VueIcon icon="arrow_forward" class="big"/>
{{ item.recommendation }}
</div>
</div>
<div class="parents-list">
<div
v-for="(parents, index) of displayedParents"
:key="index"
class="parents"
>
<div v-if="!parents.length" class="vue-ui-empty">
{{ $t('org.vue.widgets.vulnerability.direct-dep') }}
</div>
<template v-else>
<div
v-for="(parent, index) of parents"
:key="index"
class="parent"
>
<span class="name">{{ parent.name }}</span>
<VueIcon
icon="chevron_right"
class="separator-icon medium"
/>
</div>
<div class="parent current">
<span class="name">{{ item.name }}</span>
</div>
</template>
</div>
<div v-if="item.parents.length > 3" class="show-more">
<VueButton
:icon-left="showMoreParents ? 'expand_less' : 'expand_more'"
class="flat"
@click="$emit('toggle-more-parents')"
>
{{ $t(`org.vue.common.show-${showMoreParents ? 'less' : 'more'}`) }}
</VueButton>
</div>
</div>
</div>
</div>
</template>
<script>
const SEVERITIES = {
high: {
class: 'danger'
},
medium: {
class: 'warning'
},
low: {
class: ''
}
}
import { SEVERITIES } from './Vulnerability.vue'
export default {
props: {
item: {
type: Object,
required: true
},
showMoreParents: {
type: Boolean
}
},
computed: {
severity () {
return SEVERITIES[this.item.severity]
},
displayedParents () {
return this.showMoreParents ? this.item.parents : this.item.parents.slice(0, 3)
}
}
}
@@ -94,13 +124,16 @@ export default {
.wrapper
h-box()
box-center()
.list-item-info
.main-infos
flex 1
.name
font-weight bold
.name
font-weight bold
.title
cursor help
border-bottom 1px dotted
.version
margin-left 4px
@@ -108,14 +141,27 @@ export default {
font-size .9em
.severity
color $color-text-light
color $vue-ui-color-dark
.vue-ui-dark-mode &
color $vue-ui-color-light
&.danger
color $vue-ui-color-danger
&.warning
color $vue-ui-color-warning
.parents-list
margin-left 12px
width 50%
.parents
h-box()
margin 12px 0
flex-wrap wrap
width 100%
.parent
&:not(:first-child)
opacity .7
.separator-icon
>>> svg
@@ -123,4 +169,24 @@ export default {
.vue-ui-empty
padding 0
.info-versions
margin-top 4px
.info-version
display flex
align-items baseline
margin-top 2px
.vue-ui-icon
margin-right 4px
opacity .5
.version
margin-left 8px
.recommendation
margin 8px 0
display inline-flex
align-items center
</style>

View File

@@ -4,7 +4,9 @@
"common": {
"close": "Close",
"back": "Go back",
"more-info": "More info"
"more-info": "More info",
"show-more": "Show more",
"show-less": "Show less"
},
"components": {
"client-addon-component": {
@@ -811,15 +813,22 @@
"description": "Check for known vulnerabilities in your project dependencies",
"messages": {
"ok": "No vulnerability found",
"loading": "Checking security reports...",
"attention": "{n} vulnerabilities found"
"loading": "Auditing project security...",
"attention": "{n} vulnerabilities found",
"error": "Couldn't check for vulnerability"
},
"severity": {
"critical": "Critical severity",
"high": "High severity",
"medium": "Medium severity",
"moderate": "Medium severity",
"low": "Low severity"
},
"direct-dep": "Direct dependency"
"direct-dep": "Direct dependency",
"versions": {
"vulnerable": "Vulnerable versions:",
"patched": "Patched versions:"
},
"recheck": "Check again"
},
"run-task": {
"title": "Run task",

View File

@@ -59,6 +59,7 @@
"semver": "^5.5.0",
"shortid": "^2.2.11",
"vue-cli-plugin-apollo": "^0.19.1",
"vue-virtual-scroller": "^1.0.0-rc.2",
"watch": "^1.0.2"
},
"devDependencies": {

View File

@@ -13,6 +13,8 @@ import SetSize from './util/set-size'
import Focus from './util/focus'
import Bus from './util/bus'
import AnsiColors from './util/ansi-colors'
import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
Vue.use(InstantSearch)
Vue.use(VueMeta)
@@ -48,3 +50,5 @@ Vue.mixin(ClientState)
Vue.directive('set-size', SetSize)
Vue.directive('focus', Focus)
Vue.use(VueVirtualScroller)

View File

@@ -0,0 +1,121 @@
const { hasProjectYarn, execa } = require('@vue/cli-shared-utils')
const severity = {
critical: 0,
high: 1,
moderate: 2,
low: 3
}
exports.auditProject = async function (cwd) {
try {
if (hasProjectYarn) {
const child = await execa('yarn', [
'audit',
'--json',
'--non-interactive',
'--no-progress'
], {
cwd,
reject: false
})
const data = child.stdout
let auditAdvisories = []
const ids = {}
const lines = data.split(`\n`).filter(l => l.trim()).map(l => JSON.parse(l))
for (const line of lines) {
if (line.type === 'auditAdvisory') {
if (!ids[line.data.advisory.id]) {
auditAdvisories.push(line)
ids[line.data.advisory.id] = true
}
}
}
const details = {
vulnerabilities: [],
summary: {
critical: 0,
high: 0,
moderate: 0,
low: 0
}
}
auditAdvisories = auditAdvisories.sort((a, b) => severity[a.data.advisory.severity] - severity[b.data.advisory.severity])
let id = 0
for (const { data: { advisory } } of auditAdvisories) {
for (const finding of advisory.findings) {
// const [finding] = advisory.findings
const detail = {
id: id++,
name: advisory.module_name,
version: finding.version,
parents: finding.paths.sort(
(a, b) => a.length - b.length
).map(
parents => parents.split('>').slice(0, parents.length - 2).map(p => ({
name: p
}))
),
moreInfo: advisory.url,
severity: advisory.severity,
title: advisory.title,
message: advisory.overview,
versions: {
vulnerable: advisory.vulnerable_versions,
patched: advisory.patched_versions
},
recommendation: advisory.recommendation
}
details.vulnerabilities.push(detail)
details.summary[advisory.severity]++
}
}
const status = {
status: 'ok',
count: details.vulnerabilities.length,
message: null
}
if (status.count) {
status.status = 'attention'
}
for (const n in details.summary) {
if (details.summary) {
status.severity = n
break
}
}
return {
status,
details
}
} else {
// TODO NPM audit
return {
status: {
status: 'error',
message: 'Not implemented for NPM projects yet'
},
details: null
}
}
} catch (e) {
return {
status: {
status: 'error',
message: e.message
},
details: null
}
}
}

View File

@@ -96,51 +96,16 @@ module.exports = api => {
// Vulnerability check
let lastAudit = null
setSharedData('vulnerability.status', {
status: 'attention',
lastUpdate: Date.now(),
count: 3
status: 'loading',
lastUpdate: lastAudit,
count: 0,
message: null
})
setSharedData('vulnerability.details', {
vulnerabilities: [
{
name: 'bquery',
version: '0.1.2',
parents: [
{
name: 'draphql',
version: '1.8.7'
}
],
moreInfo: 'https://vuejs.org/',
severity: 'high',
message: 'Regular Expression Denial of Service'
},
{
name: 'leff-pad',
version: '5.3.9-alpha.1',
parents: [
{
name: 'view-ssr',
version: '3.0.0-alpha.12'
},
{
name: 'elpress',
version: '1.17.3'
}
],
moreInfo: 'https://vuejs.org/',
severity: 'medium',
message: 'Prototype Pollution'
},
{
name: 'yterm',
version: '4.6.2',
moreInfo: 'https://vuejs.org/',
severity: 'low',
message: 'Regular Expression Denial of Service'
}
]
vulnerabilities: [],
summary: {}
})
registerWidget({
id: 'vulnerability',
@@ -155,6 +120,24 @@ module.exports = api => {
maxHeight: 1,
maxCount: 1
})
async function checkVulnerability (params) {
setSharedData('vulnerability.status', {
status: 'loading',
lastUpdate: lastAudit,
count: 0,
message: null
})
const { auditProject } = require('./utils/audit')
const { status, details } = await auditProject(api.getCwd())
status.lastUpdate = lastAudit = Date.now()
setSharedData('vulnerability.status', status)
setSharedData('vulnerability.details', details)
}
onAction('actions.check-vunerability', checkVulnerability)
checkVulnerability()
// Run task

100
yarn.lock
View File

@@ -3110,11 +3110,36 @@ babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
esutils "^2.0.2"
js-tokens "^3.0.2"
babel-core@7.0.0-bridge.0, babel-core@^6.0.0:
babel-core@7.0.0-bridge.0:
version "7.0.0-bridge.0"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece"
integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==
babel-core@^6.0.0, babel-core@^6.26.0:
version "6.26.3"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==
dependencies:
babel-code-frame "^6.26.0"
babel-generator "^6.26.0"
babel-helpers "^6.24.1"
babel-messages "^6.23.0"
babel-register "^6.26.0"
babel-runtime "^6.26.0"
babel-template "^6.26.0"
babel-traverse "^6.26.0"
babel-types "^6.26.0"
babylon "^6.18.0"
convert-source-map "^1.5.1"
debug "^2.6.9"
json5 "^0.5.1"
lodash "^4.17.4"
minimatch "^3.0.4"
path-is-absolute "^1.0.1"
private "^0.1.8"
slash "^1.0.0"
source-map "^0.5.7"
babel-eslint@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed"
@@ -3146,7 +3171,7 @@ babel-extract-comments@^1.0.0:
dependencies:
babylon "^6.18.0"
babel-generator@^6.18.0:
babel-generator@^6.18.0, babel-generator@^6.26.0:
version "6.26.1"
resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==
@@ -3160,6 +3185,14 @@ babel-generator@^6.18.0:
source-map "^0.5.7"
trim-right "^1.0.1"
babel-helpers@^6.24.1:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=
dependencies:
babel-runtime "^6.22.0"
babel-template "^6.24.1"
babel-jest@^23.6.0:
version "23.6.0"
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-23.6.0.tgz#a644232366557a2240a0c083da6b25786185a2f1"
@@ -3246,6 +3279,19 @@ babel-preset-jest@^23.2.0:
babel-plugin-jest-hoist "^23.2.0"
babel-plugin-syntax-object-rest-spread "^6.13.0"
babel-register@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
integrity sha1-btAhFz4vy0htestFxgCahW9kcHE=
dependencies:
babel-core "^6.26.0"
babel-runtime "^6.26.0"
core-js "^2.5.0"
home-or-tmp "^2.0.0"
lodash "^4.17.4"
mkdirp "^0.5.1"
source-map-support "^0.4.15"
babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
@@ -3254,7 +3300,7 @@ babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
core-js "^2.4.0"
regenerator-runtime "^0.11.0"
babel-template@^6.16.0, babel-template@^6.26.0:
babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=
@@ -4770,7 +4816,7 @@ conventional-recommended-bump@^4.0.4:
meow "^4.0.0"
q "^1.5.1"
convert-source-map@^1.1.0, convert-source-map@^1.4.0:
convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1:
version "1.6.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==
@@ -4823,7 +4869,7 @@ core-js@^2.4.0, core-js@^2.5.7:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.3.tgz#4b70938bdffdaf64931e66e2db158f0892289c49"
integrity sha512-l00tmFFZOBHtYhN4Cz7k32VM7vTn3rE2ANjQDxdEN6zmXZ/xq1jQuutnmHvMG1ZJ7xd72+TA5YpUK8wz3rWsfQ==
core-js@^2.6.5:
core-js@^2.5.0, core-js@^2.6.5:
version "2.6.5"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895"
integrity sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==
@@ -8295,6 +8341,14 @@ hogan.js@^3.0.2:
mkdirp "0.3.0"
nopt "1.0.10"
home-or-tmp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg=
dependencies:
os-homedir "^1.0.0"
os-tmpdir "^1.0.1"
homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc"
@@ -12169,7 +12223,7 @@ os-name@^3.0.0:
macos-release "^2.0.0"
windows-release "^3.1.0"
os-tmpdir@^1.0.0, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
@@ -12482,7 +12536,7 @@ path-exists@^3.0.0:
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
path-is-absolute@^1.0.0:
path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
@@ -13708,7 +13762,7 @@ punycode@^1.2.4, punycode@^1.4.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
puppeteer@1.11.0, puppeteer@^1.11.0:
puppeteer@^1.11.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-1.11.0.tgz#63cdbe12b07275cd6e0b94bce41f3fcb20305770"
integrity sha512-iG4iMOHixc2EpzqRV+pv7o3GgmU2dNYEMkvKwSaQO/vMZURakwSOn/EYJ6OIRFYOque1qorzIBvrytPIQB3YzQ==
@@ -14605,6 +14659,11 @@ schema-utils@^1.0.0:
ajv-errors "^1.0.0"
ajv-keywords "^3.1.0"
scrollparent@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/scrollparent/-/scrollparent-2.0.1.tgz#715d5b9cc57760fb22bdccc3befb5bfe06b1a317"
integrity sha1-cV1bnMV3YPsivczDvvtb/gaxoxc=
sec@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/sec/-/sec-1.0.0.tgz#033d60a3ad20ecf2e00940d14f97823465774335"
@@ -15005,6 +15064,13 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2:
source-map-url "^0.4.0"
urix "^0.1.0"
source-map-support@^0.4.15:
version "0.4.18"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==
dependencies:
source-map "^0.5.6"
source-map-support@^0.5.0, source-map-support@^0.5.6, source-map-support@~0.5.6, source-map-support@~0.5.9:
version "0.5.10"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c"
@@ -16591,6 +16657,11 @@ vue-apollo@^3.0.0-beta.25:
chalk "^2.4.1"
throttle-debounce "^2.0.0"
vue-class-component@^6.0.0:
version "6.3.2"
resolved "https://registry.yarnpkg.com/vue-class-component/-/vue-class-component-6.3.2.tgz#e6037e84d1df2af3bde4f455e50ca1b9eec02be6"
integrity sha512-cH208IoM+jgZyEf/g7mnFyofwPDJTM/QvBNhYMjqGB8fCsRyTf68rH2ISw/G20tJv+5mIThQ3upKwoL4jLTr1A==
vue-class-component@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/vue-class-component/-/vue-class-component-7.0.1.tgz#7af2225c600667c7042b60712eefdf41dfc6de63"
@@ -16749,7 +16820,7 @@ vue-meta@^1.5.0:
lodash.uniqueid "^4.0.1"
object-assign "^4.1.1"
vue-observe-visibility@^0.4.1:
vue-observe-visibility@^0.4.1, vue-observe-visibility@^0.4.3:
version "0.4.3"
resolved "https://registry.yarnpkg.com/vue-observe-visibility/-/vue-observe-visibility-0.4.3.tgz#b2694a83c94b274f566e03a497df51540e2daedc"
integrity sha512-YyyO3a5OUkgpmC0NEf+xWJR0jVdFWzVbKRDzUumOVMhfr3+jxXEycYNHCM3rEO5lcj3ZNJpDomZEYEx0Wqqh9A==
@@ -16776,7 +16847,7 @@ vue-router@^3.0.1, vue-router@^3.0.2:
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.2.tgz#dedc67afe6c4e2bc25682c8b1c2a8c0d7c7e56be"
integrity sha512-opKtsxjp9eOcFWdp6xLQPLmRGgfM932Tl56U9chYTnoWqKxQ8M20N7AkdEbM5beUh6wICoFGYugAX9vQjyJLFg==
vue-server-renderer@^2.5.16, vue-server-renderer@^2.6.7:
vue-server-renderer@^2.5.16:
version "2.6.7"
resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.6.7.tgz#ff40f69a439da8993a6e171b7d58575bfbeefb79"
integrity sha512-CVtGR+bE63y4kyIeOcCEF2UNKquSquFQAsTHZ5R1cGM4L4Z0BXgAUEcngTOy8kN+tubt3c1zpRvbrok/bHKeDg==
@@ -16823,6 +16894,15 @@ vue-timeago@^5.0.0:
dependencies:
date-fns "^1.29.0"
vue-virtual-scroller@^1.0.0-rc.2:
version "1.0.0-rc.2"
resolved "https://registry.yarnpkg.com/vue-virtual-scroller/-/vue-virtual-scroller-1.0.0-rc.2.tgz#b03828496c87889641bdacc75510e5b1bf5bda7f"
integrity sha512-4YFx1a+QDP4f6HW/HBI/qHcmSTlh7BMH6IjEH8WC3ylt499cErl0LpvLLAx9yo3c6NtuK/XvjYXi0vvdxFB5dw==
dependencies:
scrollparent "^2.0.1"
vue-observe-visibility "^0.4.3"
vue-resize "^0.4.5"
vue@^2.5.16, vue@^2.6.6, vue@^2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.7.tgz#254f188e7621d2d19ee28d0c0442c6d21b53ae2d"