Compare commits
No commits in common. "f8c8d45fde9e3e54832e83403040b7a3a8e97a6a" and "rails" have entirely different histories.
f8c8d45fde
...
rails
40 changed files with 113 additions and 852 deletions
|
@ -1,4 +1,3 @@
|
||||||
RAILS_GROUPS=assets
|
|
||||||
DELEGATE=athshe.sutty.nl
|
DELEGATE=athshe.sutty.nl
|
||||||
HAINISH=../haini.sh/haini.sh
|
HAINISH=../haini.sh/haini.sh
|
||||||
DATABASE=
|
DATABASE=
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
pipeline:
|
|
||||||
chequear:
|
|
||||||
image: registry.nulo.in/sutty/haini.sh:root
|
|
||||||
commands:
|
|
||||||
- make bundle hain="sh -c"
|
|
||||||
- make brakeman hain="sh -c"
|
|
||||||
- make rubocop hain="sh -c"
|
|
23
Dockerfile
23
Dockerfile
|
@ -2,7 +2,7 @@
|
||||||
# el mismo repositorio de trabajo. Cuando tengamos CI/CD algunas cosas
|
# el mismo repositorio de trabajo. Cuando tengamos CI/CD algunas cosas
|
||||||
# como el tarball van a tener que cambiar porque ya vamos a haber hecho
|
# como el tarball van a tener que cambiar porque ya vamos a haber hecho
|
||||||
# un clone/pull limpio.
|
# un clone/pull limpio.
|
||||||
FROM alpine:3.13.6 AS build
|
FROM alpine:3.13.5 AS build
|
||||||
MAINTAINER "f <f@sutty.nl>"
|
MAINTAINER "f <f@sutty.nl>"
|
||||||
|
|
||||||
ARG RAILS_MASTER_KEY
|
ARG RAILS_MASTER_KEY
|
||||||
|
@ -14,10 +14,10 @@ ENV SECRET_KEY_BASE solo_es_necesaria_para_correr_rake
|
||||||
ENV RAILS_ENV production
|
ENV RAILS_ENV production
|
||||||
ENV RAILS_MASTER_KEY=$RAILS_MASTER_KEY
|
ENV RAILS_MASTER_KEY=$RAILS_MASTER_KEY
|
||||||
|
|
||||||
RUN apk add --no-cache libxslt libxml2 tzdata ruby ruby-json ruby-bigdecimal ruby-rake
|
RUN apk add --no-cache libxslt libxml2 tzdata ruby ruby-bundler ruby-json ruby-bigdecimal ruby-rake
|
||||||
RUN apk add --no-cache postgresql-libs git yarn brotli libssh2 python3
|
RUN apk add --no-cache postgresql-libs git yarn brotli libssh2 python3
|
||||||
|
|
||||||
RUN test "2.7.4" = `ruby -e 'puts RUBY_VERSION'`
|
RUN test "2.7.3" = `ruby -e 'puts RUBY_VERSION'`
|
||||||
|
|
||||||
# https://github.com/rubygems/rubygems/issues/2918
|
# https://github.com/rubygems/rubygems/issues/2918
|
||||||
# https://gitlab.alpinelinux.org/alpine/aports/issues/10808
|
# https://gitlab.alpinelinux.org/alpine/aports/issues/10808
|
||||||
|
@ -29,7 +29,7 @@ RUN cd /usr/lib/ruby/2.7.0 && patch -Np 0 -i /tmp/rubygems-platform-musl.patch
|
||||||
RUN addgroup -g 82 -S www-data
|
RUN addgroup -g 82 -S www-data
|
||||||
RUN adduser -s /bin/sh -G www-data -h /home/app -D app
|
RUN adduser -s /bin/sh -G www-data -h /home/app -D app
|
||||||
RUN install -dm750 -o app -g www-data /home/app/sutty
|
RUN install -dm750 -o app -g www-data /home/app/sutty
|
||||||
RUN gem install --no-document bundler:2.1.4
|
RUN gem install --no-document bundler
|
||||||
|
|
||||||
# Empezamos con la usuaria app
|
# Empezamos con la usuaria app
|
||||||
USER app
|
USER app
|
||||||
|
@ -39,8 +39,7 @@ WORKDIR /home/app/sutty
|
||||||
# Copiamos solo el Gemfile para poder instalar las gemas necesarias
|
# Copiamos solo el Gemfile para poder instalar las gemas necesarias
|
||||||
COPY --chown=app:www-data ./Gemfile .
|
COPY --chown=app:www-data ./Gemfile .
|
||||||
COPY --chown=app:www-data ./Gemfile.lock .
|
COPY --chown=app:www-data ./Gemfile.lock .
|
||||||
RUN bundle config set no-cache true
|
RUN bundle config set no-cache 'true'
|
||||||
RUN bundle config set specific_platform true
|
|
||||||
RUN bundle install --path=./vendor --without='test development'
|
RUN bundle install --path=./vendor --without='test development'
|
||||||
# Vaciar la caché
|
# Vaciar la caché
|
||||||
RUN rm vendor/ruby/2.7.0/cache/*.gem
|
RUN rm vendor/ruby/2.7.0/cache/*.gem
|
||||||
|
@ -61,6 +60,10 @@ RUN mv ../sutty/.bundle ./.bundle
|
||||||
# Instalar secretos
|
# Instalar secretos
|
||||||
COPY --chown=app:root ./config/credentials.yml.enc ./config/
|
COPY --chown=app:root ./config/credentials.yml.enc ./config/
|
||||||
|
|
||||||
|
# Eliminar la necesidad de un runtime JS en producción, porque los
|
||||||
|
# assets ya están pre-compilados.
|
||||||
|
RUN sed -re "/(sassc|uglifier|bootstrap|coffee-rails)/d" -i Gemfile
|
||||||
|
RUN bundle clean
|
||||||
RUN rm -rf ./node_modules ./tmp/cache ./.git ./test ./doc
|
RUN rm -rf ./node_modules ./tmp/cache ./.git ./test ./doc
|
||||||
# Eliminar archivos innecesarios
|
# Eliminar archivos innecesarios
|
||||||
USER root
|
USER root
|
||||||
|
@ -68,7 +71,7 @@ RUN apk add --no-cache findutils
|
||||||
RUN find /home/app/checkout/vendor/ruby/2.7.0 -maxdepth 3 -type d -name test -o -name spec -o -name rubocop | xargs -r rm -rf
|
RUN find /home/app/checkout/vendor/ruby/2.7.0 -maxdepth 3 -type d -name test -o -name spec -o -name rubocop | xargs -r rm -rf
|
||||||
|
|
||||||
# Contenedor final
|
# Contenedor final
|
||||||
FROM registry.nulo.in/sutty/monit:3.13.6
|
FROM sutty/monit:latest
|
||||||
ENV RAILS_ENV production
|
ENV RAILS_ENV production
|
||||||
|
|
||||||
# Pandoc
|
# Pandoc
|
||||||
|
@ -76,13 +79,13 @@ RUN echo 'http://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/reposit
|
||||||
|
|
||||||
# Instalar las dependencias, separamos la librería de base de datos para
|
# Instalar las dependencias, separamos la librería de base de datos para
|
||||||
# poder reutilizar este primer paso desde otros contenedores
|
# poder reutilizar este primer paso desde otros contenedores
|
||||||
RUN apk add --no-cache libxslt libxml2 tzdata ruby ruby-json ruby-bigdecimal ruby-rake ruby-irb ruby-io-console ruby-etc
|
RUN apk add --no-cache libxslt libxml2 tzdata ruby ruby-bundler ruby-json ruby-bigdecimal ruby-rake ruby-irb
|
||||||
RUN apk add --no-cache postgresql-libs libssh2 file rsync git jpegoptim vips
|
RUN apk add --no-cache postgresql-libs libssh2 file rsync git jpegoptim vips
|
||||||
RUN apk add --no-cache ffmpeg imagemagick pandoc tectonic oxipng jemalloc
|
RUN apk add --no-cache ffmpeg imagemagick pandoc tectonic oxipng jemalloc
|
||||||
RUN apk add --no-cache git-lfs openssh-client patch
|
RUN apk add --no-cache git-lfs openssh-client patch
|
||||||
|
|
||||||
# Chequear que la versión de ruby sea la correcta
|
# Chequear que la versión de ruby sea la correcta
|
||||||
RUN test "2.7.4" = `ruby -e 'puts RUBY_VERSION'`
|
RUN test "2.7.3" = `ruby -e 'puts RUBY_VERSION'`
|
||||||
|
|
||||||
# https://github.com/rubygems/rubygems/issues/2918
|
# https://github.com/rubygems/rubygems/issues/2918
|
||||||
# https://gitlab.alpinelinux.org/alpine/aports/issues/10808
|
# https://gitlab.alpinelinux.org/alpine/aports/issues/10808
|
||||||
|
@ -94,7 +97,7 @@ RUN apk add --no-cache patch && cd /usr/lib/ruby/2.7.0 && patch -Np 0 -i /tmp/ru
|
||||||
# principal
|
# principal
|
||||||
RUN apk add --no-cache yarn
|
RUN apk add --no-cache yarn
|
||||||
# Instalar foreman para poder correr los servicios
|
# Instalar foreman para poder correr los servicios
|
||||||
RUN gem install --no-document --no-user-install bundler:2.1.4 foreman
|
RUN gem install --no-document --no-user-install bundler foreman
|
||||||
|
|
||||||
# Agregar el grupo del servidor web y la usuaria
|
# Agregar el grupo del servidor web y la usuaria
|
||||||
RUN addgroup -g 82 -S www-data
|
RUN addgroup -g 82 -S www-data
|
||||||
|
|
20
Gemfile
20
Gemfile
|
@ -11,16 +11,13 @@ gem 'dotenv-rails', require: 'dotenv/rails-now'
|
||||||
gem 'rails', '~> 6'
|
gem 'rails', '~> 6'
|
||||||
# Use Puma as the app server
|
# Use Puma as the app server
|
||||||
gem 'puma'
|
gem 'puma'
|
||||||
|
# See https://github.com/rails/execjs#readme for more supported runtimes
|
||||||
# Solo incluir las gemas cuando estemos en desarrollo o compilando los
|
# gem 'therubyracer', platforms: :ruby
|
||||||
# assets. No es necesario instalarlas en producción.
|
# Use SCSS for stylesheets
|
||||||
#
|
gem 'sassc-rails'
|
||||||
# XXX: Supuestamente Rails ya soporta RAILS_GROUPS, pero Bundler no.
|
# Use Uglifier as compressor for JavaScript assets
|
||||||
if ENV['RAILS_GROUPS']&.split(',')&.include? 'assets'
|
gem 'uglifier', '>= 1.3.0'
|
||||||
gem 'sassc-rails'
|
gem 'bootstrap', '~> 4'
|
||||||
gem 'uglifier', '>= 1.3.0'
|
|
||||||
gem 'bootstrap', '~> 4'
|
|
||||||
end
|
|
||||||
|
|
||||||
# Turbolinks makes navigating your web application faster. Read more:
|
# Turbolinks makes navigating your web application faster. Read more:
|
||||||
# https://github.com/turbolinks/turbolinks
|
# https://github.com/turbolinks/turbolinks
|
||||||
|
@ -31,7 +28,6 @@ gem 'jbuilder', '~> 2.5'
|
||||||
# Use ActiveModel has_secure_password
|
# Use ActiveModel has_secure_password
|
||||||
gem 'bcrypt', '~> 3.1.7'
|
gem 'bcrypt', '~> 3.1.7'
|
||||||
gem 'blazer'
|
gem 'blazer'
|
||||||
gem 'chartkick'
|
|
||||||
gem 'commonmarker'
|
gem 'commonmarker'
|
||||||
gem 'devise'
|
gem 'devise'
|
||||||
gem 'devise-i18n'
|
gem 'devise-i18n'
|
||||||
|
@ -62,7 +58,6 @@ gem 'rails-i18n'
|
||||||
gem 'rails_warden'
|
gem 'rails_warden'
|
||||||
gem 'redis', require: %w[redis redis/connection/hiredis]
|
gem 'redis', require: %w[redis redis/connection/hiredis]
|
||||||
gem 'redis-rails'
|
gem 'redis-rails'
|
||||||
gem 'rollups', git: 'https://github.com/ankane/rollup.git', branch: 'master'
|
|
||||||
gem 'rubyzip'
|
gem 'rubyzip'
|
||||||
gem 'rugged'
|
gem 'rugged'
|
||||||
gem 'concurrent-ruby-ext'
|
gem 'concurrent-ruby-ext'
|
||||||
|
@ -72,7 +67,6 @@ gem 'terminal-table'
|
||||||
gem 'validates_hostname'
|
gem 'validates_hostname'
|
||||||
gem 'webpacker'
|
gem 'webpacker'
|
||||||
gem 'yaml_db', git: 'https://0xacab.org/sutty/yaml_db.git'
|
gem 'yaml_db', git: 'https://0xacab.org/sutty/yaml_db.git'
|
||||||
gem 'kaminari'
|
|
||||||
|
|
||||||
# database
|
# database
|
||||||
gem 'hairtrigger'
|
gem 'hairtrigger'
|
||||||
|
|
30
Gemfile.lock
30
Gemfile.lock
|
@ -6,15 +6,6 @@ GIT
|
||||||
rails (>= 3.0)
|
rails (>= 3.0)
|
||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
|
|
||||||
GIT
|
|
||||||
remote: https://github.com/ankane/rollup.git
|
|
||||||
revision: 94ca777d54180c23e96ac4b4285cc9b405ccbd1a
|
|
||||||
branch: master
|
|
||||||
specs:
|
|
||||||
rollups (0.1.2)
|
|
||||||
activesupport (>= 5.1)
|
|
||||||
groupdate (>= 5.2)
|
|
||||||
|
|
||||||
GIT
|
GIT
|
||||||
remote: https://github.com/fauno/email_address
|
remote: https://github.com/fauno/email_address
|
||||||
revision: 536b51f7071b68a55140c0c1726b4cd401d1c04d
|
revision: 536b51f7071b68a55140c0c1726b4cd401d1c04d
|
||||||
|
@ -214,8 +205,6 @@ GEM
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
globalid (0.4.2)
|
globalid (0.4.2)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
groupdate (5.2.2)
|
|
||||||
activesupport (>= 5)
|
|
||||||
hairtrigger (0.2.24)
|
hairtrigger (0.2.24)
|
||||||
activerecord (>= 5.0, < 7)
|
activerecord (>= 5.0, < 7)
|
||||||
ruby2ruby (~> 2.4)
|
ruby2ruby (~> 2.4)
|
||||||
|
@ -320,18 +309,6 @@ GEM
|
||||||
jekyll-write-and-commit-changes (0.1.2)
|
jekyll-write-and-commit-changes (0.1.2)
|
||||||
jekyll (~> 4)
|
jekyll (~> 4)
|
||||||
rugged (~> 1)
|
rugged (~> 1)
|
||||||
kaminari (1.2.1)
|
|
||||||
activesupport (>= 4.1.0)
|
|
||||||
kaminari-actionview (= 1.2.1)
|
|
||||||
kaminari-activerecord (= 1.2.1)
|
|
||||||
kaminari-core (= 1.2.1)
|
|
||||||
kaminari-actionview (1.2.1)
|
|
||||||
actionview
|
|
||||||
kaminari-core (= 1.2.1)
|
|
||||||
kaminari-activerecord (1.2.1)
|
|
||||||
activerecord
|
|
||||||
kaminari-core (= 1.2.1)
|
|
||||||
kaminari-core (1.2.1)
|
|
||||||
kramdown (2.3.1)
|
kramdown (2.3.1)
|
||||||
rexml
|
rexml
|
||||||
kramdown-parser-gfm (1.1.0)
|
kramdown-parser-gfm (1.1.0)
|
||||||
|
@ -368,7 +345,6 @@ GEM
|
||||||
mini_histogram (0.3.1)
|
mini_histogram (0.3.1)
|
||||||
mini_magick (4.11.0)
|
mini_magick (4.11.0)
|
||||||
mini_mime (1.1.0)
|
mini_mime (1.1.0)
|
||||||
mini_portile2 (2.5.3)
|
|
||||||
minima (2.5.1)
|
minima (2.5.1)
|
||||||
jekyll (>= 3.5, < 5.0)
|
jekyll (>= 3.5, < 5.0)
|
||||||
jekyll-feed (~> 0.9)
|
jekyll-feed (~> 0.9)
|
||||||
|
@ -381,8 +357,7 @@ GEM
|
||||||
net-ssh (6.1.0)
|
net-ssh (6.1.0)
|
||||||
netaddr (2.0.4)
|
netaddr (2.0.4)
|
||||||
nio4r (2.5.7-x86_64-linux-musl)
|
nio4r (2.5.7-x86_64-linux-musl)
|
||||||
nokogiri (1.11.7-x86_64-linux-musl)
|
nokogiri (1.11.7-x86_64-linux)
|
||||||
mini_portile2 (~> 2.5.0)
|
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
parallel (1.20.1)
|
parallel (1.20.1)
|
||||||
|
@ -660,7 +635,6 @@ DEPENDENCIES
|
||||||
bootstrap (~> 4)
|
bootstrap (~> 4)
|
||||||
brakeman
|
brakeman
|
||||||
capybara (~> 2.13)
|
capybara (~> 2.13)
|
||||||
chartkick
|
|
||||||
commonmarker
|
commonmarker
|
||||||
concurrent-ruby-ext
|
concurrent-ruby-ext
|
||||||
database_cleaner
|
database_cleaner
|
||||||
|
@ -693,7 +667,6 @@ DEPENDENCIES
|
||||||
jekyll-data!
|
jekyll-data!
|
||||||
jekyll-images
|
jekyll-images
|
||||||
jekyll-include-cache
|
jekyll-include-cache
|
||||||
kaminari
|
|
||||||
letter_opener
|
letter_opener
|
||||||
listen (>= 3.0.5, < 3.2)
|
listen (>= 3.0.5, < 3.2)
|
||||||
loaf
|
loaf
|
||||||
|
@ -719,7 +692,6 @@ DEPENDENCIES
|
||||||
recursero-jekyll-theme
|
recursero-jekyll-theme
|
||||||
redis
|
redis
|
||||||
redis-rails
|
redis-rails
|
||||||
rollups!
|
|
||||||
rubocop-rails
|
rubocop-rails
|
||||||
rubyzip
|
rubyzip
|
||||||
rugged
|
rugged
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -48,7 +48,7 @@ help: always ## Ayuda
|
||||||
@echo -e "\nArgumentos:\n"
|
@echo -e "\nArgumentos:\n"
|
||||||
@grep -E "^[a-z\-]+ \?=.*##" Makefile | sed -re "s/(.*) \?=.*##(.*)/\1;\2/" | column -s ";" -t | sed -re "s/^([^ ]+) /\x1B[38;5;197m\1\x1B[0m/"
|
@grep -E "^[a-z\-]+ \?=.*##" Makefile | sed -re "s/(.*) \?=.*##(.*)/\1;\2/" | column -s ";" -t | sed -re "s/^([^ ]+) /\x1B[38;5;197m\1\x1B[0m/"
|
||||||
|
|
||||||
assets: public/packs/manifest.json.br ## Compilar los assets
|
assets: node_modules public/packs/manifest.json.br ## Compilar los assets
|
||||||
|
|
||||||
test: always ## Ejecutar los tests
|
test: always ## Ejecutar los tests
|
||||||
$(MAKE) rake args="test RAILS_ENV=test $(args)"
|
$(MAKE) rake args="test RAILS_ENV=test $(args)"
|
||||||
|
@ -108,6 +108,7 @@ ota-js: assets ## Actualizar Javascript en el nodo delegado
|
||||||
|
|
||||||
ota: ## Actualizar Rails en el nodo delegado
|
ota: ## Actualizar Rails en el nodo delegado
|
||||||
umask 022; git format-patch $(commit)
|
umask 022; git format-patch $(commit)
|
||||||
|
scp ./0*.patch $(delegate):/tmp/
|
||||||
ssh $(delegate) mkdir -p /tmp/patches-$(commit)/
|
ssh $(delegate) mkdir -p /tmp/patches-$(commit)/
|
||||||
scp ./0*.patch $(delegate):/tmp/patches-$(commit)/
|
scp ./0*.patch $(delegate):/tmp/patches-$(commit)/
|
||||||
scp ./ota.sh $(delegate):/tmp/
|
scp ./ota.sh $(delegate):/tmp/
|
||||||
|
|
|
@ -2,13 +2,6 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
*, *::before, *::after { box-sizing: inherit; }
|
*, *::before, *::after { box-sizing: inherit; }
|
||||||
|
|
||||||
// Arreglo temporal para que las cosas sean legibles en modo oscuro
|
|
||||||
--foreground: black;
|
|
||||||
--background: white;
|
|
||||||
--color: #f206f9;
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--foreground);
|
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6, p, li {
|
h1, h2, h3, h4, h5, h6, p, li {
|
||||||
min-height: 1.5rem;
|
min-height: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,21 +22,21 @@ class PostsController < ApplicationController
|
||||||
|
|
||||||
# XXX: Cada vez que cambiamos un Post tocamos el sitio con lo que es
|
# XXX: Cada vez que cambiamos un Post tocamos el sitio con lo que es
|
||||||
# más simple saber si hubo cambios.
|
# más simple saber si hubo cambios.
|
||||||
return unless stale?([current_usuarie, site, filter_params])
|
if stale?([current_usuarie, site, filter_params])
|
||||||
|
# Todos los artículos de este sitio para el idioma actual
|
||||||
|
@posts = site.indexed_posts.where(locale: locale)
|
||||||
|
# De este tipo
|
||||||
|
@posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout]
|
||||||
|
# Que estén dentro de la categoría
|
||||||
|
@posts = @posts.in_category(filter_params[:category]) if filter_params[:category]
|
||||||
|
# Aplicar los parámetros de búsqueda
|
||||||
|
@posts = @posts.search(locale, filter_params[:q]) if filter_params[:q].present?
|
||||||
|
# A los que este usuarie tiene acceso
|
||||||
|
@posts = PostPolicy::Scope.new(current_usuarie, @posts).resolve
|
||||||
|
|
||||||
# Todos los artículos de este sitio para el idioma actual
|
# Filtrar los posts que les invitades no pueden ver
|
||||||
@posts = site.indexed_posts.where(locale: locale)
|
@usuarie = site.usuarie? current_usuarie
|
||||||
# De este tipo
|
end
|
||||||
@posts = @posts.where(layout: filter_params[:layout]) if filter_params[:layout]
|
|
||||||
# Que estén dentro de la categoría
|
|
||||||
@posts = @posts.in_category(filter_params[:category]) if filter_params[:category]
|
|
||||||
# Aplicar los parámetros de búsqueda
|
|
||||||
@posts = @posts.search(locale, filter_params[:q]) if filter_params[:q].present?
|
|
||||||
# A los que este usuarie tiene acceso
|
|
||||||
@posts = PostPolicy::Scope.new(current_usuarie, @posts).resolve
|
|
||||||
|
|
||||||
# Filtrar los posts que les invitades no pueden ver
|
|
||||||
@usuarie = site.usuarie? current_usuarie
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
@ -54,7 +54,7 @@ class PostsController < ApplicationController
|
||||||
|
|
||||||
def new
|
def new
|
||||||
authorize Post
|
authorize Post
|
||||||
@post = site.posts(lang: locale).build(layout: params[:layout])
|
@post = site.posts.build(lang: locale, layout: params[:layout])
|
||||||
|
|
||||||
breadcrumb I18n.t('loaf.breadcrumbs.posts.new', layout: @post.layout.humanized_name.downcase), ''
|
breadcrumb I18n.t('loaf.breadcrumbs.posts.new', layout: @post.layout.humanized_name.downcase), ''
|
||||||
end
|
end
|
||||||
|
@ -154,9 +154,7 @@ class PostsController < ApplicationController
|
||||||
#
|
#
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
def filter_params
|
def filter_params
|
||||||
@filter_params ||= params.permit(:q, :category, :layout).to_hash.select do |_, v|
|
@filter_params ||= params.permit(:q, :category, :layout).to_h.select { |_, v| v.present? }
|
||||||
v.present?
|
|
||||||
end.transform_keys(&:to_sym)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def site
|
def site
|
||||||
|
|
|
@ -3,166 +3,16 @@
|
||||||
# Estadísticas del sitio
|
# Estadísticas del sitio
|
||||||
class StatsController < ApplicationController
|
class StatsController < ApplicationController
|
||||||
include Pundit
|
include Pundit
|
||||||
include ActionView::Helpers::DateHelper
|
|
||||||
|
|
||||||
before_action :authenticate_usuarie!
|
before_action :authenticate_usuarie!
|
||||||
before_action :authorize_stats
|
|
||||||
|
|
||||||
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
|
def index
|
||||||
@chart_params = { interval: interval }
|
|
||||||
hostnames
|
|
||||||
last_stat
|
|
||||||
chart_options
|
|
||||||
normalized_urls
|
|
||||||
end
|
|
||||||
|
|
||||||
# Genera un gráfico de visitas por dominio asociado a este sitio
|
|
||||||
def host
|
|
||||||
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
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
render json: stats
|
|
||||||
end
|
|
||||||
|
|
||||||
def resources
|
|
||||||
return unless 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
|
|
||||||
|
|
||||||
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|
|
|
||||||
serie[:name] = serie[:dimensions].slice('host', 'uri').values.join.sub('/index.html', '/')
|
|
||||||
serie[:data].transform_values! do |value|
|
|
||||||
value * nodes
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
render json: stats
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def last_stat
|
|
||||||
@last_stat ||= Stat.last
|
|
||||||
end
|
|
||||||
|
|
||||||
def authorize_stats
|
|
||||||
@site = find_site
|
@site = find_site
|
||||||
authorize SiteStat.new(@site)
|
authorize SiteStat.new(@site)
|
||||||
end
|
|
||||||
|
|
||||||
# TODO: Eliminar cuando mergeemos referer-origin
|
# Solo queremos el promedio de tiempo de compilación, no de
|
||||||
def hostnames
|
# instalación de dependencias.
|
||||||
@hostnames ||= [@site.hostname, @site.alternative_hostnames].flatten
|
stats = @site.build_stats.jekyll
|
||||||
end
|
@build_avg = stats.average(:seconds).to_f.round(2)
|
||||||
|
@build_max = stats.maximum(:seconds).to_f.round(2)
|
||||||
# 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"
|
|
||||||
end&.uniq || [@site.url, @site.urls].flatten.uniq
|
|
||||||
end
|
|
||||||
|
|
||||||
def normalized_paths
|
|
||||||
@normalized_paths ||= normalized_urls.map do |u|
|
|
||||||
"/#{u.split('/', 4).last}"
|
|
||||||
end.map do |u|
|
|
||||||
URI.decode_www_form_component u
|
|
||||||
end
|
|
||||||
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 || Time.now) + 1.try(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: %(<div id="%{id}" class="d-flex align-items-center justify-content-center" style="height: %{height}; width: %{width};">%{loading}</div>)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Obtiene y valida los intervalos
|
|
||||||
#
|
|
||||||
# @return [Symbol]
|
|
||||||
def interval
|
|
||||||
@interval ||= begin
|
|
||||||
i = params[:interval]&.to_sym
|
|
||||||
Stat::INTERVALS.include?(i) ? i : :day
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def resource
|
|
||||||
@resource ||= begin
|
|
||||||
r = params[:resource].to_sym
|
|
||||||
Stat::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
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,7 +18,6 @@ import 'etc'
|
||||||
import Rails from '@rails/ujs'
|
import Rails from '@rails/ujs'
|
||||||
import Turbolinks from 'turbolinks'
|
import Turbolinks from 'turbolinks'
|
||||||
import * as ActiveStorage from '@rails/activestorage'
|
import * as ActiveStorage from '@rails/activestorage'
|
||||||
import 'chartkick/chart.js'
|
|
||||||
|
|
||||||
Rails.start()
|
Rails.start()
|
||||||
Turbolinks.start()
|
Turbolinks.start()
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Una tarea que se corre periódicamente
|
|
||||||
class PeriodicJob < ApplicationJob
|
|
||||||
class RunAgainException < StandardError; end
|
|
||||||
|
|
||||||
STARTING_INTERVAL = Stat::INTERVALS.first
|
|
||||||
|
|
||||||
# Tener el sitio a mano
|
|
||||||
attr_reader :site
|
|
||||||
|
|
||||||
# Descartar y notificar si pasó algo más.
|
|
||||||
#
|
|
||||||
# XXX: En realidad deberíamos seguir reintentando?
|
|
||||||
discard_on(StandardError) do |_, error|
|
|
||||||
ExceptionNotifier.notify_exception(error)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Correr indefinidamente una vez por hora.
|
|
||||||
#
|
|
||||||
# XXX: El orden importa, si el descarte viene después, nunca se va a
|
|
||||||
# reintentar.
|
|
||||||
retry_on(PeriodicJob::RunAgainException, wait: 1.try(STARTING_INTERVAL), attempts: Float::INFINITY, jitter: 0)
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Las clases que implementen esta tienen que usar este método al
|
|
||||||
# terminar.
|
|
||||||
def run_again!
|
|
||||||
raise PeriodicJob::RunAgainException, 'Reintentando'
|
|
||||||
end
|
|
||||||
|
|
||||||
# El intervalo de inicio
|
|
||||||
#
|
|
||||||
# @return [Symbol]
|
|
||||||
def starting_interval
|
|
||||||
STARTING_INTERVAL
|
|
||||||
end
|
|
||||||
|
|
||||||
# La última recolección de estadísticas o empezar desde el principio
|
|
||||||
# de los tiempos.
|
|
||||||
#
|
|
||||||
# @return [Stat]
|
|
||||||
def last_stat
|
|
||||||
@last_stat ||= site.stats.where(name: stat_name).last ||
|
|
||||||
site.stats.build(created_at: Time.new(1970, 1, 1))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Devuelve el comienzo del intervalo
|
|
||||||
#
|
|
||||||
# @return [Time]
|
|
||||||
def beginning_of_interval
|
|
||||||
@beginning_of_interval ||= last_stat.created_at.try(:"beginning_of_#{starting_interval}")
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,67 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Genera resúmenes de información para poder mostrar estadísticas y se
|
|
||||||
# corre regularmente a sí misma.
|
|
||||||
class StatCollectionJob < ApplicationJob
|
|
||||||
STAT_NAME = 'stat_collection_job'
|
|
||||||
|
|
||||||
def perform(site_id:, once: true)
|
|
||||||
@site = Site.find site_id
|
|
||||||
|
|
||||||
scope.rollup('builds', **options)
|
|
||||||
|
|
||||||
scope.rollup('space_used', **options) do |rollup|
|
|
||||||
rollup.average(:bytes)
|
|
||||||
end
|
|
||||||
|
|
||||||
scope.rollup('build_time', **options) do |rollup|
|
|
||||||
rollup.average(:seconds)
|
|
||||||
end
|
|
||||||
|
|
||||||
# XXX: Es correcto promediar promedios?
|
|
||||||
Stat::INTERVALS.reduce do |previous, current|
|
|
||||||
rollup(name: 'builds', interval_previous: previous, interval: current)
|
|
||||||
rollup(name: 'space_used', interval_previous: previous, interval: current, operation: :average)
|
|
||||||
rollup(name: 'build_time', interval_previous: previous, interval: current, operation: :average)
|
|
||||||
|
|
||||||
current
|
|
||||||
end
|
|
||||||
|
|
||||||
# Registrar que se hicieron todas las recolecciones
|
|
||||||
site.stats.create! name: STAT_NAME
|
|
||||||
|
|
||||||
run_again! unless once
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Genera un rollup recursivo en base al período anterior y aplica una
|
|
||||||
# operación.
|
|
||||||
#
|
|
||||||
# @return [NilClass]
|
|
||||||
def rollup(name:, interval_previous:, interval:, operation: :sum)
|
|
||||||
Rollup.where(name: name, interval: interval_previous)
|
|
||||||
.where_dimensions(site_id: site.id)
|
|
||||||
.group("dimensions->'site_id'")
|
|
||||||
.rollup(name, interval: interval, update: true) do |rollup|
|
|
||||||
rollup.try(:operation, :value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Los registros a procesar
|
|
||||||
#
|
|
||||||
# @return [ActiveRecord::Relation]
|
|
||||||
def scope
|
|
||||||
@scope ||= site.build_stats
|
|
||||||
.jekyll
|
|
||||||
.where('created_at => ?', beginning_of_interval)
|
|
||||||
.group(:site_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Las opciones por defecto
|
|
||||||
#
|
|
||||||
# @return [Hash]
|
|
||||||
def options
|
|
||||||
@options ||= { interval: starting_interval, update: true }
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,106 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Procesar una lista de URIs para una lista de dominios. Esto nos
|
|
||||||
# permite procesar estadísticas a demanda.
|
|
||||||
#
|
|
||||||
# Hay varias cosas acá que van a convertirse en métodos propios, como la
|
|
||||||
# detección de URIs de un sitio (aunque la versión actual detecta todas
|
|
||||||
# las páginas y no solo las de posts como tenemos planeado, hay que
|
|
||||||
# resolver eso).
|
|
||||||
#
|
|
||||||
# Los hostnames de un sitio van a poder obtenerse a partir de
|
|
||||||
# Site#hostnames con la garantía de que son únicos.
|
|
||||||
class UriCollectionJob < PeriodicJob
|
|
||||||
# Ignoramos imágenes porque suelen ser demasiadas y no aportan a las
|
|
||||||
# estadísticas.
|
|
||||||
IMAGES = %w[.png .jpg .jpeg .gif .webp].freeze
|
|
||||||
STAT_NAME = 'uri_collection_job'
|
|
||||||
|
|
||||||
def perform(site_id:, once: true)
|
|
||||||
@site = Site.find site_id
|
|
||||||
|
|
||||||
hostnames.each do |hostname|
|
|
||||||
uris.each do |uri|
|
|
||||||
return if File.exist? Rails.root.join('tmp', 'uri_collection_job_stop')
|
|
||||||
|
|
||||||
AccessLog.where(host: hostname, uri: uri)
|
|
||||||
.where('created_at >= ?', beginning_of_interval)
|
|
||||||
.completed_requests
|
|
||||||
.non_robots
|
|
||||||
.group(:host, :uri)
|
|
||||||
.rollup('host|uri', interval: starting_interval, update: true)
|
|
||||||
|
|
||||||
# Reducir las estadísticas calculadas aplicando un rollup sobre el
|
|
||||||
# intervalo más amplio.
|
|
||||||
Stat::INTERVALS.reduce do |previous, current|
|
|
||||||
Rollup.where(name: 'host|uri', interval: previous)
|
|
||||||
.where_dimensions(host: hostname, uri: uri)
|
|
||||||
.group("dimensions->'host'", "dimensions->'uri'")
|
|
||||||
.rollup('host|uri', interval: current, update: true) do |rollup|
|
|
||||||
rollup.sum(:value)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Devolver el intervalo actual
|
|
||||||
current
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Recordar la última vez que se corrió la tarea
|
|
||||||
site.stats.create! name: STAT_NAME
|
|
||||||
|
|
||||||
run_again! unless once
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def stat_name
|
|
||||||
STAT_NAME
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [String]
|
|
||||||
#
|
|
||||||
# TODO: Cambiar al mergear origin-referer
|
|
||||||
def destination
|
|
||||||
@destination ||= site.deploys.find_by(type: 'DeployLocal').destination
|
|
||||||
end
|
|
||||||
|
|
||||||
# TODO: Cambiar al mergear origin-referer
|
|
||||||
#
|
|
||||||
# @return [Array]
|
|
||||||
def hostnames
|
|
||||||
@hostnames ||= site.deploys.map do |deploy|
|
|
||||||
case deploy
|
|
||||||
when DeployLocal
|
|
||||||
site.hostname
|
|
||||||
when DeployWww
|
|
||||||
deploy.fqdn
|
|
||||||
when DeployAlternativeDomain
|
|
||||||
deploy.hostname.dup.tap do |h|
|
|
||||||
h.replace(h.end_with?('.') ? h[0..-2] : "#{h}.#{Site.domain}")
|
|
||||||
end
|
|
||||||
when DeployHiddenService
|
|
||||||
deploy.onion
|
|
||||||
end
|
|
||||||
end.compact
|
|
||||||
end
|
|
||||||
|
|
||||||
# Recolecta todas las URIs menos imágenes
|
|
||||||
#
|
|
||||||
# @return [Array]
|
|
||||||
def uris
|
|
||||||
@uris ||= Dir.chdir destination do
|
|
||||||
(Dir.glob('**/*.html') + Dir.glob('public/**/*').reject do |p|
|
|
||||||
File.directory? p
|
|
||||||
end.reject do |p|
|
|
||||||
p = p.downcase
|
|
||||||
|
|
||||||
IMAGES.any? do |i|
|
|
||||||
p.end_with? i
|
|
||||||
end
|
|
||||||
end).map do |uri|
|
|
||||||
"/#{uri}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,12 +1,4 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AccessLog < ApplicationRecord
|
class AccessLog < ApplicationRecord
|
||||||
# Las peticiones completas son las que terminaron bien y se
|
|
||||||
# respondieron con 200 OK o 304 Not Modified
|
|
||||||
#
|
|
||||||
# @see {https://en.wikipedia.org/wiki/List_of_HTTP_status_codes}
|
|
||||||
scope :completed_requests, -> { where(request_method: 'GET', request_completion: 'OK', status: [200, 304]) }
|
|
||||||
scope :non_robots, -> { where(crawler: false) }
|
|
||||||
scope :robots, -> { where(crawler: true) }
|
|
||||||
scope :pages, -> { where(sent_http_content_type: ['text/html', 'text/html; charset=utf-8', 'text/html; charset=UTF-8']) }
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,19 +25,10 @@ class MetadataBoolean < MetadataTemplate
|
||||||
# * false
|
# * false
|
||||||
# * true
|
# * true
|
||||||
def value
|
def value
|
||||||
case self[:value]
|
return document.data.fetch(name.to_s, default_value) if self[:value].nil?
|
||||||
when NilClass
|
return self[:value] unless self[:value].is_a? String
|
||||||
document.data.fetch(name.to_s, default_value)
|
|
||||||
when String
|
|
||||||
true_values.include? self[:value]
|
|
||||||
else
|
|
||||||
self[:value]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Siempre guardar el valor de este campo a menos que sea nulo
|
self[:value] = true_values.include? self[:value]
|
||||||
def empty?
|
|
||||||
value.nil?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -56,7 +56,7 @@ class MetadataContent < MetadataTemplate
|
||||||
uri = URI element['src']
|
uri = URI element['src']
|
||||||
|
|
||||||
# No permitimos recursos externos
|
# No permitimos recursos externos
|
||||||
element.remove unless uri.scheme == 'https' && uri.hostname.end_with?(Site.domain)
|
element.remove unless uri.hostname.end_with? Site.domain
|
||||||
rescue URI::Error
|
rescue URI::Error
|
||||||
element.remove
|
element.remove
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Un campo numérico de punto flotante
|
|
||||||
class MetadataFloat < MetadataTemplate
|
|
||||||
# Nada
|
|
||||||
def default_value
|
|
||||||
super || nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def save
|
|
||||||
return true unless changed?
|
|
||||||
|
|
||||||
self[:value] = value.to_f
|
|
||||||
self[:value] = encrypt(value) if private?
|
|
||||||
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
# Indicarle al navegador que acepte números decimales
|
|
||||||
#
|
|
||||||
# @return [Float]
|
|
||||||
def step
|
|
||||||
0.05
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def decrypt(value)
|
|
||||||
super(value).to_f
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -2,12 +2,6 @@
|
||||||
|
|
||||||
# Este metadato permite generar rutas manuales.
|
# Este metadato permite generar rutas manuales.
|
||||||
class MetadataPermalink < MetadataString
|
class MetadataPermalink < MetadataString
|
||||||
# El valor por defecto una vez creado es la URL que le asigne Jekyll,
|
|
||||||
# de forma que nunca cambia aunque se cambie el título.
|
|
||||||
def default_value
|
|
||||||
document.url unless post.new?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Los permalinks nunca pueden ser privados
|
# Los permalinks nunca pueden ser privados
|
||||||
def private?
|
def private?
|
||||||
false
|
false
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Un campo de texto seleccionado de una lista de valores posibles
|
|
||||||
class MetadataPredefinedValue < MetadataString
|
|
||||||
# Obtiene todos los valores desde el layout, en un formato compatible
|
|
||||||
# con options_for_select.
|
|
||||||
#
|
|
||||||
# @return [Hash]
|
|
||||||
def values
|
|
||||||
@values ||= layout.dig(:metadata, name, 'values', I18n.locale.to_s)&.invert || {}
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Solo permite almacenar los valores predefinidos.
|
|
||||||
#
|
|
||||||
# @return [String]
|
|
||||||
def sanitize(string)
|
|
||||||
v = super string
|
|
||||||
return '' unless values.values.include? v
|
|
||||||
|
|
||||||
v
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -26,7 +26,7 @@ class Site < ApplicationRecord
|
||||||
validates :design_id, presence: true
|
validates :design_id, presence: true
|
||||||
validates_inclusion_of :status, in: %w[waiting enqueued building]
|
validates_inclusion_of :status, in: %w[waiting enqueued building]
|
||||||
validates_presence_of :title
|
validates_presence_of :title
|
||||||
validates :description, length: { in: 10..160 }
|
validates :description, length: { in: 50..160 }
|
||||||
validate :deploy_local_presence
|
validate :deploy_local_presence
|
||||||
validate :compatible_layouts, on: :update
|
validate :compatible_layouts, on: :update
|
||||||
|
|
||||||
|
@ -37,7 +37,6 @@ class Site < ApplicationRecord
|
||||||
belongs_to :design
|
belongs_to :design
|
||||||
belongs_to :licencia
|
belongs_to :licencia
|
||||||
|
|
||||||
has_many :stats
|
|
||||||
has_many :log_entries, dependent: :destroy
|
has_many :log_entries, dependent: :destroy
|
||||||
has_many :deploys, dependent: :destroy
|
has_many :deploys, dependent: :destroy
|
||||||
has_many :build_stats, through: :deploys
|
has_many :build_stats, through: :deploys
|
||||||
|
@ -66,6 +65,9 @@ class Site < ApplicationRecord
|
||||||
|
|
||||||
accepts_nested_attributes_for :deploys, allow_destroy: true
|
accepts_nested_attributes_for :deploys, allow_destroy: true
|
||||||
|
|
||||||
|
# El sitio en Jekyll
|
||||||
|
attr_reader :jekyll
|
||||||
|
|
||||||
# XXX: Es importante incluir luego de los callbacks de :load_jekyll
|
# XXX: Es importante incluir luego de los callbacks de :load_jekyll
|
||||||
include Site::Index
|
include Site::Index
|
||||||
|
|
||||||
|
@ -178,28 +180,29 @@ class Site < ApplicationRecord
|
||||||
|
|
||||||
# Trae los datos del directorio _data dentro del sitio
|
# Trae los datos del directorio _data dentro del sitio
|
||||||
def data
|
def data
|
||||||
unless jekyll.data.present?
|
unless @jekyll.data.present?
|
||||||
run_in_path do
|
@jekyll.reader.read_data
|
||||||
jekyll.reader.read_data
|
|
||||||
jekyll.data['layouts'] ||= {}
|
# Define los valores por defecto según la llave buscada
|
||||||
|
@jekyll.data.default_proc = proc do |data, key|
|
||||||
|
data[key] = case key
|
||||||
|
when 'layout' then {}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
jekyll.data
|
@jekyll.data
|
||||||
end
|
end
|
||||||
|
|
||||||
# Traer las colecciones. Todos los artículos van a estar dentro de
|
# Traer las colecciones. Todos los artículos van a estar dentro de
|
||||||
# colecciones.
|
# colecciones.
|
||||||
def collections
|
def collections
|
||||||
unless @read
|
unless @read
|
||||||
run_in_path do
|
@jekyll.reader.read_collections
|
||||||
jekyll.reader.read_collections
|
|
||||||
end
|
|
||||||
|
|
||||||
@read = true
|
@read = true
|
||||||
end
|
end
|
||||||
|
|
||||||
jekyll.collections
|
@jekyll.collections
|
||||||
end
|
end
|
||||||
|
|
||||||
# Traer la configuración de forma modificable
|
# Traer la configuración de forma modificable
|
||||||
|
@ -287,9 +290,7 @@ class Site < ApplicationRecord
|
||||||
#
|
#
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
def theme_layouts
|
def theme_layouts
|
||||||
run_in_path do
|
@jekyll.reader.read_layouts
|
||||||
jekyll.reader.read_layouts
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Trae todos los valores disponibles para un campo
|
# Trae todos los valores disponibles para un campo
|
||||||
|
@ -331,12 +332,6 @@ class Site < ApplicationRecord
|
||||||
status == 'building'
|
status == 'building'
|
||||||
end
|
end
|
||||||
|
|
||||||
def jekyll
|
|
||||||
run_in_path do
|
|
||||||
@jekyll ||= Jekyll::Site.new(configuration)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Cargar el sitio Jekyll
|
# Cargar el sitio Jekyll
|
||||||
#
|
#
|
||||||
# TODO: En lugar de leer todo junto de una vez, extraer la carga de
|
# TODO: En lugar de leer todo junto de una vez, extraer la carga de
|
||||||
|
@ -350,7 +345,10 @@ class Site < ApplicationRecord
|
||||||
|
|
||||||
def reload_jekyll!
|
def reload_jekyll!
|
||||||
reset
|
reset
|
||||||
jekyll
|
|
||||||
|
Dir.chdir(path) do
|
||||||
|
@jekyll = Jekyll::Site.new(configuration)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def reload
|
def reload
|
||||||
|
@ -528,8 +526,4 @@ class Site < ApplicationRecord
|
||||||
errors.add(:design_id,
|
errors.add(:design_id,
|
||||||
I18n.t('activerecord.errors.models.site.attributes.design_id.layout_incompatible.error'))
|
I18n.t('activerecord.errors.models.site.attributes.design_id.layout_incompatible.error'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_in_path(&block)
|
|
||||||
Dir.chdir path, &block
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Registran cuándo fue la última recolección de datos.
|
|
||||||
class Stat < ApplicationRecord
|
|
||||||
# XXX: Los intervalos van en orden de mayor especificidad a menor
|
|
||||||
INTERVALS = %i[day month year].freeze
|
|
||||||
RESOURCES = %i[builds space_used build_time].freeze
|
|
||||||
|
|
||||||
belongs_to :site
|
|
||||||
end
|
|
|
@ -12,16 +12,4 @@ class SiteStatPolicy
|
||||||
def index?
|
def index?
|
||||||
site_stat.site.usuarie? usuarie
|
site_stat.site.usuarie? usuarie
|
||||||
end
|
end
|
||||||
|
|
||||||
def host?
|
|
||||||
index?
|
|
||||||
end
|
|
||||||
|
|
||||||
def resources?
|
|
||||||
index?
|
|
||||||
end
|
|
||||||
|
|
||||||
def uris?
|
|
||||||
index?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -122,8 +122,6 @@ SiteService = Struct.new(:site, :usuarie, :params, keyword_init: true) do
|
||||||
# la búsqueda.
|
# la búsqueda.
|
||||||
def change_licencias
|
def change_licencias
|
||||||
site.locales.each do |locale|
|
site.locales.each do |locale|
|
||||||
next unless I18n.available_locales.include? locale
|
|
||||||
|
|
||||||
Mobility.with_locale(locale) do
|
Mobility.with_locale(locale) do
|
||||||
permalink = "#{I18n.t('activerecord.models.licencia').downcase}/"
|
permalink = "#{I18n.t('activerecord.models.licencia').downcase}/"
|
||||||
post = site.posts(lang: locale).find_by(permalink: permalink)
|
post = site.posts(lang: locale).find_by(permalink: permalink)
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
%tr{ id: attribute }
|
|
||||||
%th= post_label_t(attribute, post: post)
|
|
||||||
%td{ dir: dir, lang: locale }= metadata.value
|
|
|
@ -1,3 +0,0 @@
|
||||||
%tr{ id: attribute }
|
|
||||||
%th= post_label_t(attribute, post: post)
|
|
||||||
%td{ dir: dir, lang: locale }= metadata.value
|
|
|
@ -1,6 +0,0 @@
|
||||||
.form-group
|
|
||||||
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
|
||||||
= number_field base, attribute, value: metadata.value, step: metadata.step,
|
|
||||||
**field_options(attribute, metadata)
|
|
||||||
= render 'posts/attribute_feedback',
|
|
||||||
post: post, attribute: attribute, metadata: metadata
|
|
|
@ -1,7 +0,0 @@
|
||||||
.form-group
|
|
||||||
= label_tag "#{base}_#{attribute}", post_label_t(attribute, post: post)
|
|
||||||
= select_tag(plain_field_name_for(base, attribute),
|
|
||||||
options_for_select(metadata.values, metadata.value),
|
|
||||||
**field_options(attribute, metadata), include_blank: t('.empty'))
|
|
||||||
= render 'posts/attribute_feedback',
|
|
||||||
post: post, attribute: attribute, metadata: metadata
|
|
|
@ -40,7 +40,7 @@
|
||||||
%section.col
|
%section.col
|
||||||
= render 'layouts/flash'
|
= render 'layouts/flash'
|
||||||
.d-flex.justify-content-between.align-items-center.pl-2-plus.pr-2-plus.mb-2
|
.d-flex.justify-content-between.align-items-center.pl-2-plus.pr-2-plus.mb-2
|
||||||
%form{ action: site_posts_path }
|
%form
|
||||||
- @filter_params.each do |param, value|
|
- @filter_params.each do |param, value|
|
||||||
- next if param == 'q'
|
- next if param == 'q'
|
||||||
%input{ type: 'hidden', name: param, value: value }
|
%input{ type: 'hidden', name: param, value: value }
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
- if @site.locales.size > 1
|
- if @site.locales.size > 1
|
||||||
%nav#locales
|
%nav#locales
|
||||||
- @site.locales.each do |locale|
|
- @site.locales.each do |locale|
|
||||||
= link_to @site.data.dig(locale.to_s, 'locale') || locale, site_posts_path(@site, **@filter_params.merge(locale: locale)),
|
= link_to t("locales.#{locale}.name"), site_posts_path(@site, **@filter_params.merge(locale: locale)),
|
||||||
class: "mr-2 mt-2 mb-2 #{locale == @locale ? 'active font-weight-bold' : ''}"
|
class: "mr-2 mt-2 mb-2 #{locale == @locale ? 'active font-weight-bold' : ''}"
|
||||||
.pl-2-plus
|
.pl-2-plus
|
||||||
- @filter_params.each do |param, value|
|
- @filter_params.each do |param, value|
|
||||||
|
@ -67,24 +67,19 @@
|
||||||
%h2= t('posts.empty')
|
%h2= t('posts.empty')
|
||||||
- else
|
- else
|
||||||
= form_tag site_posts_reorder_path, method: :post do
|
= form_tag site_posts_reorder_path, method: :post do
|
||||||
%input{ type: 'hidden', name: 'post[lang]', value: @locale }
|
|
||||||
%table.table{ data: { controller: 'reorder' } }
|
%table.table{ data: { controller: 'reorder' } }
|
||||||
%caption.sr-only= t('posts.caption')
|
%caption.sr-only= t('posts.caption')
|
||||||
%thead
|
%thead
|
||||||
%tr
|
%tr
|
||||||
%th.border-0.background-white.position-sticky{ style: 'top: 0; z-index: 2', colspan: '4' }
|
%th.border-0.background-white.position-sticky{ style: 'top: 0; z-index: 2', colspan: '4' }
|
||||||
.d-flex.flex-row.justify-content-between
|
= submit_tag t('posts.reorder.submit'), class: 'btn'
|
||||||
%div
|
%button.btn{ data: { action: 'reorder#unselect' } }
|
||||||
= submit_tag t('posts.reorder.submit'), class: 'btn'
|
= t('posts.reorder.unselect')
|
||||||
%button.btn{ data: { action: 'reorder#unselect' } }
|
%span.badge{ data: { target: 'reorder.counter' } } 0
|
||||||
= t('posts.reorder.unselect')
|
%button.btn{ data: { action: 'reorder#up' } }= t('posts.reorder.up')
|
||||||
%span.badge{ data: { target: 'reorder.counter' } } 0
|
%button.btn{ data: { action: 'reorder#down' } }= t('posts.reorder.down')
|
||||||
%button.btn{ data: { action: 'reorder#up' } }= t('posts.reorder.up')
|
%button.btn{ data: { action: 'reorder#top' } }= t('posts.reorder.top')
|
||||||
%button.btn{ data: { action: 'reorder#down' } }= t('posts.reorder.down')
|
%button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom')
|
||||||
%button.btn{ data: { action: 'reorder#top' } }= t('posts.reorder.top')
|
|
||||||
%button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom')
|
|
||||||
|
|
||||||
%div
|
|
||||||
%tbody
|
%tbody
|
||||||
- dir = t("locales.#{@locale}.dir")
|
- dir = t("locales.#{@locale}.dir")
|
||||||
- size = @posts.size
|
- size = @posts.size
|
||||||
|
@ -109,19 +104,19 @@
|
||||||
%span{ lang: post.locale, dir: dir }= post.title
|
%span{ lang: post.locale, dir: dir }= post.title
|
||||||
- if post.front_matter['draft'].present?
|
- if post.front_matter['draft'].present?
|
||||||
%span.badge.badge-primary= I18n.t('posts.attributes.draft.label')
|
%span.badge.badge-primary= I18n.t('posts.attributes.draft.label')
|
||||||
%br
|
- if post.front_matter['categories'].present?
|
||||||
%small
|
%br
|
||||||
= link_to @site.layouts[post.layout].humanized_name, site_posts_path(@site, **@filter_params.merge(layout: post.layout))
|
%small
|
||||||
- post.front_matter['categories']&.each do |category|
|
- post.front_matter['categories'].each do |category|
|
||||||
= link_to site_posts_path(@site, **@filter_params.merge(category: category)) do
|
= link_to site_posts_path(@site, **@filter_params.merge(category: category)) do
|
||||||
%span{ lang: post.locale, dir: dir }= category
|
%span{ lang: post.locale, dir: dir }= category
|
||||||
= '/' unless post.front_matter['categories'].last == category
|
= '/' unless post.front_matter['categories'].last == category
|
||||||
|
|
||||||
%td.text-nowrap
|
%td
|
||||||
= post.created_at.strftime('%F')
|
= post.created_at.strftime('%F')
|
||||||
%br/
|
%br/
|
||||||
= post.order
|
= post.order
|
||||||
%td.text-nowrap
|
%td
|
||||||
- if @usuarie || policy(post).edit?
|
- if @usuarie || policy(post).edit?
|
||||||
= link_to t('posts.edit'), edit_site_post_path(@site, post.path), class: 'btn btn-block'
|
= link_to t('posts.edit'), edit_site_post_path(@site, post.path), class: 'btn btn-block'
|
||||||
- if @usuarie || policy(post).destroy?
|
- if @usuarie || policy(post).destroy?
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
%h2= f.label :description
|
%h2= f.label :description
|
||||||
%p.lead= t('.help.description')
|
%p.lead= t('.help.description')
|
||||||
= f.text_area :description, class: form_control(site, :description),
|
= f.text_area :description, class: form_control(site, :description),
|
||||||
maxlength: 160, minlength: 10, required: true
|
maxlength: 160, minlength: 50, required: true
|
||||||
- if invalid? site, :description
|
- if invalid? site, :description
|
||||||
.invalid-feedback= site.errors.messages[:description].join(', ')
|
.invalid-feedback= site.errors.messages[:description].join(', ')
|
||||||
%hr/
|
%hr/
|
||||||
|
|
|
@ -1,43 +1,17 @@
|
||||||
|
= render 'layouts/breadcrumb',
|
||||||
|
crumbs: [link_to(t('sites.index.title'), sites_path),
|
||||||
|
link_to(@site.name, site_path(@site)), t('.title')]
|
||||||
|
|
||||||
.row
|
.row
|
||||||
.col
|
.col
|
||||||
%h1= t('.title')
|
%h1= t('.title')
|
||||||
%p.lead= t('.help')
|
%p.lead= t('.help')
|
||||||
- if @last_stat
|
|
||||||
%p
|
|
||||||
%small
|
|
||||||
= t('.last_update')
|
|
||||||
%time{ datetime: @last_stat.created_at }
|
|
||||||
#{time_ago_in_words @last_stat.created_at}.
|
|
||||||
|
|
||||||
.mb-5
|
%table.table.table-condensed
|
||||||
- Stat::INTERVALS.each do |interval|
|
%tbody
|
||||||
= link_to t(".#{interval}"), site_stats_path(interval: interval, urls: params[:urls]), class: "btn #{'btn-primary active' if @interval == interval}"
|
%tr
|
||||||
|
%td= t('.build.average')
|
||||||
.mb-5
|
%td= distance_of_time_in_words_if_more_than_a_minute @build_avg
|
||||||
%h2= t('.host.title', count: @hostnames.size)
|
%tr
|
||||||
%p.lead= t('.host.description')
|
%td= t('.build.maximum')
|
||||||
= line_chart site_stats_host_path(@chart_params), **@chart_options
|
%td= distance_of_time_in_words_if_more_than_a_minute @build_max
|
||||||
|
|
||||||
.mb-5
|
|
||||||
%h2= t('.urls.title')
|
|
||||||
%p.lead= t('.urls.description')
|
|
||||||
%form
|
|
||||||
%input{ type: 'hidden', name: 'interval', value: @interval }
|
|
||||||
.form-group
|
|
||||||
%label{ for: 'urls' }= t('.urls.label')
|
|
||||||
%textarea#urls.form-control{ name: 'urls', autocomplete: 'on', required: true, rows: @normalized_urls.size, aria_describedby: 'help-urls' }= @normalized_urls.join("\n")
|
|
||||||
%small#help-urls.feedback.form-text.text-muted= t('.urls.help')
|
|
||||||
.form-group
|
|
||||||
%button.btn{ type: 'submit' }= t('.urls.submit')
|
|
||||||
- if @normalized_urls.present?
|
|
||||||
= line_chart site_stats_uris_path(urls: params[:urls], **@chart_params), **@chart_options
|
|
||||||
|
|
||||||
.mb-5
|
|
||||||
%h2= t('.resources.title')
|
|
||||||
%p.lead= t('.resources.description')
|
|
||||||
|
|
||||||
- Stat::RESOURCES.each do |resource|
|
|
||||||
.mb-5
|
|
||||||
%h3= t(".resources.#{resource}.title")
|
|
||||||
%p.lead= t(".resources.#{resource}.description")
|
|
||||||
= line_chart site_stats_resources_path(resource: resource, **@chart_params), **@chart_options.merge(StatsController::EXTRA_OPTIONS[resource])
|
|
||||||
|
|
|
@ -11,8 +11,6 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||||
inflect.singular 'licencias', 'licencia'
|
inflect.singular 'licencias', 'licencia'
|
||||||
inflect.plural 'rol', 'roles'
|
inflect.plural 'rol', 'roles'
|
||||||
inflect.singular 'roles', 'rol'
|
inflect.singular 'roles', 'rol'
|
||||||
inflect.plural 'rollup', 'rollups'
|
|
||||||
inflect.singular 'rollups', 'rollup'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
ActiveSupport::Inflector.inflections(:es) do |inflect|
|
ActiveSupport::Inflector.inflections(:es) do |inflect|
|
||||||
|
@ -26,6 +24,4 @@ ActiveSupport::Inflector.inflections(:es) do |inflect|
|
||||||
inflect.singular 'roles', 'rol'
|
inflect.singular 'roles', 'rol'
|
||||||
inflect.plural 'licencia', 'licencias'
|
inflect.plural 'licencia', 'licencias'
|
||||||
inflect.singular 'licencias', 'licencia'
|
inflect.singular 'licencias', 'licencia'
|
||||||
inflect.plural 'rollup', 'rollups'
|
|
||||||
inflect.singular 'rollups', 'rollup'
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,8 +19,8 @@ en:
|
||||||
remember_me: 'Keeps session open for %{remember_for}'
|
remember_me: 'Keeps session open for %{remember_for}'
|
||||||
actions:
|
actions:
|
||||||
sr-help: "After this form you'll find links to recover your account and other actions."
|
sr-help: "After this form you'll find links to recover your account and other actions."
|
||||||
_true: 'Yes'
|
_true: Yes
|
||||||
_false: 'No'
|
_false: No
|
||||||
svg:
|
svg:
|
||||||
sutty:
|
sutty:
|
||||||
title: Sutty
|
title: Sutty
|
||||||
|
@ -163,7 +163,7 @@ en:
|
||||||
signature: 'With love, Sutty'
|
signature: 'With love, Sutty'
|
||||||
breadcrumb:
|
breadcrumb:
|
||||||
title: 'Your location in Sutty'
|
title: 'Your location in Sutty'
|
||||||
logout: Log out
|
logout: Exit
|
||||||
mutual_aid: Mutual aid
|
mutual_aid: Mutual aid
|
||||||
collaborations:
|
collaborations:
|
||||||
collaborate:
|
collaborate:
|
||||||
|
@ -252,38 +252,9 @@ en:
|
||||||
help: |
|
help: |
|
||||||
These statistics show information about how your site is generated and
|
These statistics show information about how your site is generated and
|
||||||
how many resources it uses.
|
how many resources it uses.
|
||||||
last_update: 'Updated every hour. Last update on '
|
build:
|
||||||
empty: 'There is no enough information yet. We invite you to come back in %{please_return_at}!'
|
average: 'Average building time'
|
||||||
loading: 'Loading...'
|
maximum: 'Maximum building time'
|
||||||
hour: 'Hourly'
|
|
||||||
day: 'Daily'
|
|
||||||
week: 'Weekly'
|
|
||||||
month: 'Monthly'
|
|
||||||
year: 'Yearly'
|
|
||||||
host:
|
|
||||||
title:
|
|
||||||
zero: 'Site visits'
|
|
||||||
one: 'Site visits'
|
|
||||||
other: 'Visits by domain name'
|
|
||||||
description: 'Counts visited pages on your site, grouped by domain names in use.'
|
|
||||||
urls:
|
|
||||||
title: 'Visits by URL'
|
|
||||||
description: 'Counts visits or downloads on any URL.'
|
|
||||||
label: 'URLs ("links")'
|
|
||||||
help: 'Copy and paste a single URL per line'
|
|
||||||
submit: 'Update graph'
|
|
||||||
resources:
|
|
||||||
title: 'Resource usage'
|
|
||||||
description: "In this section you can find statistics on your site's use of Sutty's shared resources"
|
|
||||||
builds:
|
|
||||||
title: 'Site publication'
|
|
||||||
description: 'Times you published your site.'
|
|
||||||
space_used:
|
|
||||||
title: 'Server disk usage'
|
|
||||||
description: 'Average storage space used by your site.'
|
|
||||||
build_time:
|
|
||||||
title: 'Publication time'
|
|
||||||
description: 'Average time your site takes to build.'
|
|
||||||
sites:
|
sites:
|
||||||
donations:
|
donations:
|
||||||
url: 'https://donaciones.sutty.nl/en/'
|
url: 'https://donaciones.sutty.nl/en/'
|
||||||
|
@ -405,8 +376,6 @@ en:
|
||||||
en: 'English'
|
en: 'English'
|
||||||
ar: 'Arabic'
|
ar: 'Arabic'
|
||||||
posts:
|
posts:
|
||||||
prev: Previous page
|
|
||||||
next: Next page
|
|
||||||
empty: "There are no results for those search parameters."
|
empty: "There are no results for those search parameters."
|
||||||
caption: Post list
|
caption: Post list
|
||||||
attribute_ro:
|
attribute_ro:
|
||||||
|
@ -444,8 +413,6 @@ en:
|
||||||
destroy: Remove image
|
destroy: Remove image
|
||||||
belongs_to:
|
belongs_to:
|
||||||
empty: "(Empty)"
|
empty: "(Empty)"
|
||||||
predefined_value:
|
|
||||||
empty: "(Empty)"
|
|
||||||
draft:
|
draft:
|
||||||
label: Draft
|
label: Draft
|
||||||
reorder:
|
reorder:
|
||||||
|
|
|
@ -19,8 +19,8 @@ es:
|
||||||
remember_me: 'Mantiene la sesión abierta por %{remember_for}'
|
remember_me: 'Mantiene la sesión abierta por %{remember_for}'
|
||||||
actions:
|
actions:
|
||||||
sr-help: 'Después del formulario encontrarás vínculos para recuperar tu cuenta, entre otras acciones.'
|
sr-help: 'Después del formulario encontrarás vínculos para recuperar tu cuenta, entre otras acciones.'
|
||||||
_true: 'Sí'
|
_true: Sí
|
||||||
_false: 'No'
|
_false: No
|
||||||
svg:
|
svg:
|
||||||
sutty:
|
sutty:
|
||||||
title: Sutty
|
title: Sutty
|
||||||
|
@ -163,7 +163,7 @@ es:
|
||||||
signature: 'Con cariño, Sutty'
|
signature: 'Con cariño, Sutty'
|
||||||
breadcrumb:
|
breadcrumb:
|
||||||
title: 'Tu ubicación en Sutty'
|
title: 'Tu ubicación en Sutty'
|
||||||
logout: Cerrar sesión
|
logout: Salir
|
||||||
mutual_aid: Ayuda mutua
|
mutual_aid: Ayuda mutua
|
||||||
collaborations:
|
collaborations:
|
||||||
collaborate:
|
collaborate:
|
||||||
|
@ -257,38 +257,9 @@ es:
|
||||||
help: |
|
help: |
|
||||||
Las estadísticas visibilizan información sobre cómo se genera y
|
Las estadísticas visibilizan información sobre cómo se genera y
|
||||||
cuántos recursos utiliza tu sitio.
|
cuántos recursos utiliza tu sitio.
|
||||||
last_update: 'Actualizadas cada hora. Última actualización hace '
|
build:
|
||||||
empty: 'Todavía no hay información suficiente. Te invitamos a volver en %{please_return_at} :)'
|
average: 'Tiempo promedio de generación'
|
||||||
loading: 'Cargando...'
|
maximum: 'Tiempo máximo de generación'
|
||||||
hour: 'Por hora'
|
|
||||||
day: 'Diarias'
|
|
||||||
week: 'Semanales'
|
|
||||||
month: 'Mensuales'
|
|
||||||
year: 'Anuales'
|
|
||||||
host:
|
|
||||||
title:
|
|
||||||
zero: 'Visitas del sitio'
|
|
||||||
one: 'Visitas del sitio'
|
|
||||||
other: 'Visitas agrupadas por nombre de dominio del sitio'
|
|
||||||
description: 'Cuenta la cantidad de páginas visitadas en tu sitio.'
|
|
||||||
urls:
|
|
||||||
title: 'Visitas por dirección'
|
|
||||||
description: 'Cantidad de visitas o descargas por dirección.'
|
|
||||||
label: 'Direcciones web (URL, "links", vínculos)'
|
|
||||||
help: 'Copia y pega una dirección por línea.'
|
|
||||||
submit: 'Actualizar gráfico'
|
|
||||||
resources:
|
|
||||||
title: 'Uso de recursos'
|
|
||||||
description: 'En esta sección podrás acceder a estadísticas del uso de recursos compartidos con otros sitios alojados en Sutty.'
|
|
||||||
builds:
|
|
||||||
title: 'Publicaciones del sitio'
|
|
||||||
description: 'Cantidad de veces que publicaste tu sitio.'
|
|
||||||
space_used:
|
|
||||||
title: 'Espacio utilizado en el servidor'
|
|
||||||
description: 'Espacio en disco que ocupa en promedio tu sitio.'
|
|
||||||
build_time:
|
|
||||||
title: 'Tiempo de publicación'
|
|
||||||
description: 'Tiempo promedio que toma en publicarse tu sitio.'
|
|
||||||
sites:
|
sites:
|
||||||
donations:
|
donations:
|
||||||
url: 'https://donaciones.sutty.nl/'
|
url: 'https://donaciones.sutty.nl/'
|
||||||
|
@ -413,8 +384,6 @@ es:
|
||||||
en: 'inglés'
|
en: 'inglés'
|
||||||
ar: 'árabe'
|
ar: 'árabe'
|
||||||
posts:
|
posts:
|
||||||
prev: Página anterior
|
|
||||||
next: Página siguiente
|
|
||||||
empty: No hay artículos con estos parámetros de búsqueda.
|
empty: No hay artículos con estos parámetros de búsqueda.
|
||||||
caption: Lista de artículos
|
caption: Lista de artículos
|
||||||
attribute_ro:
|
attribute_ro:
|
||||||
|
@ -452,8 +421,6 @@ es:
|
||||||
destroy: 'Eliminar imagen'
|
destroy: 'Eliminar imagen'
|
||||||
belongs_to:
|
belongs_to:
|
||||||
empty: "(Vacío)"
|
empty: "(Vacío)"
|
||||||
predefined_value:
|
|
||||||
empty: "(Vacío)"
|
|
||||||
draft:
|
draft:
|
||||||
label: Borrador
|
label: Borrador
|
||||||
reorder:
|
reorder:
|
||||||
|
|
|
@ -57,10 +57,9 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
# Gestionar artículos según idioma
|
# Gestionar artículos según idioma
|
||||||
nested do
|
nested do
|
||||||
scope '/(:locale)', constraint: /[a-z]{2}/ do
|
scope '(:locale)' do
|
||||||
post :'posts/reorder', to: 'posts#reorder'
|
post :'posts/reorder', to: 'posts#reorder'
|
||||||
resources :posts do
|
resources :posts do
|
||||||
get 'p/:page', action: :index, on: :collection
|
|
||||||
get :preview, to: 'posts#preview'
|
get :preview, to: 'posts#preview'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -76,8 +75,5 @@ Rails.application.routes.draw do
|
||||||
post 'reorder_posts', to: 'sites#reorder_posts'
|
post 'reorder_posts', to: 'sites#reorder_posts'
|
||||||
|
|
||||||
resources :stats, only: [:index]
|
resources :stats, only: [:index]
|
||||||
get :'stats/host', to: 'stats#host'
|
|
||||||
get :'stats/uris', to: 'stats#uris'
|
|
||||||
get :'stats/resources', to: 'stats#resources'
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Crear la tabla de Rollups
|
|
||||||
class CreateRollups < ActiveRecord::Migration[6.1]
|
|
||||||
def change
|
|
||||||
create_table :rollups do |t|
|
|
||||||
t.string :name, null: false
|
|
||||||
t.string :interval, null: false
|
|
||||||
t.datetime :time, null: false
|
|
||||||
t.jsonb :dimensions, null: false, default: {}
|
|
||||||
t.float :value
|
|
||||||
end
|
|
||||||
add_index :rollups, %i[name interval time dimensions], unique: true
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,18 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Cambia los msec a datetime para poder agregar por tiempos
|
|
||||||
class AddCreateAtToAccessLogs < ActiveRecord::Migration[6.1]
|
|
||||||
def up
|
|
||||||
add_column :access_logs, :created_at, :datetime, precision: 6
|
|
||||||
|
|
||||||
create_trigger(compatibility: 1).on(:access_logs).before(:insert) do
|
|
||||||
'new.created_at := to_timestamp(new.msec)'
|
|
||||||
end
|
|
||||||
|
|
||||||
ActiveRecord::Base.connection.execute('update access_logs set created_at = to_timestamp(msec);')
|
|
||||||
end
|
|
||||||
|
|
||||||
def down
|
|
||||||
remove_column :access_logs, :created_at
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,10 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Agrega índices únicos que pensábamos que ya existían.
|
|
||||||
class AddUniqueness < ActiveRecord::Migration[6.1]
|
|
||||||
def change
|
|
||||||
add_index :designs, :name, unique: true
|
|
||||||
add_index :designs, :gem, unique: true
|
|
||||||
add_index :licencias, :name, unique: true
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,12 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Una tabla que lleva el recuento de recolección de estadísticas, solo
|
|
||||||
# es necesario para saber cuándo se hicieron, si se hicieron y usar como
|
|
||||||
# caché.
|
|
||||||
class CreateStats < ActiveRecord::Migration[6.1]
|
|
||||||
def change
|
|
||||||
create_table :stats do |t|
|
|
||||||
t.timestamps
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -13,8 +13,6 @@
|
||||||
"@rails/ujs": "^6.1.3-1",
|
"@rails/ujs": "^6.1.3-1",
|
||||||
"@rails/webpacker": "5.2.1",
|
"@rails/webpacker": "5.2.1",
|
||||||
"babel-loader": "^8.2.2",
|
"babel-loader": "^8.2.2",
|
||||||
"chart.js": "^3.5.1",
|
|
||||||
"chartkick": "^4.0.5",
|
|
||||||
"circular-dependency-plugin": "^5.2.2",
|
"circular-dependency-plugin": "^5.2.2",
|
||||||
"commonmark": "^0.29.0",
|
"commonmark": "^0.29.0",
|
||||||
"fork-awesome": "^1.1.7",
|
"fork-awesome": "^1.1.7",
|
||||||
|
|
24
yarn.lock
24
yarn.lock
|
@ -2119,25 +2119,6 @@ chalk@^4.1.0:
|
||||||
ansi-styles "^4.1.0"
|
ansi-styles "^4.1.0"
|
||||||
supports-color "^7.1.0"
|
supports-color "^7.1.0"
|
||||||
|
|
||||||
chart.js@>=3.0.2, chart.js@^3.5.1:
|
|
||||||
version "3.5.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.5.1.tgz#73e24d23a4134a70ccdb5e79a917f156b6f3644a"
|
|
||||||
integrity sha512-m5kzt72I1WQ9LILwQC4syla/LD/N413RYv2Dx2nnTkRS9iv/ey1xLTt0DnPc/eWV4zI+BgEgDYBIzbQhZHc/PQ==
|
|
||||||
|
|
||||||
chartjs-adapter-date-fns@>=2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz#5e53b2f660b993698f936f509c86dddf9ed44c6b"
|
|
||||||
integrity sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw==
|
|
||||||
|
|
||||||
chartkick@^4.0.5:
|
|
||||||
version "4.0.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/chartkick/-/chartkick-4.0.5.tgz#310a60c931e8ceedc39adee2ef8e9d1e474cb0e6"
|
|
||||||
integrity sha512-xKak4Fsgfvp1hj/LykRKkniDMaZASx2A4TdVc/sfsiNFFNf1m+D7PGwP1vgj1UsbsCjOCSfGWWyJpOYxkUCBug==
|
|
||||||
optionalDependencies:
|
|
||||||
chart.js ">=3.0.2"
|
|
||||||
chartjs-adapter-date-fns ">=2.0.0"
|
|
||||||
date-fns ">=2.0.0"
|
|
||||||
|
|
||||||
chokidar@^2.1.8:
|
chokidar@^2.1.8:
|
||||||
version "2.1.8"
|
version "2.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
|
||||||
|
@ -2763,11 +2744,6 @@ dashdash@^1.12.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
assert-plus "^1.0.0"
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
date-fns@>=2.0.0:
|
|
||||||
version "2.24.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.24.0.tgz#7d86dc0d93c87b76b63d213b4413337cfd1c105d"
|
|
||||||
integrity sha512-6ujwvwgPID6zbI0o7UbURi2vlLDR9uP26+tW6Lg+Ji3w7dd0i3DOcjcClLjLPranT60SSEFBwdSyYwn/ZkPIuw==
|
|
||||||
|
|
||||||
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
|
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
|
Loading…
Reference in a new issue