continue admin area and api

This commit is contained in:
wodka 2019-09-03 17:26:57 +02:00
parent b4e9f2ae9b
commit 8b345515ab
32 changed files with 602 additions and 55 deletions

View File

@ -2,8 +2,9 @@ import { ApiModelProperty } from '@nestjs/swagger';
export class AuthJwtDto {
@ApiModelProperty()
access_token: string;
token: {
accessToken: string;
@ApiModelProperty()
refresh_token: string;
refreshToken: string;
}
}

View File

@ -51,20 +51,22 @@ export class AuthService {
const payload = {
id: user.id,
username: user.username,
roles: user.roles,
scope: user.roles,
};
return {
access_token: this.jwtService.sign(payload),
// TODO add refresh token invalidation uppon usage! They should only work once
refresh_token: this.jwtService.sign(
{
...payload,
refresh: true
},
{
expiresIn: '30days',
}
),
token: {
accessToken: this.jwtService.sign(payload),
// TODO add refresh token invalidation uppon usage! They should only work once
refreshToken: this.jwtService.sign(
{
...payload,
refresh: true
},
{
expiresIn: '30days',
}
),
}
};
}
}

View File

@ -13,8 +13,6 @@ export class RegisterService {
) {}
async register (username: string, email: string, password: string): Promise<void> {
// TODO actually create user
let user = new User()
user.email = email
user.username = username

View File

@ -12,9 +12,12 @@ export class FormController {
@Get()
@UseGuards(AuthGuard('jwt'))
async list(@Request() req): Promise<any> {
throw new NotImplementedException()
async list(@Request() req): Promise<FormDto[]> {
// TODO calculate total forms, add for pagination
const results = await this.formService.findBy({})
return results.map(form => new FormDto(form))
}
@Post()
@UseGuards(AuthGuard('jwt'))
async create(@Request() req): Promise<FormDto> {

View File

@ -8,10 +8,32 @@ export class FormDto {
@ApiModelProperty()
title: string;
fields: [];
@ApiModelProperty()
live: boolean;
@ApiModelProperty()
created: Date;
@ApiModelProperty()
lastModified: Date;
@ApiModelProperty()
fields: any;
@ApiModelProperty()
info: {
responses: number;
}
constructor(partial: Partial<Form>) {
this.id = partial._id.toString();
this.id = partial._id.toString()
this.title = partial.title
this.live = partial.isLive
this.created = partial.created
this.lastModified = partial.lastModified
this.fields = partial.form_fields
this.info = {
responses: 0 // TODO
}
}
}

View File

@ -17,8 +17,10 @@ export class Form extends Typegoose {
})
readonly title: string;
@prop()
readonly created: any;
@prop()
readonly lastModified: any;
@prop({

View File

@ -2,6 +2,7 @@ import {Injectable} from '@nestjs/common';
import { InjectModel } from 'nestjs-typegoose';
import { ModelType } from 'typegoose';
import {Form} from "../models/form.model"
import {User} from "../../user/models/user.model"
@Injectable()
export class FormService {
@ -10,4 +11,8 @@ export class FormService {
async findById(id: string): Promise<Form> {
return await this.formModel.findById(id).exec()
}
async findBy(conditions): Promise<Form[]> {
return await this.formModel.find(conditions).exec()
}
}

View File

@ -6,10 +6,7 @@ import { useContainer } from "class-validator"
const pkg = require('../package.json')
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// app.enableCors({ origin: '*' });
// app.getHttpAdapter().options('*', cors());
const app = await NestFactory.create(AppModule, { cors: true });
useContainer(app.select(AppModule), { fallbackOnErrors: true });

View File

@ -3,6 +3,7 @@ import { AuthGuard } from '@nestjs/passport';
import { ApiBearerAuth, ApiImplicitQuery, ApiResponse, ApiUseTags } from "@nestjs/swagger"
import { UserService } from "../services/user.service"
import { UserDto } from "../dto/user.dto"
import {FormDto} from "../../form/dto/form.dto"
@ApiUseTags('users')
@ApiBearerAuth()
@ -12,8 +13,10 @@ export class UserController {
@Get()
@UseGuards(AuthGuard('jwt'))
async list(@Request() req): Promise<any> {
throw new NotImplementedException()
async list(@Request() req): Promise<UserDto[]> {
// TODO calculate total forms, add for pagination
const results = await this.userService.findBy({})
return results.map(form => new UserDto(form))
}
@ApiResponse({ status: 200, description: 'User Object', type: UserDto})

View File

@ -8,8 +8,20 @@ export class UserDto {
@ApiModelProperty()
username: string;
@ApiModelProperty()
email: string;
@ApiModelProperty()
roles: string[];
@ApiModelProperty()
created: Date;
constructor(partial: Partial<User>) {
this.id = partial._id.toString();
this.id = partial._id.toString()
this.username = partial.username
this.email = partial.email
this.roles = partial.roles
this.created = partial.created
}
}

View File

@ -33,6 +33,10 @@ export class UserService {
return await this.userModel.findOne(conditions).exec()
}
async findBy(conditions): Promise<User[]> {
return await this.userModel.find(conditions).exec()
}
async save(user: User): Promise<User> {
let model = new this.userModel(user)
return await model.save()

View File

@ -1,7 +1,9 @@
$accent: #fae596;
$primary: #3fb0ac;
html {
@import url('https://fonts.googleapis.com/icon?family=Material+Icons');
1html {
font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 16px;
@ -13,6 +15,23 @@ html {
box-sizing: border-box;
}
.bg-primary {
&.navbar-dark,
&.dark {
background-color: #173e43 !important;
}
&.dark {
a {
color: #dddfd4;
&:hover {
color: #fae596;
}
}
}
}
.btn-primary {
color: #fff;
background-color: $primary;

View File

@ -1,5 +1,57 @@
<template>
<div>
<b-navbar variant="primary" toggleable="sm" type="dark">
<b-navbar-brand href="https://ohmyform.com">
<img src="../assets/img/logo_white_small.png" style="height: 27px" />
</b-navbar-brand>
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
<b-collapse id="nav-collapse" is-nav>
<b-navbar-nav>
<b-nav-item
:active="/\/admin\/forms/.test($route.fullPath)"
to="/admin/forms"
>
Forms
</b-nav-item>
<b-nav-item
:active="/\/admin\/users/.test($route.fullPath)"
to="/admin/users"
>
Users
</b-nav-item>
<b-nav-item
:active="/\/admin\/configuration/.test($route.fullPath)"
to="/admin/configuration"
>
Configuration
</b-nav-item>
</b-navbar-nav>
<b-navbar-nav class="ml-auto">
<b-nav-item-dropdown right>
<template slot="button-content">
<font-awesome-icon icon="user-circle" />
</template>
<b-dropdown-item to="/admin/me">Profile</b-dropdown-item>
<b-dropdown-item @click="logout">Sign Out</b-dropdown-item>
</b-nav-item-dropdown>
</b-navbar-nav>
</b-collapse>
</b-navbar>
<nuxt />
</div>
</template>
<script>
export default {
methods: {
async logout() {
await this.$auth.logout()
this.$router.push('/login')
}
}
}
</script>

View File

@ -1,5 +1,5 @@
<template>
<div class="screen">
<div class="screen bg-primary dark">
<div class="content">
<nuxt />
</div>
@ -16,7 +16,6 @@
<style lang="scss" scoped>
.screen {
min-height: 100vh;
background: #173e43;
display: flex;
flex-direction: column;
@ -33,13 +32,8 @@
text-align: center;
a {
color: #dddfd4;
padding-right: 16px;
padding-left: 16px;
&:hover {
color: #fae596;
}
}
}
}

View File

@ -16,10 +16,38 @@ export default {
link: [{ rel: 'icon', type: 'image/png', href: '/favicon.png' }]
},
proxy: {
'/api': { target: 'http://localhost:3000', pathRewrite: { '/api/': '/' } }
},
router: {
middleware: ['auth']
},
server: {
port: 3100
},
auth: {
strategies: {
local: {
endpoints: {
login: { url: '/api/auth/login', method: 'post', propertyName: 'token.accessToken' },
logout: { url: '/api/auth/logout', method: 'post' },
user: false
},
tokenRequired: true,
tokenType: 'Bearer'
}
},
redirect: {
login: '/login',
logout: '/',
home: '/admin'
}
},
/*
** Customize the progress-bar color
*/
@ -35,15 +63,18 @@ export default {
/*
** Plugins to load before mounting the App
*/
plugins: [],
plugins: [
'@/plugins/font-awesome.js',
'@/plugins/fab.js'
],
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/auth',
'@nuxtjs/proxy',
'@nuxtjs/axios',
// Doc: https://bootstrap-vue.js.org/docs/
'bootstrap-vue/nuxt'
],
/*

View File

@ -13,13 +13,19 @@
"precommit": "npm run lint"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.22",
"@fortawesome/free-solid-svg-icons": "^5.10.2",
"@fortawesome/vue-fontawesome": "^0.1.7",
"@nuxtjs/auth": "^4.8.1",
"@nuxtjs/axios": "^5.3.6",
"@nuxtjs/proxy": "^1.3.3",
"bootstrap": "^4.1.3",
"bootstrap-vue": "^2.0.0-rc.11",
"cross-env": "^5.2.0",
"node-sass": "^4.12.0",
"nuxt": "^2.4.0",
"sass-loader": "^8.0.0"
"sass-loader": "^8.0.0",
"vue-fab": "^2.3.1"
},
"devDependencies": {
"@nuxtjs/eslint-config": "^0.0.1",

View File

@ -0,0 +1,11 @@
<template>
<div>
config XD
</div>
</template>
<script>
export default {
layout: 'admin'
}
</script>

View File

@ -0,0 +1,22 @@
<template>
<div>Form: {{ form }}</div>
</template>
<script>
export default {
layout: 'admin',
data() {
return {
form: null
}
},
created() {
this.load()
},
methods: {
async load() {
this.form = await this.$axios.$get(`/api/forms/${this.$route.params.id}`)
}
}
}
</script>

View File

@ -0,0 +1,46 @@
<template>
<div>
<b-alert show variant="info" class="m-3">
All created forms, they are publicly visible if live is true
</b-alert>
<fab bg-color="#173e43" />
<b-table striped hover :items="provider" :fields="fields">
<template slot="[menu]" slot-scope="data">
<nuxt-link :to="'/admin/forms/' + data.item.id">Open</nuxt-link>
</template>
</b-table>
</div>
</template>
<script>
export default {
layout: 'admin',
data() {
return {
fields: [
{
key: 'title'
},
{
key: 'created'
},
{
key: 'live'
},
{
key: 'responses'
},
{
key: 'menu'
}
]
}
},
methods: {
provider(ctx) {
return this.$axios.$get('/api/forms')
}
}
}
</script>

View File

@ -3,3 +3,9 @@
ADMIN
</div>
</template>
<script>
export default {
layout: 'admin'
}
</script>

11
ui/pages/admin/me.vue Normal file
View File

@ -0,0 +1,11 @@
<template>
<div>
My Profile XD
</div>
</template>
<script>
export default {
layout: 'admin'
}
</script>

View File

@ -0,0 +1,22 @@
<template>
<div>User: {{ user }}</div>
</template>
<script>
export default {
layout: 'admin',
data() {
return {
user: null
}
},
created() {
this.load()
},
methods: {
async load() {
this.user = await this.$axios.$get(`/api/users/${this.$route.params.id}`)
}
}
}
</script>

View File

@ -0,0 +1,45 @@
<template>
<div>
<b-alert show variant="info" class="m-3">
Available Users, since you are an admin you can edit them
</b-alert>
<b-table striped hover :items="provider" :fields="fields">
<template slot="[menu]" slot-scope="data">
<nuxt-link :to="'/admin/users/' + data.item.id">Open</nuxt-link>
</template>
</b-table>
</div>
</template>
<script>
export default {
layout: 'admin',
data() {
return {
fields: [
{
key: 'username'
},
{
key: 'email'
},
{
key: 'roles'
},
{
key: 'created'
},
{
key: 'menu'
}
]
}
},
methods: {
provider(ctx) {
return this.$axios.$get('/api/users')
}
}
}
</script>

View File

@ -1,5 +0,0 @@
<template>
<div>
FORM
</div>
</template>

22
ui/pages/forms/_id.vue Normal file
View File

@ -0,0 +1,22 @@
<template>
<div>Form: {{ form }}</div>
</template>
<script>
export default {
layout: 'admin',
data() {
return {
form: null
}
},
created() {
this.load()
},
methods: {
async load() {
this.form = await this.$axios.$get(`/public/${this.$route.params.id}`)
}
}
}
</script>

View File

@ -6,7 +6,8 @@
<script>
export default {
layout: 'screen'
layout: 'screen',
auth: 'guest'
}
</script>

View File

@ -2,7 +2,7 @@
<div>
<img src="../assets/img/logo_white_small.png" alt="OhMyForm" />
<b-form class="box" @submit="submit">
<b-form class="box" @submit.prevent="submit">
<b-form-group label-for="username">
<b-form-input
id="username"
@ -22,6 +22,12 @@
></b-form-input>
</b-form-group>
<b-form-group label-for="session">
<b-form-checkbox id="session" v-model="session">
Login only for current Tab
</b-form-checkbox>
</b-form-group>
<b-button type="submit" block variant="primary">Login</b-button>
<nuxt-link to="/recover" class="recover">Forgot your password?</nuxt-link>
</b-form>
@ -31,15 +37,28 @@
<script>
export default {
layout: 'screen',
auth: 'guest',
data() {
return {
username: '',
password: ''
password: '',
session: true
}
},
methods: {
submit() {
// TODO
async submit() {
try {
await this.$auth.loginWith('local', {
data: {
username: this.username,
password: this.password
}
})
this.$router.push('/admin')
} catch (e) {
// TODO failed login
}
}
}
}
@ -52,6 +71,15 @@ img {
}
.box {
margin-top: 60px;
height: 400px;
padding-left: 8px;
padding-right: 8px;
/deep/ .custom-checkbox {
.custom-control-label {
color: #fff;
}
}
}
.recover {
display: block;

71
ui/pages/recover.vue Normal file
View File

@ -0,0 +1,71 @@
<template>
<div>
<img src="../assets/img/logo_white_small.png" alt="OhMyForm" />
<b-form class="box" @submit.prevent="submit">
<b-form-group label-for="username">
<b-form-input
id="username"
v-model="username"
trim
placeholder="Username or Email"
></b-form-input>
</b-form-group>
<b-button type="submit" block variant="primary">Request Reset</b-button>
<nuxt-link to="/login" class="recover">
Just remembered your password? Sign in here
</nuxt-link>
</b-form>
</div>
</template>
<script>
export default {
layout: 'screen',
auth: 'guest',
data() {
return {
username: ''
}
},
methods: {
async submit() {
try {
await this.$axios.$post('/auth/recover', {
username: this.username
})
// TODO success
} catch (e) {
// TODO show error
}
}
}
}
</script>
<style lang="scss" scoped>
img {
max-width: 80%;
width: 300px;
}
.box {
margin-top: 60px;
height: 400px;
padding-left: 8px;
padding-right: 8px;
/deep/ .custom-checkbox {
.custom-control-label {
color: #fff;
}
}
}
.recover {
display: block;
padding-top: 15px;
color: #fff;
font-size: 14px;
}
</style>

View File

@ -42,6 +42,7 @@
<script>
export default {
layout: 'screen',
auth: 'guest',
data() {
return {
username: '',
@ -64,6 +65,9 @@ img {
}
.box {
margin-top: 60px;
height: 400px;
padding-left: 8px;
padding-right: 8px;
}
.recover {
display: block;

6
ui/plugins/fab.js Normal file
View File

@ -0,0 +1,6 @@
import Vue from 'vue'
import fab from 'vue-fab'
export default () => {
Vue.component('fab', fab)
}

View File

@ -0,0 +1,10 @@
import Vue from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faUserCircle } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
export default () => {
library.add(faUserCircle)
Vue.component('font-awesome-icon', FontAwesomeIcon)
}

View File

@ -703,6 +703,30 @@
resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7"
integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==
"@fortawesome/fontawesome-common-types@^0.2.22":
version "0.2.22"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.22.tgz#3f1328d232a0fd5de8484d833c8519426f39f016"
integrity sha512-QmEuZsipX5/cR9JOg0fsTN4Yr/9lieYWM8AQpmRa0eIfeOcl/HLYoEa366BCGRSrgNJEexuvOgbq9jnJ22IY5g==
"@fortawesome/fontawesome-svg-core@^1.2.22":
version "1.2.22"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.22.tgz#9a6117c96c8b823c7d531000568ac75c3c02e123"
integrity sha512-Q941E4x8UfnMH3308n0qrgoja+GoqyiV846JTLoCcCWAKokLKrixCkq6RDBs8r+TtAWaLUrBpI+JFxQNX/WNPQ==
dependencies:
"@fortawesome/fontawesome-common-types" "^0.2.22"
"@fortawesome/free-solid-svg-icons@^5.10.2":
version "5.10.2"
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.10.2.tgz#61bcecce3aa5001fd154826238dfa840de4aa05a"
integrity sha512-9Os/GRUcy+iVaznlg8GKcPSQFpIQpAg14jF0DWsMdnpJfIftlvfaQCWniR/ex9FoOpSEOrlXqmUCFL+JGeciuA==
dependencies:
"@fortawesome/fontawesome-common-types" "^0.2.22"
"@fortawesome/vue-fontawesome@^0.1.7":
version "0.1.7"
resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-0.1.7.tgz#121867297cafd141af78c67d92ab9f1ad4b7328b"
integrity sha512-YCw2Q2m4fxzyFsPOH3uDYMoJztTD+pT+AAyse4LFpbdrBg+r8ueaVT8BFnXEjrGwMDJJeXrwJ5AOC6q/JWBI4w==
"@nuxt/babel-preset-app@2.9.2":
version "2.9.2"
resolved "https://registry.yarnpkg.com/@nuxt/babel-preset-app/-/babel-preset-app-2.9.2.tgz#e8d2ac841db845ef88ca2687093a95a480106e5d"
@ -953,7 +977,21 @@
webpack-node-externals "^1.7.2"
webpackbar "^4.0.0"
"@nuxtjs/axios@^5.3.6":
"@nuxtjs/auth@^4.8.1":
version "4.8.1"
resolved "https://registry.yarnpkg.com/@nuxtjs/auth/-/auth-4.8.1.tgz#e554bbd032fdcd08d9fddcb159f30ff2c7de5b1a"
integrity sha512-EsBiiWXnojOxQ1ouK07tsXMP8pLFiAc+L4NLd1NGr3Z45iGRrjwLLy/weOH65vJGqNYX3lIQBGYscUyzGaaAyg==
dependencies:
"@nuxtjs/axios" "^5.5.4"
consola "^2.9.0"
cookie "^0.4.0"
dotprop "^1.2.0"
is-https "^1.0.0"
js-cookie "^2.2.0"
lodash "^4.17.11"
nanoid "^2.0.3"
"@nuxtjs/axios@^5.3.6", "@nuxtjs/axios@^5.5.4":
version "5.6.0"
resolved "https://registry.yarnpkg.com/@nuxtjs/axios/-/axios-5.6.0.tgz#30fd28c8a409ea32c92c76b26202e5da068f814c"
integrity sha512-Rl4nnudm+sSkMtgfSEAeA5bq6aFpbBoYVXLXWaDxfydslukRd2SdEDdGv0gHE7F/jtIw+JfptWDHCHnzuoO/Ng==
@ -2406,7 +2444,7 @@ cookie-signature@1.0.6:
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
cookie@0.4.0:
cookie@0.4.0, cookie@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
@ -2993,6 +3031,11 @@ dot-prop@^4.1.0, dot-prop@^4.1.1:
dependencies:
is-obj "^1.0.0"
dotprop@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/dotprop/-/dotprop-1.2.0.tgz#8fdf345c757da479ec8af218ae4239a73df721a7"
integrity sha512-mVQb8y5u3UkzNua2Hc8Ut/uKyCjm9GG2MRk/0fxJ9Mxo8Nb8XyWqaP0wVXerMucmu0mQmlcZm3S1mjOdcbCwQA==
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@ -4632,6 +4675,11 @@ is-glob@^4.0.0, is-glob@^4.0.1:
dependencies:
is-extglob "^2.1.1"
is-https@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-https/-/is-https-1.0.0.tgz#9c1dde000dc7e7288edb983bef379e498e7cb1bf"
integrity sha512-1adLLwZT9XEXjzhQhZxd75uxf0l+xI9uTSFaZeSESjL3E1eXSPpO+u5RcgqtzeZ1KCaNvtEwZSTO2P4U5erVqQ==
is-installed-globally@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80"
@ -4784,6 +4832,11 @@ js-base64@^2.1.8:
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121"
integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==
js-cookie@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
js-levenshtein@^1.1.3:
version "1.1.6"
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
@ -5073,7 +5126,7 @@ lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
loose-envify@^1.0.0:
loose-envify@^1.0.0, loose-envify@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -5406,6 +5459,11 @@ nan@^2.12.1, nan@^2.13.2:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
nanoid@^2.0.3:
version "2.1.0"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.0.tgz#3de3dbd68cfb2f3bd52550e2bfd439cf75040eb2"
integrity sha512-g5WwS+p6Cm+zQhO2YOpRbQThZVnNb7DDq74h8YDCLfAGynrEOrbx2E16dc8ciENiP1va5sqaAruqn2sN+xpkWg==
nanomatch@^1.2.9:
version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@ -6158,7 +6216,7 @@ pkg-dir@^4.1.0:
dependencies:
find-up "^4.0.0"
popper.js@^1.15.0:
popper.js@^1.0.2, popper.js@^1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2"
integrity sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==
@ -8215,6 +8273,13 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
tooltip.js@^1.1.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/tooltip.js/-/tooltip.js-1.3.2.tgz#ccfe450ffd7332ce5f254033778649526c1db542"
integrity sha512-DeDr9JxYx/lSvQ53ZCRFLxXrmrSyU3fLz6k+ITUTw69AIYtpWij/NmOJQscJ7BwY5lcEwWJWSfqqQWVvTMYZiw==
dependencies:
popper.js "^1.0.2"
toposort@^1.0.0:
version "1.0.7"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
@ -8538,6 +8603,13 @@ uuid@^3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
v-tooltip@2.0.0-beta.1:
version "2.0.0-beta.1"
resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-2.0.0-beta.1.tgz#9d09a4ea96054ebfe268bf0b50c7845fd5d64b5b"
integrity sha1-nQmk6pYFTr/iaL8LUMeEX9XWS1s=
dependencies:
tooltip.js "^1.1.2"
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
@ -8570,6 +8642,13 @@ vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019"
integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==
vue-clickaway@^2.1.0:
version "2.2.2"
resolved "https://registry.yarnpkg.com/vue-clickaway/-/vue-clickaway-2.2.2.tgz#cecf6839575e8b2afc5d3edb3efb616d293dbb44"
integrity sha512-25SpjXKetL06GLYoLoC8pqAV6Cur9cQ//2g35GRFBV4FgoljbZZjTINR8g2NuVXXDMLSUXaKx5dutgO4PaDE7A==
dependencies:
loose-envify "^1.2.0"
vue-client-only@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/vue-client-only/-/vue-client-only-2.0.0.tgz#ddad8d675ee02c761a14229f0e440e219de1da1c"
@ -8587,6 +8666,16 @@ vue-eslint-parser@^5.0.0:
esquery "^1.0.1"
lodash "^4.17.11"
vue-fab@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/vue-fab/-/vue-fab-2.3.1.tgz#5f36c8595f6ceff576b01b97a40817fdf262bd85"
integrity sha512-suC4T0gfvDussE1HQ5+ts8D4VknKTiKJqZqDXCtaAc5ithkZYDk5HN7+qS/Svoj2GTaJc3jpG91aLWUgdd+YUw==
dependencies:
v-tooltip "2.0.0-beta.1"
vue "^2.2.1"
vue-clickaway "^2.1.0"
vue-ripple-directive "^1.0.0"
vue-functional-data-merge@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz#08a7797583b7f35680587f8a1d51d729aa1dc657"
@ -8620,6 +8709,13 @@ vue-no-ssr@^1.1.1:
resolved "https://registry.yarnpkg.com/vue-no-ssr/-/vue-no-ssr-1.1.1.tgz#875f3be6fb0ae41568a837f3ac1a80eaa137b998"
integrity sha512-ZMjqRpWabMPqPc7gIrG0Nw6vRf1+itwf0Itft7LbMXs2g3Zs/NFmevjZGN1x7K3Q95GmIjWbQZTVerxiBxI+0g==
vue-ripple-directive@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/vue-ripple-directive/-/vue-ripple-directive-1.1.1.tgz#3399ea5c49f28899eea53d226f51ccacd27f202b"
integrity sha512-W25CDdrgopuRdAwmjEeBtmJMTvbHkoze7jn8qWVwLHXBj8yBRZc05O4OCTYqjwZLO1hrclMQ467wahb6wbRCZg==
dependencies:
vue "^2.2.1"
vue-router@~3.0.7:
version "3.0.7"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.7.tgz#b36ca107b4acb8ff5bc4ff824584059c23fcb87b"
@ -8660,7 +8756,7 @@ vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
vue@^2.6.10:
vue@^2.2.1, vue@^2.6.10:
version "2.6.10"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==