2019-08-02 00:20:42 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
# Estadísticas del sitio
|
|
|
|
class StatsController < ApplicationController
|
|
|
|
include Pundit
|
2021-10-08 21:35:40 +00:00
|
|
|
include ActionView::Helpers::DateHelper
|
|
|
|
|
2019-08-02 00:20:42 +00:00
|
|
|
before_action :authenticate_usuarie!
|
2021-10-08 21:33:31 +00:00
|
|
|
before_action :authorize_stats
|
2019-08-02 00:20:42 +00:00
|
|
|
|
2021-10-08 21:40:16 +00:00
|
|
|
EXTRA_OPTIONS = {
|
|
|
|
builds: {},
|
|
|
|
space_used: { bytes: true },
|
|
|
|
build_time: {}
|
|
|
|
}.freeze
|
|
|
|
|
2021-10-08 19:31:02 +00:00
|
|
|
# XXX: Permitir a Chart.js inyectar su propio CSS
|
|
|
|
content_security_policy only: :index do |policy|
|
|
|
|
policy.style_src :self, :unsafe_inline
|
|
|
|
policy.script_src :self, :unsafe_inline
|
|
|
|
end
|
|
|
|
|
2019-08-02 00:20:42 +00:00
|
|
|
def index
|
2021-10-08 19:31:02 +00:00
|
|
|
@chart_params = { interval: interval }
|
|
|
|
hostnames
|
2021-10-08 21:24:19 +00:00
|
|
|
last_stat
|
2021-10-08 21:35:40 +00:00
|
|
|
chart_options
|
2021-10-09 20:27:45 +00:00
|
|
|
normalized_urls
|
2021-10-08 19:31:02 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Genera un gráfico de visitas por dominio asociado a este sitio
|
|
|
|
def host
|
2021-10-09 18:52:18 +00:00
|
|
|
return unless stale? [last_stat, hostnames, interval]
|
|
|
|
|
|
|
|
stats = Rollup.where_dimensions(host: hostnames).multi_series('host', interval: interval).tap do |series|
|
|
|
|
series.each do |serie|
|
|
|
|
serie[:name] = serie.dig(:dimensions, 'host')
|
|
|
|
serie[:data].transform_values! do |value|
|
|
|
|
value * nodes
|
2021-10-08 19:31:02 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2021-10-09 18:52:18 +00:00
|
|
|
|
|
|
|
render json: stats
|
2021-10-08 21:39:55 +00:00
|
|
|
end
|
2021-10-08 19:31:02 +00:00
|
|
|
|
2021-10-08 21:39:55 +00:00
|
|
|
def resources
|
2021-10-09 18:52:18 +00:00
|
|
|
return unless stale? [last_stat, interval, resource]
|
|
|
|
|
|
|
|
options = {
|
|
|
|
interval: interval,
|
|
|
|
dimensions: {
|
|
|
|
deploy_id: @site.deploys.where(type: 'DeployLocal').pluck(:id).first
|
2021-10-08 21:39:55 +00:00
|
|
|
}
|
2021-10-09 18:52:18 +00:00
|
|
|
}
|
2021-10-08 21:39:55 +00:00
|
|
|
|
2021-10-09 18:52:18 +00:00
|
|
|
render json: Rollup.series(resource, **options)
|
2021-10-08 19:31:02 +00:00
|
|
|
end
|
|
|
|
|
2021-10-09 20:27:45 +00:00
|
|
|
def uris
|
|
|
|
return unless stale? [last_stat, hostnames, interval, normalized_urls]
|
|
|
|
|
|
|
|
options = { host: hostnames, uri: normalized_paths }
|
|
|
|
stats = Rollup.where_dimensions(**options).multi_series('host|uri', interval: interval).tap do |series|
|
|
|
|
series.each do |serie|
|
2021-10-09 21:29:53 +00:00
|
|
|
serie[:name] = serie[:dimensions].slice('host', 'uri').values.join.sub('/index.html', '/')
|
2021-10-09 20:27:45 +00:00
|
|
|
serie[:data].transform_values! do |value|
|
|
|
|
value * nodes
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
render json: stats
|
|
|
|
end
|
|
|
|
|
2021-10-08 19:31:02 +00:00
|
|
|
private
|
|
|
|
|
2021-10-08 21:24:19 +00:00
|
|
|
def last_stat
|
|
|
|
@last_stat ||= Stat.last
|
|
|
|
end
|
|
|
|
|
2021-10-08 21:33:31 +00:00
|
|
|
def authorize_stats
|
|
|
|
@site = find_site
|
|
|
|
authorize SiteStat.new(@site)
|
|
|
|
end
|
|
|
|
|
2021-10-08 19:31:02 +00:00
|
|
|
# TODO: Eliminar cuando mergeemos referer-origin
|
|
|
|
def hostnames
|
|
|
|
@hostnames ||= [@site.hostname, @site.alternative_hostnames].flatten
|
|
|
|
end
|
|
|
|
|
2021-10-09 20:27:45 +00:00
|
|
|
# Normalizar las URLs
|
|
|
|
#
|
|
|
|
# @return [Array]
|
|
|
|
def normalized_urls
|
|
|
|
@normalized_urls ||= params.permit(:urls).try(:[],
|
|
|
|
:urls)&.split("\n")&.map(&:strip)&.select(&:present?)&.select do |uri|
|
|
|
|
uri.start_with? 'https://'
|
|
|
|
end&.map do |u|
|
|
|
|
# XXX: Eliminar
|
|
|
|
# @see {https://0xacab.org/sutty/containers/nginx/-/merge_requests/1}
|
|
|
|
next u unless u.end_with? '/'
|
|
|
|
|
|
|
|
"#{u}index.html"
|
2021-10-09 21:27:42 +00:00
|
|
|
end&.uniq || [@site.url, @site.urls].flatten.uniq
|
2021-10-09 20:27:45 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def normalized_paths
|
|
|
|
@normalized_paths ||= normalized_urls.map do |u|
|
|
|
|
"/#{u.split('/', 4).last}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-10-08 21:35:40 +00:00
|
|
|
# Opciones por defecto para los gráficos.
|
|
|
|
#
|
|
|
|
# La invitación a volver dentro de X tiempo es para dar un estimado de
|
|
|
|
# cuándo habrá información disponible, porque Rollup genera intervalos
|
|
|
|
# completos (¿aunque dice que no?)
|
|
|
|
#
|
|
|
|
# La diferencia se calcula sumando el intervalo a la hora de última
|
|
|
|
# toma de estadísticas y restando el tiempo que pasó desde ese
|
|
|
|
# momento.
|
|
|
|
def chart_options
|
2021-10-09 18:50:38 +00:00
|
|
|
time = last_stat.created_at + 1.try(interval)
|
2021-10-08 21:35:40 +00:00
|
|
|
please_return_at = { please_return_at: distance_of_time_in_words(Time.now, time) }
|
|
|
|
|
|
|
|
@chart_options ||= {
|
|
|
|
locale: I18n.locale,
|
|
|
|
empty: I18n.t('stats.index.empty', **please_return_at),
|
|
|
|
loading: I18n.t('stats.index.loading'),
|
|
|
|
html: %(<div id="%{id}" class="d-flex align-items-center justify-content-center" style="height: %{height}; width: %{width};">%{loading}</div>)
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2021-10-08 19:31:02 +00:00
|
|
|
# Obtiene y valida los intervalos
|
|
|
|
#
|
|
|
|
# @return [Symbol]
|
|
|
|
def interval
|
|
|
|
@interval ||= begin
|
2021-10-09 20:28:26 +00:00
|
|
|
i = params[:interval]&.to_sym
|
2021-10-09 18:50:38 +00:00
|
|
|
Stat::INTERVALS.include?(i) ? i : :day
|
2021-10-08 19:31:02 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-10-08 21:39:55 +00:00
|
|
|
def resource
|
|
|
|
@resource ||= begin
|
|
|
|
r = params[:resource].to_sym
|
2021-10-09 18:50:38 +00:00
|
|
|
Stat::RESOURCES.include?(r) ? r : :builds
|
2021-10-08 21:39:55 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-10-08 19:31:02 +00:00
|
|
|
# Obtiene la cantidad de nodos de Sutty, para poder calcular la
|
|
|
|
# cantidad de visitas.
|
|
|
|
#
|
|
|
|
# Como repartimos las visitas por nodo rotando las IPs en el
|
|
|
|
# nameserver y los resolvedores de DNS eligen un nameserver
|
|
|
|
# aleatoriamente, la cantidad de visitas se reparte
|
|
|
|
# equitativamente.
|
|
|
|
#
|
|
|
|
# XXX: Remover cuando podamos centralizar los AccessLog
|
|
|
|
#
|
|
|
|
# @return [Integer]
|
|
|
|
def nodes
|
|
|
|
@nodes ||= ENV.fetch('NODES', 1).to_i
|
2019-08-02 00:20:42 +00:00
|
|
|
end
|
|
|
|
end
|