# frozen_string_literal: true # Estadísticas del sitio class StatsController < ApplicationController include Pundit include ActionView::Helpers::DateHelper before_action :authenticate_usuarie! before_action :authorize_stats INTERVALS = %i[hour day week month year].freeze RESOURCES = %i[builds space_used build_time].freeze EXTRA_OPTIONS = { builds: {}, space_used: { bytes: true }, build_time: {} }.freeze # 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 def index @chart_params = { interval: interval } hostnames last_stat chart_options end # Genera un gráfico de visitas por dominio asociado a este sitio def host if 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 end end end render json: stats end end def resources if stale? [last_stat, interval, resource] options = { interval: interval, dimensions: { deploy_id: @site.deploys.where(type: 'DeployLocal').pluck(:id).first } } render json: Rollup.series(resource, **options) end end private def last_stat @last_stat ||= Stat.last end def authorize_stats @site = find_site authorize SiteStat.new(@site) end # TODO: Eliminar cuando mergeemos referer-origin def hostnames @hostnames ||= [@site.hostname, @site.alternative_hostnames].flatten end # 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 time = last_stat.created_at + (1.send(interval)) 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: %(
%{loading}
) } end # Obtiene y valida los intervalos # # @return [Symbol] def interval @interval ||= begin i = params[:interval].to_sym INTERVALS.include?(i) ? i : :day end end def resource @resource ||= begin r = params[:resource].to_sym RESOURCES.include?(r) ? r : :builds end end # 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 end end