mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-16 22:36:21 +00:00
Merge branch 'blazer' into staging
This commit is contained in:
commit
063d2625fb
24 changed files with 405 additions and 53 deletions
|
@ -3,6 +3,7 @@
|
|||
# Forma de ingreso a Sutty
|
||||
class ApplicationController < ActionController::Base
|
||||
include ExceptionHandler
|
||||
include Pundit
|
||||
|
||||
protect_from_forgery with: :null_session, prepend: true
|
||||
|
||||
|
@ -10,6 +11,7 @@ class ApplicationController < ActionController::Base
|
|||
before_action :configure_permitted_parameters, if: :devise_controller?
|
||||
around_action :set_locale
|
||||
|
||||
rescue_from Pundit::NilPolicyError, with: :page_not_found
|
||||
rescue_from ActionController::RoutingError, with: :page_not_found
|
||||
rescue_from ActionController::ParameterMissing, with: :page_not_found
|
||||
|
||||
|
@ -33,7 +35,7 @@ class ApplicationController < ActionController::Base
|
|||
def find_site
|
||||
id = params[:site_id] || params[:id]
|
||||
|
||||
unless (site = current_usuarie.sites.find_by_name(id))
|
||||
unless (site = current_usuarie&.sites&.find_by_name(id))
|
||||
raise SiteNotFound
|
||||
end
|
||||
|
||||
|
@ -62,6 +64,21 @@ class ApplicationController < ActionController::Base
|
|||
render 'application/page_not_found', status: :not_found
|
||||
end
|
||||
|
||||
# Necesario para poder acceder a Blazer. Solo les usuaries de este
|
||||
# sitio pueden acceder al panel.
|
||||
def require_usuarie
|
||||
site = find_site
|
||||
authorize SiteBlazer.new(site)
|
||||
|
||||
# Necesario para los breadcrumbs.
|
||||
ActionView::Base.include Loaf::ViewExtensions unless ActionView::Base.included_modules.include? Loaf::ViewExtensions
|
||||
|
||||
breadcrumb current_usuarie.email, main_app.edit_usuarie_registration_path
|
||||
breadcrumb 'sites.index', main_app.sites_path, match: :exact
|
||||
breadcrumb site.title, main_app.site_path(site), match: :exact
|
||||
breadcrumb 'stats.index', root_path, match: :exact
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def configure_permitted_parameters
|
||||
|
|
194
app/controllers/concerns/blazer_decorator.rb
Normal file
194
app/controllers/concerns/blazer_decorator.rb
Normal file
|
@ -0,0 +1,194 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Modificaciones para Blazer
|
||||
module BlazerDecorator
|
||||
# No poder obtener información de la base de datos.
|
||||
module DisableDatabaseInfo
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
def docs; end
|
||||
|
||||
def tables; end
|
||||
|
||||
def schema; end
|
||||
end
|
||||
end
|
||||
|
||||
# Deshabilitar edición de consultas y chequeos.
|
||||
module DisableEdits
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
def create; end
|
||||
|
||||
def update; end
|
||||
|
||||
def destroy; end
|
||||
|
||||
def run; end
|
||||
|
||||
def refresh; end
|
||||
|
||||
def cancel; end
|
||||
end
|
||||
end
|
||||
|
||||
# Blazer hace un gran esfuerzo para ejecutar consultas de forma
|
||||
# asincrónica pero termina enviándolas por JS.
|
||||
module RunSync
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
alias_method :original_show, :show
|
||||
|
||||
include Blazer::BaseHelper
|
||||
|
||||
def show
|
||||
original_show
|
||||
|
||||
options = { user: blazer_user, query: @query, run_id: SecureRandom.uuid, async: false }
|
||||
@data_source = Blazer.data_sources[@query.data_source]
|
||||
@result = Blazer::RunStatement.new.perform(@data_source, @statement, options)
|
||||
chart_data
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Solo mostrar las consultas de le usuarie
|
||||
def set_queries(_ = nil)
|
||||
@queries = (@current_usuarie || current_usuarie).blazer_queries
|
||||
end
|
||||
|
||||
# blazer-2.4.2/app/views/blazer/queries/run.html.erb
|
||||
def chart_type
|
||||
case @result.chart_type
|
||||
when /\Aline(2)?\z/
|
||||
chart_options.merge! min: nil
|
||||
when /\Abar(2)?\z/
|
||||
chart_options.merge! library: { tooltips: { intersect: false, axis: 'x' } }
|
||||
when 'pie'
|
||||
chart_options
|
||||
when 'scatter'
|
||||
chart_options.merge! library: { tooltips: { intersect: false } }, xtitle: @result.columns[0],
|
||||
ytitle: @result.columns[1]
|
||||
when nil
|
||||
else
|
||||
if @result.column_types.size == 2
|
||||
chart_options.merge! library: { tooltips: { intersect: false, axis: 'x' } }
|
||||
else
|
||||
chart_options.merge! library: { tooltips: { intersect: false } }
|
||||
end
|
||||
end
|
||||
|
||||
@result.chart_type
|
||||
end
|
||||
|
||||
def chart_data
|
||||
@chart_data ||=
|
||||
case chart_type
|
||||
when 'line'
|
||||
@result.columns[1..-1].each_with_index.map do |k, i|
|
||||
{
|
||||
name: blazer_series_name(k),
|
||||
data: @result.rows.map do |r|
|
||||
[r[0], r[i + 1]]
|
||||
end,
|
||||
library: series_library[i]
|
||||
}
|
||||
end
|
||||
when 'line2'
|
||||
@result.rows.group_by do |r|
|
||||
v = r[1]
|
||||
(@result.boom[@result.columns[1]] || {})[v.to_s] || v
|
||||
end.each_with_index.map do |(name, v), i|
|
||||
{
|
||||
name: blazer_series_name(name),
|
||||
data: v.map do |v2|
|
||||
[v2[0], v2[2]]
|
||||
end,
|
||||
library: series_library[i]
|
||||
}
|
||||
end
|
||||
when 'pie'
|
||||
@result.rows.map do |r|
|
||||
[(@result.boom[@result.columns[0]] || {})[r[0].to_s] || r[0], r[1]]
|
||||
end
|
||||
when 'bar'
|
||||
(@result.rows.first.size - 1).times.map do |i|
|
||||
name = @result.columns[i + 1]
|
||||
|
||||
{
|
||||
name: blazer_series_name(name),
|
||||
data: @result.rows.first(20).map do |r|
|
||||
[(@result.boom[@result.columns[0]] || {})[r[0].to_s] || r[0], r[i + 1]]
|
||||
end
|
||||
}
|
||||
end
|
||||
when 'bar2'
|
||||
first_20 = @result.rows.group_by { |r| r[0] }.values.first(20).flatten(1)
|
||||
labels = first_20.map { |r| r[0] }.uniq
|
||||
series = first_20.map { |r| r[1] }.uniq
|
||||
labels.each do |l|
|
||||
series.each do |s|
|
||||
first_20 << [l, s, 0] unless first_20.find { |r| r[0] == l && r[1] == s }
|
||||
end
|
||||
end
|
||||
|
||||
first_20.group_by do |r|
|
||||
v = r[1]
|
||||
(@result.boom[@result.columns[1]] || {})[v.to_s] || v
|
||||
end.each_with_index.map do |(name, v), _i|
|
||||
{
|
||||
name: blazer_series_name(name),
|
||||
data: v.sort_by do |r2|
|
||||
labels.index(r2[0])
|
||||
end.map do |v2|
|
||||
v3 = v2[0]
|
||||
[(@result.boom[@result.columns[0]] || {})[v3.to_s] || v3, v2[2]]
|
||||
end
|
||||
}
|
||||
end
|
||||
when 'scatter'
|
||||
@result.rows
|
||||
end
|
||||
end
|
||||
|
||||
def target_index
|
||||
@target_index ||= @result.columns.index do |k|
|
||||
k.downcase == 'target'
|
||||
end
|
||||
end
|
||||
|
||||
def series_library
|
||||
@series_library ||= {}.tap do |sl|
|
||||
if target_index
|
||||
color = '#109618'
|
||||
sl[target_index - 1] = {
|
||||
pointStyle: 'line',
|
||||
hitRadius: 5,
|
||||
borderColor: color,
|
||||
pointBackgroundColor: color,
|
||||
backgroundColor: color,
|
||||
pointHoverBackgroundColor: color
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def chart_options
|
||||
@chart_options ||= { id: SecureRandom.hex }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
classes = [Blazer::QueriesController, Blazer::ChecksController, Blazer::DashboardsController]
|
||||
modules = [BlazerDecorator::DisableDatabaseInfo, BlazerDecorator::DisableEdits]
|
||||
classes.each do |klass|
|
||||
modules.each do |modul|
|
||||
klass.include modul unless klass.included_modules.include? modul
|
||||
end
|
||||
end
|
||||
|
||||
Blazer::QueriesController.include BlazerDecorator::RunSync
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
# Controlador para artículos
|
||||
class PostsController < ApplicationController
|
||||
include Pundit
|
||||
rescue_from Pundit::NilPolicyError, with: :page_not_found
|
||||
|
||||
before_action :authenticate_usuarie!
|
||||
|
||||
# TODO: Traer los comunes desde ApplicationController
|
||||
|
|
|
@ -6,8 +6,6 @@ class PrivateController < ApplicationController
|
|||
# XXX: Permite ejecutar JS
|
||||
skip_forgery_protection
|
||||
|
||||
include Pundit
|
||||
|
||||
# Enviar el archivo si existe, agregar una / al final siempre para no
|
||||
# romper las direcciones relativas.
|
||||
def show
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
# Controlador de sitios
|
||||
class SitesController < ApplicationController
|
||||
include Pundit
|
||||
rescue_from Pundit::NilPolicyError, with: :page_not_found
|
||||
|
||||
before_action :authenticate_usuarie!
|
||||
|
||||
breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Estadísticas del sitio
|
||||
class StatsController < ApplicationController
|
||||
include Pundit
|
||||
before_action :authenticate_usuarie!
|
||||
|
||||
def index
|
||||
@site = find_site
|
||||
authorize SiteStat.new(@site)
|
||||
|
||||
# Solo queremos el promedio de tiempo de compilación, no de
|
||||
# instalación de dependencias.
|
||||
stats = @site.build_stats.jekyll
|
||||
@build_avg = stats.average(:seconds).to_f.round(2)
|
||||
@build_max = stats.maximum(:seconds).to_f.round(2)
|
||||
end
|
||||
end
|
|
@ -1,3 +1,3 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
SiteStat = Struct.new(:site)
|
||||
SiteBlazer = Struct.new(:site)
|
|
@ -11,6 +11,8 @@ class Usuarie < ApplicationRecord
|
|||
|
||||
has_many :roles
|
||||
has_many :sites, through: :roles
|
||||
has_many :blazer_audits, foreign_key: 'user_id', class_name: 'Blazer::Audit'
|
||||
has_many :blazer_queries, foreign_key: 'creator_id', class_name: 'Blazer::Query'
|
||||
|
||||
def name
|
||||
email.split('@', 2).first
|
||||
|
|
10
app/policies/site_blazer_policy.rb
Normal file
10
app/policies/site_blazer_policy.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Les invitades no pueden ver las estadísticas (aun)
|
||||
SiteBlazerPolicy = Struct.new(:usuarie, :site_blazer) do
|
||||
def home?
|
||||
site_blazer&.site&.usuarie? usuarie
|
||||
end
|
||||
|
||||
alias_method :show?, :home?
|
||||
end
|
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Política de acceso a las estadísticas
|
||||
class SiteStatPolicy
|
||||
attr_reader :site_stat, :usuarie
|
||||
|
||||
def initialize(usuarie, site_stat)
|
||||
@usuarie = usuarie
|
||||
@site_stat = site_stat
|
||||
end
|
||||
|
||||
def index?
|
||||
site_stat.site.usuarie? usuarie
|
||||
end
|
||||
end
|
5
app/views/blazer/check_mailer/failing_checks.haml
Normal file
5
app/views/blazer/check_mailer/failing_checks.haml
Normal file
|
@ -0,0 +1,5 @@
|
|||
%ul
|
||||
- @checks.each do |check|
|
||||
%li
|
||||
= check.query.name
|
||||
= check.state
|
30
app/views/blazer/check_mailer/state_change.haml
Normal file
30
app/views/blazer/check_mailer/state_change.haml
Normal file
|
@ -0,0 +1,30 @@
|
|||
!!!
|
||||
%html
|
||||
%head
|
||||
%meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
|
||||
%body{:style => "font-family: 'Helvetica Neue', Arial, Helvetica; font-size: 14px; color: #333;"}
|
||||
- if @error
|
||||
%p= @error
|
||||
- elsif @rows_count > 0 && @check_type == "bad_data"
|
||||
%p
|
||||
- if @rows_count <= 10
|
||||
= pluralize(@rows_count, "row")
|
||||
- else
|
||||
Showing 10 of #{@rows_count} rows
|
||||
%table{:style => "width: 100%; border-spacing: 0; border-collapse: collapse;"}
|
||||
%thead
|
||||
%tr
|
||||
- @columns.first(5).each do |column|
|
||||
%th{:style => "padding: 8px; line-height: 1.4; text-align: left; vertical-align: bottom; border-bottom: 2px solid #ddd; width: #{(100 / @columns.size).round(2)}%;"}
|
||||
= column
|
||||
%tbody
|
||||
- @rows.first(10).each do |row|
|
||||
%tr
|
||||
- @columns.first(5).each_with_index do |column, i|
|
||||
%td{:style => "padding: 8px; line-height: 1.4; vertical-align: top; border-top: 1px solid #ddd;"}
|
||||
- value = row[i]
|
||||
- if @column_types[i] == "time" && value.to_s.length > 10
|
||||
- value = Time.parse(value).in_time_zone(Blazer.time_zone) rescue value
|
||||
= value
|
||||
- if @columns.size > 5
|
||||
%p{:style => "color: #999;"} Only first 5 columns shown
|
9
app/views/blazer/queries/home.haml
Normal file
9
app/views/blazer/queries/home.haml
Normal file
|
@ -0,0 +1,9 @@
|
|||
#queries
|
||||
%table.table
|
||||
%tbody.list
|
||||
- @queries.each do |query|
|
||||
%tr
|
||||
-#
|
||||
Por alguna razón no tenemos acceso a query_path para poder
|
||||
generar la URL según Rails
|
||||
%td= link_to query[:name], "/sites/#{params[:site_id]}/stats/queries/#{query.to_param}"
|
51
app/views/blazer/queries/show.haml
Normal file
51
app/views/blazer/queries/show.haml
Normal file
|
@ -0,0 +1,51 @@
|
|||
- blazer_title @query.name
|
||||
.container
|
||||
.row
|
||||
.col-12
|
||||
%h1= @query.name
|
||||
- if @query.description.present?
|
||||
%p.lead= @query.description
|
||||
- unless @result.chart_type.blank?
|
||||
.col-12
|
||||
- case @result.chart_type
|
||||
- when 'line'
|
||||
= line_chart @chart_data, **@chart_options
|
||||
- when 'line2'
|
||||
= line_chart @chart_data, **@chart_options
|
||||
- when 'pie'
|
||||
= pie_chart @chart_data, **@chart_options
|
||||
- when 'bar'
|
||||
= column_chart @chart_data, **@chart_options
|
||||
- when 'bar2'
|
||||
= column_chart @chart_data, **@chart_options
|
||||
- when 'scatter'
|
||||
= scatter_chart @chart_data, **@chart_options
|
||||
.col-12
|
||||
%table.table
|
||||
%thead
|
||||
%tr
|
||||
- @result.columns.each do |key|
|
||||
- next if key.include? 'ciphertext'
|
||||
- next if key.include? 'encrypted'
|
||||
%th.position-sticky.background-white{ style: 'top: 0' }= t("blazer.columns.#{key}", default: key.titleize)
|
||||
%tbody
|
||||
- @result.rows.each do |row|
|
||||
%tr
|
||||
- row.each_with_index do |v, i|
|
||||
- k = @result.columns[i]
|
||||
- next if k.include? 'ciphertext'
|
||||
- next if k.include? 'encrypted'
|
||||
%td
|
||||
- if v.is_a?(Time)
|
||||
- v = blazer_time_value(@data_source, k, v)
|
||||
|
||||
- unless v.nil?
|
||||
- if v.is_a?(String) && v.empty?
|
||||
%span.text-muted= t('.empty')
|
||||
- elsif @data_source.linked_columns[k]
|
||||
= link_to blazer_format_value(k, v), @data_source.linked_columns[k].gsub('{value}', u(v.to_s)), target: '_blank'
|
||||
- else
|
||||
= blazer_format_value(k, v)
|
||||
|
||||
- if (v2 = (@result.boom[k] || {})[v.nil? ? v : v.to_s])
|
||||
%span.text-muted= v2
|
|
@ -12,7 +12,7 @@
|
|||
- else
|
||||
%span.line-clamp-1= link_to crumb.name, crumb.url
|
||||
|
||||
- if current_usuarie
|
||||
- if @current_usuarie || current_usuarie
|
||||
%ul.navbar-nav
|
||||
- if @site&.tienda?
|
||||
%li.nav-item
|
||||
|
@ -20,5 +20,5 @@
|
|||
role: 'button', class: 'btn'
|
||||
|
||||
%li.nav-item
|
||||
= link_to t('.logout'), destroy_usuarie_session_path,
|
||||
= link_to t('.logout'), main_app.destroy_usuarie_session_path,
|
||||
method: :delete, role: 'button', class: 'btn'
|
||||
|
|
14
app/views/layouts/blazer/application.haml
Normal file
14
app/views/layouts/blazer/application.haml
Normal file
|
@ -0,0 +1,14 @@
|
|||
!!!
|
||||
%html
|
||||
%head
|
||||
%meta{content: 'text/html; charset=UTF-8', 'http-equiv': 'Content-Type'}/
|
||||
%title= blazer_title ? blazer_title : 'Sutty'
|
||||
%meta{charset: 'utf-8'}/
|
||||
= favicon_link_tag 'blazer/favicon.png'
|
||||
= stylesheet_link_tag 'application'
|
||||
= javascript_pack_tag 'blazer', 'data-turbolinks-track': 'reload'
|
||||
= csrf_meta_tags
|
||||
%body{ class: yield(:body) }
|
||||
.container-fluid#sutty
|
||||
= render 'layouts/breadcrumb'
|
||||
= yield
|
|
@ -38,6 +38,13 @@ module Sutty
|
|||
|
||||
config.active_storage.variant_processor = :vips
|
||||
|
||||
config.to_prepare do
|
||||
# Load application's model / class decorators
|
||||
Dir.glob(File.join(File.dirname(__FILE__), '../app/**/*_decorator.rb')) do |c|
|
||||
Rails.configuration.cache_classes ? require(c) : load(c)
|
||||
end
|
||||
end
|
||||
|
||||
config.after_initialize do
|
||||
ActiveStorage::DirectUploadsController.include ActiveStorage::AuthenticatedDirectUploadsController
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ user_method: current_usuarie
|
|||
user_name: email
|
||||
|
||||
# custom before_action to use for auth
|
||||
# before_action_method: require_admin
|
||||
before_action_method: require_usuarie
|
||||
|
||||
# email to send checks from
|
||||
from_email: blazer@<%= ENV.fetch('SUTTY', 'sutty.nl') %>
|
||||
|
|
|
@ -576,3 +576,14 @@ en:
|
|||
edit: 'Editing'
|
||||
usuaries:
|
||||
index: 'Users'
|
||||
stats:
|
||||
index: 'Statistics'
|
||||
blazer:
|
||||
columns:
|
||||
total: 'Total'
|
||||
dia: 'Date'
|
||||
date: 'Date'
|
||||
visitas: 'Visits'
|
||||
queries:
|
||||
show:
|
||||
empty: '(empty)'
|
||||
|
|
|
@ -584,3 +584,14 @@ es:
|
|||
edit: 'Editando'
|
||||
usuaries:
|
||||
index: 'Usuaries'
|
||||
stats:
|
||||
index: 'Estadísticas'
|
||||
blazer:
|
||||
columns:
|
||||
total: 'Total'
|
||||
dia: 'Fecha'
|
||||
date: 'Fecha'
|
||||
visitas: 'Visitas'
|
||||
queries:
|
||||
show:
|
||||
empty: '(vacío)'
|
||||
|
|
|
@ -4,8 +4,6 @@ Rails.application.routes.draw do
|
|||
devise_for :usuaries
|
||||
get '/.well-known/change-password', to: redirect('/usuaries/edit')
|
||||
|
||||
mount Blazer::Engine, at: 'blazer'
|
||||
|
||||
root 'application#index'
|
||||
|
||||
constraints(Constraints::ApiSubdomain.new) do
|
||||
|
@ -38,6 +36,9 @@ Rails.application.routes.draw do
|
|||
match '/api/v3/projects/:site_id/notices' => 'api/v1/notices#create', via: %i[post]
|
||||
|
||||
resources :sites, constraints: { site_id: %r{[^/]+}, id: %r{[^/]+} } do
|
||||
# Usar Blazer para mostrar estadísticas
|
||||
mount Blazer::Engine, at: 'stats', as: 'stats'
|
||||
|
||||
# Gestionar actualizaciones del sitio
|
||||
get 'pull', to: 'sites#fetch'
|
||||
post 'pull', to: 'sites#merge'
|
||||
|
@ -73,7 +74,5 @@ Rails.application.routes.draw do
|
|||
# Compilar el sitio
|
||||
post 'enqueue', to: 'sites#enqueue'
|
||||
post 'reorder_posts', to: 'sites#reorder_posts'
|
||||
|
||||
resources :stats, only: [:index]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
# Blazer
|
||||
class InstallBlazer < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
return unless Rails.env.production?
|
||||
|
||||
create_table :blazer_queries do |t|
|
||||
t.references :creator
|
||||
t.string :name
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
"@rails/webpacker": "5.2.1",
|
||||
"@suttyweb/editor": "0.0.7",
|
||||
"babel-loader": "^8.2.2",
|
||||
"chart.js": "2.9.3",
|
||||
"chartkick": "3.2.1",
|
||||
"circular-dependency-plugin": "^5.2.2",
|
||||
"commonmark": "^0.29.0",
|
||||
"fork-awesome": "^1.1.7",
|
||||
|
|
35
yarn.lock
35
yarn.lock
|
@ -2124,6 +2124,34 @@ chalk@^4.1.0:
|
|||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chart.js@2.9.3:
|
||||
version "2.9.3"
|
||||
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.3.tgz#ae3884114dafd381bc600f5b35a189138aac1ef7"
|
||||
integrity sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==
|
||||
dependencies:
|
||||
chartjs-color "^2.1.0"
|
||||
moment "^2.10.2"
|
||||
|
||||
chartjs-color-string@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71"
|
||||
integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==
|
||||
dependencies:
|
||||
color-name "^1.0.0"
|
||||
|
||||
chartjs-color@^2.1.0:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.4.1.tgz#6118bba202fe1ea79dd7f7c0f9da93467296c3b0"
|
||||
integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==
|
||||
dependencies:
|
||||
chartjs-color-string "^0.6.0"
|
||||
color-convert "^1.9.3"
|
||||
|
||||
chartkick@3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/chartkick/-/chartkick-3.2.1.tgz#a80c2005ae353c5ae011d0a756b6f592fc8fc7a9"
|
||||
integrity sha512-zV0kUeZNqrX28AmPt10QEDXHKadbVFOTAFkCMyJifHzGFkKzGCDXxVR8orZ0fC1HbePzRn5w6kLCOVxDQbMUCg==
|
||||
|
||||
chokidar@^2.1.8:
|
||||
version "2.1.8"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
|
||||
|
@ -2243,7 +2271,7 @@ collection-visit@^1.0.0:
|
|||
map-visit "^1.0.0"
|
||||
object-visit "^1.0.0"
|
||||
|
||||
color-convert@^1.9.0, color-convert@^1.9.1:
|
||||
color-convert@^1.9.0, color-convert@^1.9.1, color-convert@^1.9.3:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
|
||||
|
@ -5010,6 +5038,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
|
|||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
moment@^2.10.2:
|
||||
version "2.29.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
||||
|
||||
move-concurrently@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
|
||||
|
|
Loading…
Reference in a new issue