Refonte du front en VueJS

master
Norore 2020-11-18 19:44:46 +01:00
parent 8f06c3d639
commit 1495c12307
32 changed files with 783 additions and 77 deletions

View File

@ -7,6 +7,7 @@ gem 'puma'
gem 'webpacker'
gem 'uglifier'
gem 'bootsnap', require: false
gem 'js-routes'
gem 'pg'

View File

@ -133,6 +133,9 @@ GEM
rainbow (>= 2.0.0)
i18n (1.8.5)
concurrent-ruby (~> 1.0)
js-routes (1.4.9)
railties (>= 4)
sprockets-rails
listen (3.2.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
@ -287,6 +290,7 @@ DEPENDENCIES
guard-rails
http
httplog
js-routes
listen
net-ping
openssl

View File

@ -8,6 +8,8 @@ yarn add jquery
yarn add popper.js
yarn add bootstrap
yarn add @fortawesome/fontawesome-free
bin/rails webpacker:install:vue
yarn add vue-router
bin/rails db:create db:migrate
bin/webpack-dev-server
```

View File

@ -2,5 +2,40 @@ class ApplicationController < ActionController::Base
def main
@services = Service.all
@checks = Check.all
end
def sidebar
@services = Service.all
sidebar = []
percent = 100
@services.each do |s|
ref = nil
unless s.config[:http].nil?
ref = s.config[:http]
end
sidebar.push(
{
percent: percent,
type: s.type,
ref: ref,
name: s.name,
error: nil
})
percent -= 10
end
render json: sidebar
end
def dashboard
@services = Service.all
@checks = Check.all
dashboard = {
total_services: @services.size,
total_checks: @checks.size,
stats: @services
}
render json: dashboard
end
end

View File

@ -2,26 +2,38 @@ class ServicesController < ApplicationController
before_action :set_service, only: [:show, :edit, :update, :destroy]
def show
render json: @service
end
def new
@service = Service.new
end
CONFIG_PARAMS = {
ping: %i[host],
http: %i[url code],
tcp: %i[host port],
udp: %i[host port]
}
def create
@service = Service.new(service_params)
if @service.save
redirect_to @service, notice: t('notice.service.created', service: @service.name)
else
render :new
end
type = params[:service][:type].to_sym
config = params[:service].slice *CONFIG_PARAMS[type]
p = { name: params[:service][:name], type: type, config: config }
@service = Service.create! **p
redirect_to @service, notice: t('notice.service.created', service: @service.name)
end
def edit
end
def update
if @service.update(mission_params)
type = params[:service][:type].to_sym
config = params[:service].slice *CONFIG_PARAMS[type]
p = { id: params[:id], name: params[:service][:name], type: type, config: config }
if @service.update(p)
redirect_to @service, notice: t('notice.service.updated', service: @service.name)
else
render :edit

View File

@ -0,0 +1,34 @@
<template>
<main class="container-fluid">
<div id="container" class="row">
<Sidebar/>
<div id="content" class="col-sm-10">
<router-view/>
</div>
</div>
</main>
</template>
<script>
import Sidebar from '../components/Sidebar.vue'
export default {
components: {
Sidebar
}
}
</script>
<style>
main {
display: flex;
flex-direction: column;
flex-wrap: wrap;
flex-grow: 1;
}
#container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
flex-grow: 1;
}
</style>

View File

@ -0,0 +1,10 @@
<template>
<footer class="bg-dark text-light text-center p-4">
<div>
Monit is a free web application to monitor your servers, developped by
<a href="https://imirhil.fr/">Aeris</a> (backend) and <a href="https://norore.fr">Norore</a> (frontend) under A-GPL
<br>
Icon made by <a href="https://norore.fr">Norore</a> under A-GPL
</div>
</footer>
</template>

View File

@ -0,0 +1,42 @@
<template>
<header>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark" id="mainmenu" role="navigation">
<a class="navbar-brand" href="/"><img src="../../assets/images/logo.png" height="30px" alt="Monit logo"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#navbarNav" aria-controls="navbarNavDropdown"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mr-auto">
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown"
role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
EN
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<div class="dropdown-item" v-for="locale in locales">
{{ locale.lang }}
</div>
</div>
</li>
</ul>
</div>
</nav>
</header>
</template>
<script>
export default {
data: function () {
return {
locales: [
{lang: 'English', abbr: 'EN'},
{lang: 'French', abbr: 'FR'}
]
}
}
}
</script>

View File

@ -0,0 +1,47 @@
<template>
<aside id="aside" class="col-sm-2">
<a href="/services/new" class="mx-0 add">
<i class="fa fa-plus"></i> Add a service
</a>
<div v-for="service in sidebar" class="m-0 mt-1 row bg-light">
<div class="col-sm-2">
<span class="badge badge-secondary">{{ service.percent }}%</span>
</div>
<div class="col-sm-2">
<span class="badge badge-pill badge-secondary">
{{ service.type }}
</span>
</div>
<div class="col-sm-6">
{{ service.name }}
</div>
<div class="col-sm-2 text-right">
<small>{{ service.error }}</small>
</div>
</div>
</aside>
</template>
<script>
const axios = require('axios')
export default {
data: function () {
return {
sidebar: null
}
},
mounted () {
axios
.get(Routes.sidebar_path())
.then(response => {
this.sidebar = response.data
})
.catch(error => {
console.log(error)
this.errored = true
})
.finally(() => this.loading = false)
}
}
</script>

View File

@ -1,5 +1,13 @@
$dark: #111;
$light: #ccc;
$dark: #33383b;
$light: #e7edf4;
$link-color: #ff9400;
$link-hover-color: lighten($link-color, 10%);
$link-color: #003554;
$link-hover-color: lighten($link-color, 10%);
$table-bg: $light;
$table-border-color: $dark;
$input-bg: #D2D7DE;
$btn-primary-bg: #0084D1;
$btn-secondary-bg: #454D52;

View File

@ -18,10 +18,69 @@ body {
flex-direction: column;
}
main {
display: flex;
flex-direction: column;
flex-wrap: wrap;
flex-grow: 1;
}
#container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
flex-grow: 1;
}
footer {
margin-top: auto;
}
/**
* End of the trick
*/
*/
a {
text-decoration: underline dotted;
}
footer a {
color: #78fd00;
&:hover {
color: #acfc65;
}
}
table {
border: .15em solid $dark;
}
aside {
//min-height: 84vh;
background-color: $dark;
//margin: 0 auto;
}
aside a.add {
color: $light;
&:hover {
color: $light;
}
}
main {
background-color: $light;
}
/**
* Forms
*/
label {
font-weight: bold;
color: $dark;
}
input {
border: none;
//background-color: ;
}

View File

@ -0,0 +1,38 @@
<template>
<v-app>
<v-content>
<Header/>
<Container/>
<Footer/>
</v-content>
</v-app>
</template>
<script>
import Header from '../components/Header'
import Container from '../components/Container'
import Footer from '../components/Footer'
export default {
name: 'App',
components: {
Header,
Container,
Footer
},
data: () => ({
//
})
}
</script>
<style>
v-content {
min-height: 100vh;
display: flex;
flex-direction: column;
}
footer {
margin-top: auto;
}
</style>

View File

@ -1,6 +1,23 @@
require("jquery")
require("vue")
require("bootstrap")
require('@fortawesome/fontawesome-free')
import 'css/application'
import 'bootstrap'
import 'bootstrap'
import Routes from '../routes/index.js.erb';
window.Routes = Routes;
import Vue from 'vue'
import App from './App.vue'
import router from './router.js'
import 'axios'
document.addEventListener('DOMContentLoaded', () => {
const app = new Vue({
router,
render: h => h(App)
}).$mount()
document.body.appendChild(app.$el)
})

View File

@ -0,0 +1,33 @@
import Vue from 'vue'
import Router from 'vue-router'
import Login from '../views/Login.vue'
import Dashboard from '../views/Dashboard.vue'
import Service from '../views/Service.vue'
import NewService from '../views/NewService.vue'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: Dashboard
},
{
path: '/service',
name: 'service',
component: Service
},
{
path: '/service/new',
name: 'newservice',
component: NewService
},
{
path: '/login',
name: 'login',
component: Login
}
]
})

View File

@ -0,0 +1 @@
<%= JsRoutes.generate() %>

View File

@ -0,0 +1,37 @@
// show form elements in function of type of check
// window.createConfig = function (element) {
// const type = element.selectedOptions[0].value
// const host = document.getElementById("host")
// const port = document.getElementById("port")
// const link = document.getElementById("link")
// const code = document.getElementById("code")
// switch (type) {
// case 'ping':
// host.style.display = 'block'
// port.style.display = 'none'
// link.style.display = 'none'
// code.style.display = 'none'
// break
// case 'http':
// host.style.display = 'none'
// port.style.display = 'none'
// link.style.display = 'block'
// code.style.display = 'block'
// break
// case 'tcp':
// case 'udp':
// host.style.display = 'block'
// port.style.display = 'block'
// link.style.display = 'none'
// code.style.display = 'none'
// break
// default:
// host.style.display = 'none'
// port.style.display = 'none'
// link.style.display = 'none'
// code.style.display = 'none'
// break
// }
// }

View File

@ -0,0 +1,73 @@
<template>
<div>
<h1>Dashboard</h1>
<h2>Quick stats</h2>
<p>You have defined {{ total_services }} services.</p>
<p>A total of {{ total_checks }} checks were performed.</p>
<h2 class="text-dark">Latest events for all services</h2>
<table class="table">
<thead class="thead-dark">
<tr>
<th>Event</th>
<th>Type</th>
<th>Service</th>
<th>Last update datetime</th>
</tr>
</thead>
<tbody>
<tr v-for="service in stats">
<td></td>
<td>{{ service.type }}</td>
<td><a :href="'#/service#' + service.id">{{ service.name }}</a></td>
<td>{{ formatDateTime(service.updated_at) }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
const axios = require('axios')
export default {
data: function () {
return {
total_services: null,
total_checks: null,
stats: null
}
},
methods: {
formatDateTime: function (str) {
const datetime = new Date(str)
const day = datetime.getDate()
const month = datetime.getMonth()
const year = datetime.getFullYear()
const hours = datetime.getHours()
const minutes = datetime.getMinutes()
const seconds = datetime.getSeconds()
return day+"/"+month+"/"+year+" "+hours+":"+minutes+":"+seconds
}
},
mounted () {
axios
.get(Routes.dashboard_path())
.then(response => {
console.log(response.data)
this.total_services = response.data["total_services"]
this.total_checks = response.data["total_checks"]
this.stats = response.data["stats"]
})
.catch(error => {
console.log(error)
this.errored = true
})
.finally(() => this.loading = false)
}
}
</script>

View File

@ -0,0 +1,5 @@
<template>
<div>
<h2>welcome to login page</h2>
</div>
</template>

View File

@ -0,0 +1,7 @@
<template>
</template>
<script>
</script>

View File

@ -0,0 +1,49 @@
<template>
<div>
<h1>{{ service.name }}</h1>
<strong>Type of service:</strong> {{ service.type }}<br>
<strong>Config:</strong> {{ service.config }}<br>
<strong>Created time:</strong> {{ formatDateTime(service.created_at) }}<br>
<strong>Last updated time:</strong> {{ formatDateTime(service.updated_at) }}<br>
</div>
</template>
<script>
const axios = require('axios')
const path = window.location.hash
const id = path.split('#').pop()
export default {
data: function () {
return {
service: null
}
},
methods: {
formatDateTime: function (str) {
const datetime = new Date(str)
const day = datetime.getDate()
const month = datetime.getMonth()
const year = datetime.getFullYear()
const hours = datetime.getHours()
const minutes = datetime.getMinutes()
const seconds = datetime.getSeconds()
return day+"/"+month+"/"+year+" "+hours+":"+minutes+":"+seconds
}
},
mounted () {
axios
.get(Routes.service_path(id))
.then(response => {
console.log(response.data)
this.service = response.data
})
.catch(error => {
console.log(error)
this.errored = true
})
.finally(() => this.loading = false)
}
}
</script>

View File

@ -1,26 +1,30 @@
<h1 class="text-steal">Monitors</h1>
<h1 class="text-steal">Dashboard</h1>
<%= link_to "Add a service", new_service_url %>
<h2>Quick stats</h2>
<table class="table table-responsive">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Type</th>
</tr>
</thead>
<tbody>
<% @services.each do |s| %>
<tr>
<td><%= link_to s.name, service_url %></td>
<td><%= s.type %></td>
</tr>
<% end %>
</tbody>
</table>
<p>You have defined <%= @services.size %> services.</p>
<h1 class="text-dark">Overall uptime</h1>
<p>A total of <%= @checks.size %> checks were performed.</p>
<h1 class="text-dark">Latest downtime</h1>
<h2 class="text-dark">Latest events for all services</h2>
<h1 class="text-dark">Quick stats</h1>
<!--<table class="table">-->
<!-- <thead class="thead-dark">-->
<!-- <tr>-->
<!-- <th>Event</th>-->
<!-- <th>Type</th>-->
<!-- <th>Service</th>-->
<!-- <th>Datetime</th>-->
<!-- </tr>-->
<!-- </thead>-->
<!-- <tbody>-->
<%# @services.each do |service| %>
<!-- <tr>-->
<!-- <td></td>-->
<!-- <td><%#= service.type %></td>-->
<!-- <td><%#= link_to service.name, service %></td>-->
<!-- <td><%#= service.updated_at %></td>-->
<!-- </tr>-->
<%# end %>
<!-- </tbody>-->
<!--</table>-->

View File

@ -0,0 +1,22 @@
<%= link_to new_service_url, class: %i[add] do %>
<i class="fa fa-plus"></i> Add a service
<% end %>
<% @services.each do |service| %>
<div class="m-1 row bg-light">
<div class="col-1">
<span class="badge badge-secondary">0%</span>
</div>
<div class="col-3">
<span class="badge badge-pill badge-secondary">
<%= service.type %>
</span>
</div>
<div class="col-6">
<%= link_to service.name, service %>
</div>
<div class="col-2 text-right">
<%= service.updated_at.strftime('%d/%m/%y %H:%M') %>
</div>
</div>
<% end %>

View File

@ -11,20 +11,28 @@
</head>
<body>
<%= render 'layouts/header' %>
<%#= render 'layouts/header' %>
<main class="mx-5">
<% flash.each do |key, value| %>
<div class="<%= flash_class(key) %> alert-dismissible fade show mt-4">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<%= value %>
</div>
<% end %>
<!-- <main class="container-fluid">-->
<!-- <div id="container" class="row">-->
<!-- <aside id="sidebar" class="col-sm-2">-->
<%#= render 'layouts/sidebar' %>
<!-- </aside>-->
<%= yield %>
</main>
<%= render 'layouts/footer' %>
<!-- <div id="content" class="col-sm-10">-->
<%# flash.each do |key, value| %>
<!-- <div class="<%#= flash_class(key) %> alert-dismissible fade show mt-4">-->
<!-- <button type="button" class="close" data-dismiss="alert" aria-label="Close">-->
<!-- <span aria-hidden="true">&times;</span>-->
<!-- </button>-->
<%#= value %>
<!-- </div>-->
<%# end %>
<%#= yield %>
<!-- </div>-->
<!-- </div>-->
<!-- </main>-->
<%#= render 'layouts/footer' %>
</body>
</html>

View File

@ -15,34 +15,53 @@
</div>
<% end %>
<fieldset class="mb-1 p-2">
<legend class="h4"><%= t("service.form.title") %></legend>
<div class="form-group row">
<%= form.label :name, t("service.form.name"), class: %i[col-sm-2 col-form-label] %>
<div>
<%= form.text_field :name, class: %i[form-control col-sm-10] %>
</div>
<div class="form-group row">
<%= form.label :name, t("service.form.name"), class: %i[col-sm-2 col-form-label] %>
<div>
<%= form.text_field :name, class: %i[form-control] %>
</div>
</div>
<div class="form-group row">
<%= form.label :type, t("service.form.type"), class: %i[col-sm-2 col-form-label] %>
<div>
<%= form.select :type, options_for_select(
%i[ping tcp udp http cert].collect { |n| [ t("service.form.type.#{n}"), n ] }, form.object.type
), {}, class: %i[form-control col-sm-10] %>
</div>
<div class="form-group row">
<%= form.label :type, t("service.form.type"), class: %i[col-sm-2 col-form-label] %>
<div>
<%= form.select :type, options_for_select(
%i[ping tcp udp http cert].collect { |n| [ t("service.form.type.#{n}"), n ] }, form.object.type
), {}, onchange: 'createConfig(this)', class: %i[form-control] %>
</div>
</div>
<div class="form-group row">
<%= form.label :config, t("service.form.config"), class: %i[col-sm-2 col-form-label] %>
<div>
<%= form.text_field :config, class: %i[form-control col-sm-10] %>
</div>
<!-- type of service will define config fields required by app -->
<div id="host" class="form-group row">
<%= form.label :host, t("service.form.host"), class: %i[col-sm-2 col-form-label] %>
<div>
<%= form.text_field :host, class: %i[form-control] %>
</div>
</fieldset>
</div>
<div id="port" class="form-group row">
<%= form.label :port, t("service.form.port"), class: %i[col-sm-2 col-form-label] %>
<div>
<%= form.text_field :port, class: %i[form-control] %>
</div>
</div>
<div id="link" class="form-group row"> <!-- id named link for aesthetic purpose only on JS part -->
<%= form.label :url, t("service.form.url"), class: %i[col-sm-2 col-form-label] %>
<div>
<%= form.text_field :url, class: %i[form-control] %>
</div>
</div>
<div id="code" class="form-group row">
<%= form.label :code, t("service.form.code"), class: %i[col-sm-2 col-form-label] %>
<div>
<%= form.text_field :code, class: %i[form-control] %>
</div>
</div>
<div class="actions text-right">
<%= link_to t('button.back'), request.referer, class: %i[btn btn-secondary] %>&nbsp;
<%= link_to t('button.cancel'), root_path, class: %i[btn btn-secondary] %>&nbsp;
<%= form.submit t('button.submit'), class: %i[btn btn-primary] %>
</div>
<% end %>

View File

@ -1,5 +1,3 @@
<h1><%= t('service.new.title') %></h1>
<%= render 'form', service: @service %>
<%= link_to t('home'), root_path %>

View File

@ -0,0 +1,4 @@
<h1><%= @service.name %></h1>
<strong>Type of service:</strong> <%= @service.type %><br>
<strong>Config:</strong> <%= @service.config %>

View File

@ -17,4 +17,15 @@ en:
form:
title: Service
name: Name
type: Type
type: Type
check:
new:
title: Create a new check
edit:
title: Edit check %{service}
form:
title: Check
url: Address

View File

@ -2,4 +2,9 @@ Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
root to: "application#main"
resources :services
resources :checks
get '/sidebar', to: 'application#sidebar', as: 'sidebar'
get '/dashboard', to: 'application#dashboard', as: 'dashboard'
# get '/service/id', to: 'service#view', as: 'service'
end

View File

@ -1,4 +1,7 @@
const { environment } = require('@rails/webpacker')
const erb = require('./loaders/erb')
const { VueLoaderPlugin } = require('vue-loader')
const vue = require('./loaders/vue')
const webpack = require("webpack")
environment.plugins.append("Provide", new webpack.ProvidePlugin({
@ -7,4 +10,7 @@ environment.plugins.append("Provide", new webpack.ProvidePlugin({
Popper: ['popper.js', 'default']
}))
environment.plugins.prepend('VueLoaderPlugin', new VueLoaderPlugin())
environment.loaders.prepend('vue', vue)
environment.loaders.prepend('erb', erb)
module.exports = environment

View File

@ -33,6 +33,8 @@ default: &default
- .woff2
extensions:
- .erb
- .vue
- .mjs
- .js
- .sass

View File

@ -4,10 +4,16 @@
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.0",
"@rails/webpacker": "5.2.1",
"axios": "^0.21.0",
"bootstrap": "^4.5.2",
"jquery": "^3.5.1",
"popper": "^1.0.1",
"popper.js": "^1.16.1"
"popper.js": "^1.16.1",
"rails-erb-loader": "^5.5.2",
"vue": "^2.6.12",
"vue-loader": "^15.9.5",
"vue-router": "^3.4.9",
"vue-template-compiler": "^2.6.12"
},
"devDependencies": {
"webpack-dev-server": "^3.11.0"

117
yarn.lock
View File

@ -982,6 +982,22 @@
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d"
integrity sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==
"@vue/component-compiler-utils@^3.1.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-3.2.0.tgz#8f85182ceed28e9b3c75313de669f83166d11e5d"
integrity sha512-lejBLa7xAMsfiZfNp7Kv51zOzifnb29FwdnMLa96z26kXErPFioSf9BMcePVIQ6/Gc6/mC0UrPpxAWIHyae0vw==
dependencies:
consolidate "^0.15.1"
hash-sum "^1.0.2"
lru-cache "^4.1.2"
merge-source-map "^1.1.0"
postcss "^7.0.14"
postcss-selector-parser "^6.0.2"
source-map "~0.6.1"
vue-template-es2015-compiler "^1.9.0"
optionalDependencies:
prettier "^1.18.2"
"@webassemblyjs/ast@1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"
@ -1545,6 +1561,13 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428"
integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==
axios@^0.21.0:
version "0.21.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.0.tgz#26df088803a2350dff2c27f96fef99fe49442aca"
integrity sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==
dependencies:
follow-redirects "^1.10.0"
babel-loader@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3"
@ -1658,7 +1681,7 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
bluebird@^3.5.5:
bluebird@^3.1.1, bluebird@^3.5.5:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
@ -2548,6 +2571,13 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
consolidate@^0.15.1:
version "0.15.1"
resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7"
integrity sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==
dependencies:
bluebird "^3.1.1"
constants-browserify@^1.0.0, constants-browserify@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
@ -2942,6 +2972,11 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -3807,7 +3842,7 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
follow-redirects@^1.0.0:
follow-redirects@^1.0.0, follow-redirects@^1.10.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
@ -4208,6 +4243,11 @@ hash-base@^3.0.0:
readable-stream "^3.6.0"
safe-buffer "^5.2.0"
hash-sum@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"
integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=
hash.js@^1.0.0, hash.js@^1.0.3:
version "1.1.7"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
@ -4216,7 +4256,7 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
he@1.2.0:
he@1.2.0, he@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
@ -5190,7 +5230,7 @@ loader-runner@^2.4.0:
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0:
loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
@ -5332,7 +5372,7 @@ loud-rejection@^1.0.0:
currently-unhandled "^0.4.1"
signal-exit "^3.0.0"
lru-cache@^4.0.1:
lru-cache@^4.0.1, lru-cache@^4.1.2:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
@ -5471,6 +5511,13 @@ merge-descriptors@1.0.1:
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
merge-source-map@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646"
integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==
dependencies:
source-map "^0.6.1"
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@ -7158,6 +7205,11 @@ preserve@^0.2.0:
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=
prettier@^1.18.2:
version "1.19.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
process-nextick-args@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
@ -7302,6 +7354,14 @@ querystringify@^2.1.1:
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
rails-erb-loader@^5.5.2:
version "5.5.2"
resolved "https://registry.yarnpkg.com/rails-erb-loader/-/rails-erb-loader-5.5.2.tgz#db3fa8ac89600f09d179a1a70a2ca18c592576ea"
integrity sha512-cjQH9SuSvRPhnWkvjmmAW/S4AFVDfAtYnQO4XpKJ8xpRdZayT73iXoE+IPc3VzN03noZXhVmyvsCvKvHj4LY6w==
dependencies:
loader-utils "^1.1.0"
lodash.defaults "^4.2.0"
random-bytes@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
@ -9078,6 +9138,53 @@ vm-browserify@~0.0.1:
dependencies:
indexof "0.0.1"
vue-hot-reload-api@^2.3.0:
version "2.3.4"
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==
vue-loader@^15.9.5:
version "15.9.5"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.5.tgz#7a960dc420a3439deaacdda038fdcdbf7c432706"
integrity sha512-oeMOs2b5o5gRqkxfds10bCx6JeXYTwivRgbb8hzOrcThD2z1+GqEKE3EX9A2SGbsYDf4rXwRg6D5n1w0jO5SwA==
dependencies:
"@vue/component-compiler-utils" "^3.1.0"
hash-sum "^1.0.2"
loader-utils "^1.1.0"
vue-hot-reload-api "^2.3.0"
vue-style-loader "^4.1.0"
vue-router@^3.4.9:
version "3.4.9"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.4.9.tgz#c016f42030ae2932f14e4748b39a1d9a0e250e66"
integrity sha512-CGAKWN44RqXW06oC+u4mPgHLQQi2t6vLD/JbGRDAXm0YpMv0bgpKuU5bBd7AvMgfTz9kXVRIWKHqRwGEb8xFkA==
vue-style-loader@^4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"
integrity sha512-0ip8ge6Gzz/Bk0iHovU9XAUQaFt/G2B61bnWa2tCcqqdgfHs1lF9xXorFbE55Gmy92okFT+8bfmySuUOu13vxQ==
dependencies:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
vue-template-compiler@^2.6.12:
version "2.6.12"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz#947ed7196744c8a5285ebe1233fe960437fcc57e"
integrity sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
vue-template-es2015-compiler@^1.9.0:
version "1.9.1"
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.12:
version "2.6.12"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.12.tgz#f5ebd4fa6bd2869403e29a896aed4904456c9123"
integrity sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==
watchpack-chokidar2@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0"