5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-05-05 05:07:04 +00:00

Merge branch 'rails' into blazer

This commit is contained in:
f 2022-03-07 12:27:53 -03:00
commit 507e7ced84
44 changed files with 1194 additions and 394 deletions

View file

@ -1,5 +1,8 @@
RAILS_GROUPS=assets
DELEGATE=athshe.sutty.nl
HAINISH=../haini.sh/haini.sh
DATABASE= DATABASE=
RAILS_ENV= RAILS_ENV=development
IMAP_SERVER= IMAP_SERVER=
DEFAULT_FROM= DEFAULT_FROM=
EXCEPTION_TO= EXCEPTION_TO=

View file

@ -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.5 AS build FROM alpine:3.13.6 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-bundler ruby-json ruby-bigdecimal ruby-rake RUN apk add --no-cache libxslt libxml2 tzdata ruby 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.3" = `ruby -e 'puts RUBY_VERSION'` RUN test "2.7.4" = `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 RUN gem install --no-document bundler:2.1.4
# Empezamos con la usuaria app # Empezamos con la usuaria app
USER app USER app
@ -39,7 +39,8 @@ 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
@ -60,10 +61,6 @@ 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
@ -71,7 +68,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 sutty/monit:latest FROM registry.nulo.in/sutty/monit:3.13.6
ENV RAILS_ENV production ENV RAILS_ENV production
# Pandoc # Pandoc
@ -79,13 +76,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-bundler ruby-json ruby-bigdecimal ruby-rake ruby-irb 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 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.3" = `ruby -e 'puts RUBY_VERSION'` RUN test "2.7.4" = `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
@ -97,7 +94,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 foreman RUN gem install --no-document --no-user-install bundler:2.1.4 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

22
Gemfile
View file

@ -11,13 +11,18 @@ 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
# gem 'therubyracer', platforms: :ruby # Solo incluir las gemas cuando estemos en desarrollo o compilando los
# Use SCSS for stylesheets # assets. No es necesario instalarlas en producción.
gem 'sassc-rails' #
# Use Uglifier as compressor for JavaScript assets # XXX: Supuestamente Rails ya soporta RAILS_GROUPS, pero Bundler no.
gem 'uglifier', '>= 1.3.0' if ENV['RAILS_GROUPS']&.split(',')&.include? 'assets'
gem 'bootstrap', '~> 4' gem 'sassc-rails'
gem 'uglifier', '>= 1.3.0'
gem 'bootstrap', '~> 4'
end
gem 'nokogiri'
# 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
@ -28,6 +33,7 @@ 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'
@ -58,6 +64,7 @@ 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'
@ -67,6 +74,7 @@ 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'

View file

@ -6,6 +6,15 @@ GIT
rails (>= 3.0) rails (>= 3.0)
rake (>= 0.8.7) rake (>= 0.8.7)
GIT
remote: https://github.com/ankane/rollup.git
revision: 0ab6c603450175eb1004f7793e86486943cb9f72
branch: master
specs:
rollups (0.1.3)
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
@ -18,66 +27,66 @@ GIT
GEM GEM
remote: https://gems.sutty.nl/ remote: https://gems.sutty.nl/
specs: specs:
actioncable (6.1.3.2) actioncable (6.1.4.1)
actionpack (= 6.1.3.2) actionpack (= 6.1.4.1)
activesupport (= 6.1.3.2) activesupport (= 6.1.4.1)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (6.1.3.2) actionmailbox (6.1.4.1)
actionpack (= 6.1.3.2) actionpack (= 6.1.4.1)
activejob (= 6.1.3.2) activejob (= 6.1.4.1)
activerecord (= 6.1.3.2) activerecord (= 6.1.4.1)
activestorage (= 6.1.3.2) activestorage (= 6.1.4.1)
activesupport (= 6.1.3.2) activesupport (= 6.1.4.1)
mail (>= 2.7.1) mail (>= 2.7.1)
actionmailer (6.1.3.2) actionmailer (6.1.4.1)
actionpack (= 6.1.3.2) actionpack (= 6.1.4.1)
actionview (= 6.1.3.2) actionview (= 6.1.4.1)
activejob (= 6.1.3.2) activejob (= 6.1.4.1)
activesupport (= 6.1.3.2) activesupport (= 6.1.4.1)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (6.1.3.2) actionpack (6.1.4.1)
actionview (= 6.1.3.2) actionview (= 6.1.4.1)
activesupport (= 6.1.3.2) activesupport (= 6.1.4.1)
rack (~> 2.0, >= 2.0.9) rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.3.2) actiontext (6.1.4.1)
actionpack (= 6.1.3.2) actionpack (= 6.1.4.1)
activerecord (= 6.1.3.2) activerecord (= 6.1.4.1)
activestorage (= 6.1.3.2) activestorage (= 6.1.4.1)
activesupport (= 6.1.3.2) activesupport (= 6.1.4.1)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (6.1.3.2) actionview (6.1.4.1)
activesupport (= 6.1.3.2) activesupport (= 6.1.4.1)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.1.3.2) activejob (6.1.4.1)
activesupport (= 6.1.3.2) activesupport (= 6.1.4.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (6.1.3.2) activemodel (6.1.4.1)
activesupport (= 6.1.3.2) activesupport (= 6.1.4.1)
activerecord (6.1.3.2) activerecord (6.1.4.1)
activemodel (= 6.1.3.2) activemodel (= 6.1.4.1)
activesupport (= 6.1.3.2) activesupport (= 6.1.4.1)
activestorage (6.1.3.2) activestorage (6.1.4.1)
actionpack (= 6.1.3.2) actionpack (= 6.1.4.1)
activejob (= 6.1.3.2) activejob (= 6.1.4.1)
activerecord (= 6.1.3.2) activerecord (= 6.1.4.1)
activesupport (= 6.1.3.2) activesupport (= 6.1.4.1)
marcel (~> 1.0.0) marcel (~> 1.0.0)
mini_mime (~> 1.0.2) mini_mime (>= 1.1.0)
activesupport (6.1.3.2) activesupport (6.1.4.1)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
minitest (>= 5.1) minitest (>= 5.1)
tzinfo (~> 2.0) tzinfo (~> 2.0)
zeitwerk (~> 2.3) zeitwerk (~> 2.3)
addressable (2.7.0) addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 5.0)
adhesiones-jekyll-theme (0.2.1) adhesiones-jekyll-theme (0.2.1)
jekyll (~> 4.0) jekyll (~> 4.0)
@ -89,13 +98,13 @@ GEM
jekyll-relative-urls (~> 0.0) jekyll-relative-urls (~> 0.0)
jekyll-seo-tag (~> 2.1) jekyll-seo-tag (~> 2.1)
ast (2.4.2) ast (2.4.2)
autoprefixer-rails (10.2.5.0) autoprefixer-rails (10.3.3.0)
execjs (< 2.8.0) execjs (~> 2)
bcrypt (3.1.16-x86_64-linux-musl) bcrypt (3.1.16-x86_64-linux-musl)
bcrypt_pbkdf (1.1.0-x86_64-linux-musl) bcrypt_pbkdf (1.1.0-x86_64-linux-musl)
benchmark-ips (2.8.4) benchmark-ips (2.9.2)
bindex (0.8.1-x86_64-linux-musl) bindex (0.8.1-x86_64-linux-musl)
blazer (2.4.2) blazer (2.4.7)
activerecord (>= 5) activerecord (>= 5)
chartkick (>= 3.2) chartkick (>= 3.2)
railties (>= 5) railties (>= 5)
@ -104,7 +113,7 @@ GEM
autoprefixer-rails (>= 9.1.0) autoprefixer-rails (>= 9.1.0)
popper_js (>= 1.14.3, < 2) popper_js (>= 1.14.3, < 2)
sassc-rails (>= 2.0.0) sassc-rails (>= 2.0.0)
brakeman (5.0.1) brakeman (5.1.2)
builder (3.2.4) builder (3.2.4)
capybara (2.18.0) capybara (2.18.0)
addressable addressable
@ -113,15 +122,15 @@ GEM
rack (>= 1.0.0) rack (>= 1.0.0)
rack-test (>= 0.5.4) rack-test (>= 0.5.4)
xpath (>= 2.0, < 4.0) xpath (>= 2.0, < 4.0)
chartkick (4.0.4) chartkick (4.1.2)
childprocess (3.0.0) childprocess (4.1.0)
coderay (1.1.3) coderay (1.1.3)
colorator (1.1.0) colorator (1.1.0)
commonmarker (0.21.2-x86_64-linux-musl) commonmarker (0.21.2-x86_64-linux-musl)
ruby-enum (~> 0.5) ruby-enum (~> 0.5)
concurrent-ruby (1.1.8) concurrent-ruby (1.1.9)
concurrent-ruby-ext (1.1.8-x86_64-linux-musl) concurrent-ruby-ext (1.1.9-x86_64-linux-musl)
concurrent-ruby (= 1.1.8) concurrent-ruby (= 1.1.9)
crass (1.0.6) crass (1.0.6)
database_cleaner (2.0.1) database_cleaner (2.0.1)
database_cleaner-active_record (~> 2.0.0) database_cleaner-active_record (~> 2.0.0)
@ -129,8 +138,8 @@ GEM
activerecord (>= 5.a) activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0) database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1) database_cleaner-core (2.0.1)
dead_end (1.1.7) dead_end (3.1.0)
derailed_benchmarks (2.1.0) derailed_benchmarks (2.1.1)
benchmark-ips (~> 2) benchmark-ips (~> 2)
dead_end dead_end
get_process_mem (~> 0) get_process_mem (~> 0)
@ -148,8 +157,8 @@ GEM
railties (>= 4.1.0) railties (>= 4.1.0)
responders responders
warden (~> 1.2.3) warden (~> 1.2.3)
devise-i18n (1.9.4) devise-i18n (1.10.1)
devise (>= 4.7.1) devise (>= 4.8.0)
devise_invitable (2.0.5) devise_invitable (2.0.5)
actionmailer (>= 5.0) actionmailer (>= 5.0)
devise (>= 4.6) devise (>= 4.6)
@ -157,8 +166,8 @@ GEM
dotenv-rails (2.7.6) dotenv-rails (2.7.6)
dotenv (= 2.7.6) dotenv (= 2.7.6)
railties (>= 3.2) railties (>= 3.2)
down (5.2.1) down (5.2.4)
addressable (~> 2.5) addressable (~> 2.8)
ed25519 (1.2.4-x86_64-linux-musl) ed25519 (1.2.4-x86_64-linux-musl)
editorial-autogestiva-jekyll-theme (0.3.4) editorial-autogestiva-jekyll-theme (0.3.4)
jekyll (~> 4) jekyll (~> 4)
@ -179,48 +188,50 @@ GEM
jekyll-unique-urls (~> 0) jekyll-unique-urls (~> 0)
jekyll-write-and-commit-changes (~> 0) jekyll-write-and-commit-changes (~> 0)
sutty-liquid (~> 0) sutty-liquid (~> 0)
em-websocket (0.5.2) em-websocket (0.5.3)
eventmachine (>= 0.12.9) eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0) http_parser.rb (~> 0)
errbase (0.2.1) errbase (0.2.1)
erubi (1.10.0) erubi (1.10.0)
eventmachine (1.2.7-x86_64-linux-musl) eventmachine (1.2.7-x86_64-linux-musl)
exception_notification (4.4.3) exception_notification (4.4.3)
actionmailer (>= 4.0, < 7) actionmailer (>= 4.0, < 7)
activesupport (>= 4.0, < 7) activesupport (>= 4.0, < 7)
execjs (2.7.0) execjs (2.8.1)
factory_bot (6.2.0) factory_bot (6.2.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
factory_bot_rails (6.2.0) factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0) factory_bot (~> 6.2.0)
railties (>= 5.0.0) railties (>= 5.0.0)
fast_blank (1.0.0-x86_64-linux-musl) fast_blank (1.0.1-x86_64-linux-musl)
fast_jsonparser (0.5.0-x86_64-linux-musl) fast_jsonparser (0.5.0-x86_64-linux-musl)
ffi (1.15.0-x86_64-linux-musl) ffi (1.15.4-x86_64-linux-musl)
flamegraph (0.9.5) flamegraph (0.9.5)
forwardable-extended (2.6.0) forwardable-extended (2.6.0)
friendly_id (5.4.2) friendly_id (5.4.2)
activerecord (>= 4.0.0) activerecord (>= 4.0.0)
get_process_mem (0.2.7) get_process_mem (0.2.7)
ffi (~> 1.0) ffi (~> 1.0)
globalid (0.4.2) globalid (0.6.0)
activesupport (>= 4.2.0) activesupport (>= 5.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)
ruby_parser (~> 3.10) ruby_parser (~> 3.10)
haml (5.2.1) haml (5.2.2)
temple (>= 0.8.0) temple (>= 0.8.0)
tilt tilt
haml-lint (0.999.999) haml-lint (0.999.999)
haml_lint haml_lint
haml_lint (0.37.0) haml_lint (0.37.1)
haml (>= 4.0, < 5.3) haml (>= 4.0, < 5.3)
parallel (~> 1.10) parallel (~> 1.10)
rainbow rainbow
rubocop (>= 0.50.0) rubocop (>= 0.50.0)
sysexits (~> 1.1) sysexits (~> 1.1)
hamlit (2.15.0-x86_64-linux-musl) hamlit (2.15.1-x86_64-linux-musl)
temple (>= 0.8.2) temple (>= 0.8.2)
thor thor
tilt tilt
@ -232,24 +243,24 @@ GEM
heapy (0.2.0) heapy (0.2.0)
thor thor
hiredis (0.6.3-x86_64-linux-musl) hiredis (0.6.3-x86_64-linux-musl)
http_parser.rb (0.6.0-x86_64-linux-musl) http_parser.rb (0.8.0-x86_64-linux-musl)
httparty (0.18.1) httparty (0.18.1)
mime-types (~> 3.0) mime-types (~> 3.0)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
i18n (1.8.10) i18n (1.8.11)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
icalendar (2.7.1) icalendar (2.7.1)
ice_cube (~> 0.16) ice_cube (~> 0.16)
ice_cube (0.16.3) ice_cube (0.16.4)
image_processing (1.12.1) image_processing (1.12.1)
mini_magick (>= 4.9.5, < 5) mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3) ruby-vips (>= 2.0.17, < 3)
inline_svg (1.7.2) inline_svg (1.7.2)
activesupport (>= 3.0) activesupport (>= 3.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
jbuilder (2.11.2) jbuilder (2.11.3)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
jekyll (4.2.0) jekyll (4.2.1)
addressable (~> 2.4) addressable (~> 2.4)
colorator (~> 1.0) colorator (~> 1.0)
em-websocket (~> 0.5) em-websocket (~> 0.5)
@ -264,8 +275,8 @@ GEM
rouge (~> 3.0) rouge (~> 3.0)
safe_yaml (~> 1.0) safe_yaml (~> 1.0)
terminal-table (~> 2.0) terminal-table (~> 2.0)
jekyll-commonmark (1.3.1) jekyll-commonmark (1.3.2)
commonmarker (~> 0.14) commonmarker (~> 0.14, < 0.22)
jekyll (>= 3.7, < 5.0) jekyll (>= 3.7, < 5.0)
jekyll-data (1.1.2) jekyll-data (1.1.2)
jekyll (>= 3.3, < 5.0.0) jekyll (>= 3.3, < 5.0.0)
@ -276,21 +287,19 @@ GEM
jekyll (>= 3.7, < 5.0) jekyll (>= 3.7, < 5.0)
jekyll-hardlinks (0.1.2) jekyll-hardlinks (0.1.2)
jekyll (~> 4) jekyll (~> 4)
jekyll-ignore-layouts (0.1.0) jekyll-ignore-layouts (0.1.2)
jekyll (~> 4) jekyll (~> 4)
jekyll-images (0.2.7) jekyll-images (0.3.0)
jekyll (~> 4) jekyll (~> 4)
ruby-filemagic (~> 0.7) ruby-filemagic (~> 0.7)
ruby-vips (~> 2) ruby-vips (~> 2)
jekyll-include-cache (0.2.1) jekyll-include-cache (0.2.1)
jekyll (>= 3.7, < 5.0) jekyll (>= 3.7, < 5.0)
jekyll-linked-posts (0.2.0) jekyll-linked-posts (0.4.2)
jekyll (~> 4) jekyll (~> 4)
jekyll-locales (0.1.12) jekyll-locales (0.1.13)
jekyll-lunr (0.2.0) jekyll-lunr (0.3.0)
loofah (~> 2.4) loofah (~> 2.4)
jekyll-node-modules (0.1.0)
jekyll (~> 4)
jekyll-order (0.1.4) jekyll-order (0.1.4)
jekyll-relative-urls (0.0.6) jekyll-relative-urls (0.0.6)
jekyll (~> 4) jekyll (~> 4)
@ -298,9 +307,9 @@ GEM
sassc (> 2.0.1, < 3.0) sassc (> 2.0.1, < 3.0)
jekyll-seo-tag (2.7.1) jekyll-seo-tag (2.7.1)
jekyll (>= 3.8, < 5.0) jekyll (>= 3.8, < 5.0)
jekyll-spree-client (0.1.14) jekyll-spree-client (0.1.19)
fast_blank (~> 1) fast_blank (~> 1)
spree-api-client (~> 0.2) spree-api-client (>= 0.2.4)
jekyll-turbolinks (0.0.5) jekyll-turbolinks (0.0.5)
jekyll (~> 4) jekyll (~> 4)
turbolinks-source (~> 5) turbolinks-source (~> 5)
@ -308,9 +317,21 @@ GEM
jekyll (~> 4) jekyll (~> 4)
jekyll-watch (2.2.1) jekyll-watch (2.2.1)
listen (~> 3.0) listen (~> 3.0)
jekyll-write-and-commit-changes (0.1.2) jekyll-write-and-commit-changes (0.2.1)
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)
@ -326,46 +347,46 @@ GEM
ruby_dep (~> 1.2) ruby_dep (~> 1.2)
loaf (0.10.0) loaf (0.10.0)
railties (>= 3.2) railties (>= 3.2)
lockbox (0.6.4) lockbox (0.6.6)
lograge (0.11.2) lograge (0.11.2)
actionpack (>= 4) actionpack (>= 4)
activesupport (>= 4) activesupport (>= 4)
railties (>= 4) railties (>= 4)
request_store (~> 1.0) request_store (~> 1.0)
loofah (2.9.1) loofah (2.12.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.7.1) mail (2.7.1)
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
marcel (1.0.1) marcel (1.0.2)
memory_profiler (1.0.0) memory_profiler (1.0.0)
mercenary (0.4.0) mercenary (0.4.0)
method_source (1.0.0) method_source (1.0.0)
mime-types (3.3.1) mime-types (3.4.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2021.0225) mime-types-data (3.2021.1115)
mini_histogram (0.3.1) mini_histogram (0.3.1)
mini_magick (4.11.0) mini_magick (4.11.0)
mini_mime (1.0.3) mini_mime (1.1.2)
mini_portile2 (2.5.1) mini_portile2 (2.6.1)
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)
jekyll-seo-tag (~> 2.1) jekyll-seo-tag (~> 2.1)
minitest (5.14.4) minitest (5.14.4)
mobility (1.1.2) mobility (1.2.4)
i18n (>= 0.6.10, < 2) i18n (>= 0.6.10, < 2)
request_store (~> 1.0) request_store (~> 1.0)
multi_xml (0.6.0) multi_xml (0.6.0)
net-ssh (6.1.0) net-ssh (6.1.0)
netaddr (2.0.4) netaddr (2.0.5)
nio4r (2.5.7-x86_64-linux-musl) nio4r (2.5.8-x86_64-linux-musl)
nokogiri (1.11.5-x86_64-linux-musl) nokogiri (1.12.5-x86_64-linux-musl)
mini_portile2 (~> 2.5.0) mini_portile2 (~> 2.6.1)
racc (~> 1.4) racc (~> 1.4)
orm_adapter (0.5.0) orm_adapter (0.5.0)
parallel (1.20.1) parallel (1.21.0)
parser (3.0.1.1) parser (3.0.2.0)
ast (~> 2.4.1) ast (~> 2.4.1)
pathutil (0.16.2) pathutil (0.16.2)
forwardable-extended (~> 2.6) forwardable-extended (~> 2.6)
@ -374,27 +395,27 @@ GEM
activerecord (>= 5.2) activerecord (>= 5.2)
activesupport (>= 5.2) activesupport (>= 5.2)
popper_js (1.16.0) popper_js (1.16.0)
prometheus_exporter (0.7.0) prometheus_exporter (1.0.0)
webrick webrick
pry (0.14.1) pry (0.14.1)
coderay (~> 1.1) coderay (~> 1.1)
method_source (~> 1.0) method_source (~> 1.0)
public_suffix (4.0.6) public_suffix (4.0.6)
puma (5.3.1-x86_64-linux-musl) puma (5.5.2-x86_64-linux-musl)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.1.0) pundit (2.1.1)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
racc (1.5.2-x86_64-linux-musl) racc (1.6.0-x86_64-linux-musl)
rack (2.2.3) rack (2.2.3)
rack-cors (1.1.1) rack-cors (1.1.1)
rack (>= 2.0.0) rack (>= 2.0.0)
rack-mini-profiler (2.3.2) rack-mini-profiler (2.3.3)
rack (>= 1.2.0) rack (>= 1.2.0)
rack-proxy (0.6.5) rack-proxy (0.7.0)
rack rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
radios-comunitarias-jekyll-theme (0.1.4) radios-comunitarias-jekyll-theme (0.1.5)
jekyll (~> 4.0) jekyll (~> 4.0)
jekyll-data (~> 1.1) jekyll-data (~> 1.1)
jekyll-feed (~> 0.9) jekyll-feed (~> 0.9)
@ -405,65 +426,66 @@ GEM
jekyll-relative-urls (~> 0.0) jekyll-relative-urls (~> 0.0)
jekyll-seo-tag (~> 2.1) jekyll-seo-tag (~> 2.1)
jekyll-turbolinks (~> 0) jekyll-turbolinks (~> 0)
rails (6.1.3.2) rails (6.1.4.1)
actioncable (= 6.1.3.2) actioncable (= 6.1.4.1)
actionmailbox (= 6.1.3.2) actionmailbox (= 6.1.4.1)
actionmailer (= 6.1.3.2) actionmailer (= 6.1.4.1)
actionpack (= 6.1.3.2) actionpack (= 6.1.4.1)
actiontext (= 6.1.3.2) actiontext (= 6.1.4.1)
actionview (= 6.1.3.2) actionview (= 6.1.4.1)
activejob (= 6.1.3.2) activejob (= 6.1.4.1)
activemodel (= 6.1.3.2) activemodel (= 6.1.4.1)
activerecord (= 6.1.3.2) activerecord (= 6.1.4.1)
activestorage (= 6.1.3.2) activestorage (= 6.1.4.1)
activesupport (= 6.1.3.2) activesupport (= 6.1.4.1)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 6.1.3.2) railties (= 6.1.4.1)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.3.0) rails-html-sanitizer (1.4.2)
loofah (~> 2.3) loofah (~> 2.3)
rails-i18n (6.0.0) rails-i18n (6.0.0)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 7) railties (>= 6.0.0, < 7)
rails_warden (0.6.0) rails_warden (0.6.0)
warden (>= 1.2.0) warden (>= 1.2.0)
railties (6.1.3.2) railties (6.1.4.1)
actionpack (= 6.1.3.2) actionpack (= 6.1.4.1)
activesupport (= 6.1.3.2) activesupport (= 6.1.4.1)
method_source method_source
rake (>= 0.8.7) rake (>= 0.13)
thor (~> 1.0) thor (~> 1.0)
rainbow (3.0.0) rainbow (3.0.0)
rake (13.0.3) rake (13.0.6)
rb-fsevent (0.11.0) rb-fsevent (0.11.0)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
recursero-jekyll-theme (0.1.3) recursero-jekyll-theme (0.2.0)
jekyll (~> 4.0) jekyll (~> 4)
jekyll-commonmark (~> 1.3)
jekyll-data (~> 1.1) jekyll-data (~> 1.1)
jekyll-feed (~> 0.9) jekyll-dotenv (>= 0.2)
jekyll-feed (~> 0.15)
jekyll-ignore-layouts (~> 0)
jekyll-images (~> 0.2) jekyll-images (~> 0.2)
jekyll-include-cache (~> 0) jekyll-include-cache (~> 0)
jekyll-linked-posts (~> 0.2) jekyll-linked-posts (~> 0)
jekyll-locales (~> 0.1) jekyll-locales (~> 0.1)
jekyll-lunr (~> 0.1) jekyll-lunr (~> 0.1)
jekyll-node-modules (~> 0.1) jekyll-order (~> 0)
jekyll-order (~> 0.1) jekyll-relative-urls (~> 0)
jekyll-relative-urls (~> 0.0) jekyll-seo-tag (~> 2)
jekyll-seo-tag (~> 2.1)
jekyll-turbolinks (~> 0)
jekyll-unique-urls (~> 0.1) jekyll-unique-urls (~> 0.1)
sutty-archives (~> 2.2) sutty-archives (~> 2.2)
sutty-liquid (~> 0.1) sutty-liquid (~> 0)
redis (4.2.5) redis (4.5.1)
redis-actionpack (5.2.0) redis-actionpack (5.2.0)
actionpack (>= 5, < 7) actionpack (>= 5, < 7)
redis-rack (>= 2.1.0, < 3) redis-rack (>= 2.1.0, < 3)
redis-store (>= 1.1.0, < 2) redis-store (>= 1.1.0, < 2)
redis-activesupport (5.2.0) redis-activesupport (5.2.1)
activesupport (>= 3, < 7) activesupport (>= 3, < 7)
redis-store (>= 1.3, < 2) redis-store (>= 1.3, < 2)
redis-rack (2.1.3) redis-rack (2.1.3)
@ -482,19 +504,19 @@ GEM
actionpack (>= 5.0) actionpack (>= 5.0)
railties (>= 5.0) railties (>= 5.0)
rexml (3.2.5) rexml (3.2.5)
rouge (3.26.0) rouge (3.26.1)
rubocop (1.15.0) rubocop (1.23.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.0.0.0) parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0) regexp_parser (>= 1.8, < 3.0)
rexml rexml
rubocop-ast (>= 1.5.0, < 2.0) rubocop-ast (>= 1.12.0, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0) unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.5.0) rubocop-ast (1.13.0)
parser (>= 3.0.1.1) parser (>= 3.0.1.1)
rubocop-rails (2.10.1) rubocop-rails (2.12.4)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.7.0, < 2.0) rubocop (>= 1.7.0, < 2.0)
@ -502,17 +524,17 @@ GEM
i18n i18n
ruby-filemagic (0.7.2-x86_64-linux-musl) ruby-filemagic (0.7.2-x86_64-linux-musl)
ruby-progressbar (1.11.0) ruby-progressbar (1.11.0)
ruby-statistics (2.1.3) ruby-statistics (3.0.0)
ruby-vips (2.1.2) ruby-vips (2.1.4)
ffi (~> 1.12) ffi (~> 1.12)
ruby2ruby (2.4.4) ruby2ruby (2.4.4)
ruby_parser (~> 3.1) ruby_parser (~> 3.1)
sexp_processor (~> 4.6) sexp_processor (~> 4.6)
ruby_dep (1.5.0) ruby_dep (1.5.0)
ruby_parser (3.15.1) ruby_parser (3.18.1)
sexp_processor (~> 4.9) sexp_processor (~> 4.16)
rubyzip (2.3.0) rubyzip (2.3.2)
rugged (1.1.0-x86_64-linux-musl) rugged (1.2.0-x86_64-linux-musl)
safe_yaml (1.0.6) safe_yaml (1.0.6)
safely_block (0.3.0) safely_block (0.3.0)
errbase (>= 0.1.1) errbase (>= 0.1.1)
@ -524,11 +546,12 @@ GEM
sprockets (> 3.0) sprockets (> 3.0)
sprockets-rails sprockets-rails
tilt tilt
selenium-webdriver (3.142.7) selenium-webdriver (4.1.0)
childprocess (>= 0.5, < 4.0) childprocess (>= 0.5, < 5.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2) rubyzip (>= 1.2.2)
semantic_range (3.0.0) semantic_range (3.0.0)
sexp_processor (4.15.2) sexp_processor (4.16.0)
share-to-fediverse-jekyll-theme (0.1.4) share-to-fediverse-jekyll-theme (0.1.4)
jekyll (~> 4.0) jekyll (~> 4.0)
jekyll-data (~> 1.1) jekyll-data (~> 1.1)
@ -540,7 +563,7 @@ GEM
simpleidn (0.2.1) simpleidn (0.2.1)
unf (~> 0.1.4) unf (~> 0.1.4)
sourcemap (0.1.1) sourcemap (0.1.1)
spree-api-client (0.2.1) spree-api-client (0.2.4)
fast_blank (~> 1) fast_blank (~> 1)
httparty (~> 0.18.0) httparty (~> 0.18.0)
spring (2.1.1) spring (2.1.1)
@ -550,9 +573,9 @@ GEM
sprockets (4.0.2) sprockets (4.0.2)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-rails (3.2.2) sprockets-rails (3.4.1)
actionpack (>= 4.0) actionpack (>= 5.2)
activesupport (>= 4.0) activesupport (>= 5.2)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
sqlite3 (1.4.2-x86_64-linux-musl) sqlite3 (1.4.2-x86_64-linux-musl)
stackprof (0.2.17-x86_64-linux-musl) stackprof (0.2.17-x86_64-linux-musl)
@ -577,14 +600,14 @@ GEM
jekyll-include-cache (~> 0) jekyll-include-cache (~> 0)
jekyll-relative-urls (~> 0.0) jekyll-relative-urls (~> 0.0)
jekyll-seo-tag (~> 2.1) jekyll-seo-tag (~> 2.1)
sutty-liquid (0.7.3) sutty-liquid (0.7.4)
fast_blank (~> 1.0) fast_blank (~> 1.0)
jekyll (~> 4) jekyll (~> 4)
sutty-minima (2.5.0) sutty-minima (2.5.0)
jekyll (>= 3.5, < 5.0) jekyll (>= 3.5, < 5.0)
jekyll-feed (~> 0.9) jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1) jekyll-seo-tag (~> 2.1)
symbol-fstring (1.0.0-x86_64-linux-musl) symbol-fstring (1.0.2-x86_64-linux-musl)
sysexits (1.2.0) sysexits (1.2.0)
temple (0.8.2) temple (0.8.2)
terminal-table (2.0.0) terminal-table (2.0.0)
@ -601,30 +624,30 @@ GEM
execjs (>= 0.3.0, < 3) execjs (>= 0.3.0, < 3)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.7-x86_64-linux-musl) unf_ext (0.0.8-x86_64-linux-musl)
unicode-display_width (1.7.0) unicode-display_width (1.8.0)
validates_hostname (1.0.11) validates_hostname (1.0.11)
activerecord (>= 3.0) activerecord (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
warden (1.2.9) warden (1.2.9)
rack (>= 2.0.9) rack (>= 2.0.9)
web-console (4.1.0) web-console (4.2.0)
actionview (>= 6.0.0) actionview (>= 6.0.0)
activemodel (>= 6.0.0) activemodel (>= 6.0.0)
bindex (>= 0.4.0) bindex (>= 0.4.0)
railties (>= 6.0.0) railties (>= 6.0.0)
webpacker (5.4.0) webpacker (5.4.3)
activesupport (>= 5.2) activesupport (>= 5.2)
rack-proxy (>= 0.6.1) rack-proxy (>= 0.6.1)
railties (>= 5.2) railties (>= 5.2)
semantic_range (>= 2.3.0) semantic_range (>= 2.3.0)
webrick (1.7.0) webrick (1.7.0)
websocket-driver (0.7.3-x86_64-linux-musl) websocket-driver (0.7.5-x86_64-linux-musl)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.4.2) zeitwerk (2.5.1)
PLATFORMS PLATFORMS
ruby ruby
@ -638,6 +661,7 @@ 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
@ -670,6 +694,7 @@ 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
@ -680,6 +705,7 @@ DEPENDENCIES
minima minima
mobility mobility
net-ssh net-ssh
nokogiri
pg pg
pg_search pg_search
prometheus_exporter prometheus_exporter
@ -695,6 +721,7 @@ DEPENDENCIES
recursero-jekyll-theme recursero-jekyll-theme
redis redis
redis-rails redis-rails
rollups!
rubocop-rails rubocop-rails
rubyzip rubyzip
rugged rugged

192
Makefile
View file

@ -1,136 +1,121 @@
.SHELL := /bin/bash SHELL := /bin/bash
# Incluir las variables de entorno .DEFAULT_GOAL := help
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
root_dir := $(patsubst %/,%,$(dir $(mkfile_path)))
include $(root_dir)/.env
delegate := athshe # Copiar el archivo de configuración y avisar cuando hay que
# actualizarlo.
.env: .env.example
@test -f $@ || cp -v $< $@
@test -f $@ && echo "Revisa $@ para actualizarlo con respecto a $<"
@test -f $@ && diff -auN --color $@ $<
assets := package.json yarn.lock $(shell find app/assets/ app/javascript/ -type f) include .env
alpine_version := 3.13 export
hain ?= ../haini.sh/haini.sh
env ?= staging # XXX: El espacio antes del comentario cuenta como espacio
args ?=## Argumentos para Hain
commit ?= origin/rails## Commit desde el que actualizar
env ?= staging## Entorno del nodo delegado
sutty ?= $(SUTTY)## Dirección local
delegate ?= $(DELEGATE)## Cambia el nodo delegado
hain ?= ENV_FILE=.env $(HAINISH)## Ubicación de Hainish
# El nodo delegado tiene dos entornos, production y staging.
# Dependiendo del entorno que elijamos, se van a generar los assets y el
# contenedor y subirse a un servidor u otro. No utilizamos CI/CD (aún).
#
# Production es el entorno de panel.sutty.nl
ifeq ($(env),production) ifeq ($(env),production)
container ?= sutty container ?= sutty
## TODO: Cambiar a otra cosa
branch ?= rails branch ?= rails
public ?= public public ?= public
endif endif
# Staging es el entorno de panel.staging.sutty.nl
ifeq ($(env),staging) ifeq ($(env),staging)
container := staging container := staging
branch := staging branch := staging
public := staging public := staging
endif endif
export help: always ## Ayuda
@echo -e "Sutty\n" | sed -re "s/^.*/\x1B[38;5;197m&\x1B[0m/"
@echo -e "Servidor: https://panel.$(SUTTY_WITH_PORT)/\n"
@echo -e "Uso: make TAREA args=\"ARGUMENTOS\"\n"
@echo -e "Tareas:\n"
@grep -E "^[a-z\-]+:.*##" Makefile | sed -re "s/(.*):.*##(.*)/\1;\2/" | column -s ";" -t | sed -re "s/^([^ ]+) /\x1B[38;5;197m\1\x1B[0m/"
@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/"
public/packs/manifest.json.br: $(assets) assets: public/packs/manifest.json.br ## Compilar los assets
$(hain) 'cd /Sutty/sutty; PANEL_URL=https://panel.sutty.nl RAILS_ENV=production NODE_ENV=production bundle exec rake assets:precompile assets:clean'
assets: public/packs/manifest.json.br test: always ## Ejecutar los tests
$(MAKE) rake args="test RAILS_ENV=test $(args)"
tests := $(shell find test/ -name "*_test.rb") postgresql: /etc/hosts ## Iniciar la base de datos
$(tests): always
$(hain) 'cd /Sutty/sutty; bundle exec rake test TEST="$@" RAILS_ENV=test'
test: always
$(hain) 'cd /Sutty/sutty; RAILS_ENV=test bundle exec rake test'
postgresql: /etc/hosts
pgrep postgres >/dev/null || $(hain) postgresql pgrep postgres >/dev/null || $(hain) postgresql
serve: /etc/hosts postgresql serve-js: /etc/hosts node_modules ## Iniciar el servidor de desarrollo de Javascript
$(hain) 'bundle exec ./bin/webpack-dev-server'
serve: /etc/hosts postgresql Gemfile.lock ## Iniciar el servidor de desarrollo de Rails
$(MAKE) rails args=server $(MAKE) rails args=server
# make rails args="db:migrate" rails: ## Corre rails dentro del entorno de desarrollo (pasar argumentos con args=).
rails:
$(MAKE) bundle args="exec rails $(args)" $(MAKE) bundle args="exec rails $(args)"
rake: rake: ## Corre rake dentro del entorno de desarrollo (pasar argumentos con args=).
$(MAKE) bundle args="exec rake $(args)" $(MAKE) bundle args="exec rake $(args)"
bundle: bundle: ## Corre bundle dentro del entorno de desarrollo (pasar argumentos con args=).
$(hain) 'cd /Sutty/sutty; bundle $(args)' $(hain) 'bundle $(args)'
yarn: psql := psql -h $(PG_HOST) -U $(PG_USER) -p $(PG_PORT) -d sutty
copy-table:
test -n "$(table)"
echo "truncate $(table) $(cascade);" | $(psql)
ssh $(delegate) docker exec postgresql pg_dump -U sutty -d sutty -t $(table) | $(psql)
psql:
$(psql)
rubocop: ## Yutea el código que está por ser commiteado
git status --porcelain \
| grep -E "^(A|M)" \
| sed "s/^...//" \
| grep ".rb$$" \
| ../haini.sh/haini.sh "xargs -r ./bin/rubocop --auto-correct"
audit: ## Encuentra dependencias con vulnerabilidades
$(hain) 'gem install bundler-audit'
$(hain) 'bundle audit --update'
brakeman: ## Busca posibles vulnerabilidades en Sutty
$(MAKE) bundle args='exec brakeman'
yarn: ## Tareas de yarn
$(hain) 'yarn $(args)' $(hain) 'yarn $(args)'
# Servir JS con el dev server. clean: ## Limpieza
# Esto acelera la compilación del javascript, tiene que correrse por separado rm -rf _sites/test-* _deploy/test-* log/*.log tmp/cache tmp/letter_opener tmp/miniprofiler tmp/storage
# de serve.
serve-js: /etc/hosts
$(hain) 'cd /Sutty/sutty; bundle exec ./bin/webpack-dev-server'
# Limpiar los archivos de testeo build: Gemfile.lock ## Generar la imagen Docker
clean:
rm -rf _sites/test-* _deploy/test-*
# Generar la imagen Docker
build: assets
time docker build --build-arg="BRANCH=$(branch)" --build-arg="RAILS_MASTER_KEY=`cat config/master.key`" -t sutty/$(container) . time docker build --build-arg="BRANCH=$(branch)" --build-arg="RAILS_MASTER_KEY=`cat config/master.key`" -t sutty/$(container) .
docker tag sutty/$(container):latest sutty:keep docker tag sutty/$(container):latest sutty:keep
@echo -e "\a"
save: save: ## Subir la imagen Docker al nodo delegado
time docker save sutty/$(container):latest | ssh root@$(delegate).sutty.nl docker load time docker save sutty/$(container):latest | ssh root@$(delegate) docker load
date +%F | xargs -I {} git tag -f $(container)-{} date +%F | xargs -I {} git tag -f $(container)-{}
@echo -e "\a" @echo -e "\a"
# proyectos. ota-js: assets ## Actualizar Javascript en el nodo delegado
../gems/: rsync -avi --delete-after --chown 1000:82 public/ root@$(delegate):/srv/sutty/srv/http/data/_$(public)/
mkdir -p $@ ssh root@$(delegate) docker exec $(container) sh -c "cat /srv/http/tmp/puma.pid | xargs -r kill -USR2"
# Crear el directorio donde se almacenan las gemas binarias ota: ## Actualizar Rails en el nodo delegado
# TODO: Mover a un proyecto propio, porque lo utilizamos en todos los
gem_dir := $(shell readlink -f ../gems)
gem_cache_dir := $(gem_dir)/cache
gem_binary_dir := $(gem_dir)/$(alpine_version)
ifeq ($(MAKECMDGOALS),build-gems)
gems := $(shell bundle show --paths | xargs -I {} sh -c 'find {}/ext/ -name extconf.rb &>/dev/null && basename {}')
gems := $(patsubst %-x86_64-linux,%,$(gems))
gems := $(patsubst %,$(gem_cache_dir)/%.gem,$(gems))
gems_musl := $(patsubst $(gem_cache_dir)/%.gem,$(gem_binary_dir)/%-x86_64-linux-musl.gem,$(gems))
endif
$(gem_binary_dir)/%-x86_64-linux-musl.gem:
@docker run \
-v $(gem_dir):/srv/gems \
-v `readlink -f ~/.ccache`:/home/builder/.ccache \
-e HTTP_BASIC_USER=$(HTTP_BASIC_USER) \
-e HTTP_BASIC_PASSWORD=$(HTTP_BASIC_PASSWORD) \
-e GEM=`echo $(notdir $*) | sed -re "s/-[^-]+$$//"` \
-e VERSION=`echo $(notdir $*) | sed -re "s/.*-([^-]+)$$/\1/"` \
-e JOBS=2 \
--rm -it \
sutty/gem-compiler:latest || echo "No se pudo compilar $*"
# Compilar todas las gemas binarias y subirlas a gems.sutty.nl para que
# al crear el contenedor no tengamos que compilarlas cada vez
build-gems: $(gems_musl)
cached_gems = $(wildcard $(gem_dir)/cache/*.gem)
rebuild_gems = $(patsubst $(gem_dir)/cache/%.gem,$(gem_dir)/$(alpine_version)/%-x86_64-linux-musl.gem,$(cached_gems))
rebuild-gems: $(rebuild_gems)
dirs := $(patsubst %,root/%,data sites deploy public)
$(dirs):
mkdir -p $@
ota: assets
sudo chgrp -R 82 public/
rsync -avi --delete-after public/ $(delegate):/srv/sutty/srv/http/data/_$(public)/
ssh $(delegate) docker exec $(container) sh -c "cat /srv/http/tmp/puma.pid | xargs -r kill -USR2"
# Hotfixes
#
# TODO: Reemplazar esto por git pull en el contenedor
commit ?= origin/rails
ota-rb:
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/
@ -140,6 +125,19 @@ ota-rb:
ssh $(delegate) docker exec $(container) ota $(commit) ssh $(delegate) docker exec $(container) ota $(commit)
rm ./0*.patch rm ./0*.patch
# Todos los archivos de assets. Si alguno cambia, se van a recompilar
# los assets que luego se suben al nodo delegado.
assets := package.json yarn.lock $(shell find app/assets/ app/javascript/ -type f)
public/packs/manifest.json.br: $(assets)
$(hain) 'PANEL_URL=https://panel.sutty.nl RAILS_ENV=production NODE_ENV=production bundle exec rake assets:precompile assets:clean'
# Correr un test en particular por ejemplo
# `make test/models/usuarie_test.rb`
tests := $(shell find test/ -name "*_test.rb")
$(tests): always
$(MAKE) test args="TEST=$@"
# Agrega las direcciones locales al sistema
/etc/hosts: always /etc/hosts: always
@echo "Chequeando si es necesario agregar el dominio local $(SUTTY)" @echo "Chequeando si es necesario agregar el dominio local $(SUTTY)"
@grep -q " $(SUTTY)$$" $@ || echo -e "127.0.0.1 $(SUTTY)\n::1 $(SUTTY)" | sudo tee -a $@ @grep -q " $(SUTTY)$$" $@ || echo -e "127.0.0.1 $(SUTTY)\n::1 $(SUTTY)" | sudo tee -a $@
@ -147,4 +145,12 @@ ota-rb:
@grep -q " panel.$(SUTTY)$$" $@ || echo -e "127.0.0.1 panel.$(SUTTY)\n::1 panel.$(SUTTY)" | sudo tee -a $@ @grep -q " panel.$(SUTTY)$$" $@ || echo -e "127.0.0.1 panel.$(SUTTY)\n::1 panel.$(SUTTY)" | sudo tee -a $@
@grep -q " postgresql.$(SUTTY)$$" $@ || echo -e "127.0.0.1 postgresql.$(SUTTY)\n::1 postgresql.$(SUTTY)" | sudo tee -a $@ @grep -q " postgresql.$(SUTTY)$$" $@ || echo -e "127.0.0.1 postgresql.$(SUTTY)\n::1 postgresql.$(SUTTY)" | sudo tee -a $@
# Instala las dependencias de Javascript
node_modules: package.json
$(MAKE) yarn
# Instala las dependencias de Rails
Gemfile.lock: Gemfile
$(MAKE) bundle args=install
.PHONY: always .PHONY: always

View file

@ -15,6 +15,17 @@ Este repositorio es la plataforma _Ruby on Rails_ para alojar el
Para más información visita el [sitio de Sutty](https://sutty.nl/). Para más información visita el [sitio de Sutty](https://sutty.nl/).
### Desarrollar
Todas las tareas se gestionan con `make`, por favor instala GNU Make
antes de comenzar.
```bash
make help
```
[Leer la documentación](https://docs.sutty.nl/)
## English ## English
Sutty is a platform for hosting safer, faster and more resilient Sutty is a platform for hosting safer, faster and more resilient
@ -25,3 +36,13 @@ This repository is the Ruby on Rails platform that hosts the
self-managed [panel](https://panel.sutty.nl/). self-managed [panel](https://panel.sutty.nl/).
For more information, visit [Sutty's website](https://sutty.nl/en/). For more information, visit [Sutty's website](https://sutty.nl/en/).
### Development
Every task is run via `make`, please install GNU Make before developing.
```bash
make help
```
[Read the documentation](https://docs.sutty.nl/en/)

View file

@ -2,6 +2,13 @@
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;
} }
@ -64,6 +71,10 @@
strong, em, del, u, sub, sup, small { background: #0002; } strong, em, del, u, sub, sup, small { background: #0002; }
a { background: #13fefe50; } a { background: #13fefe50; }
[data-editor-selected] { outline: #f206f9 solid thick; } [data-editor-selected] { outline: #f206f9 solid thick; }
p[data-multimedia-inner] {
// Ignorar clicks en el párrafo placeholder
pointer-events: none;
}
} }
*[data-editor-loading] { *[data-editor-loading] {

View file

@ -19,21 +19,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.
if stale?([current_usuarie, site, filter_params]) return unless 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
# Filtrar los posts que les invitades no pueden ver # Todos los artículos de este sitio para el idioma actual
@usuarie = site.usuarie? current_usuarie @posts = site.indexed_posts.where(locale: locale)
end # 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
# Filtrar los posts que les invitades no pueden ver
@usuarie = site.usuarie? current_usuarie
end end
def show def show
@ -51,7 +51,7 @@ class PostsController < ApplicationController
def new def new
authorize Post authorize Post
@post = site.posts.build(lang: locale, layout: params[:layout]) @post = site.posts(lang: locale).build(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
@ -151,7 +151,9 @@ class PostsController < ApplicationController
# #
# @return [Hash] # @return [Hash]
def filter_params def filter_params
@filter_params ||= params.permit(:q, :category, :layout).to_h.select { |_, v| v.present? } @filter_params ||= params.permit(:q, :category, :layout).to_hash.select do |_, v|
v.present?
end.transform_keys(&:to_sym)
end end
def site def site

View file

@ -0,0 +1,168 @@
# 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
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
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
authorize SiteStat.new(@site)
end
# TODO: Eliminar cuando mergeemos referer-origin
def hostnames
@hostnames ||= [@site.hostname, @site.alternative_hostnames].flatten
end
# 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

View file

@ -18,6 +18,7 @@ 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()

View file

@ -5,13 +5,23 @@ class DeployJob < ApplicationJob
class DeployException < StandardError; end class DeployException < StandardError; end
# rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/MethodLength
def perform(site, notify = true) def perform(site, notify = true, time = Time.now)
ActiveRecord::Base.connection_pool.with_connection do ActiveRecord::Base.connection_pool.with_connection do
@site = Site.find(site) @site = Site.find(site)
# Si ya hay una tarea corriendo, aplazar esta # Si ya hay una tarea corriendo, aplazar esta. Si estuvo
# esperando más de 10 minutos, recuperar el estado anterior.
#
# Como el trabajo actual se aplaza al siguiente, arrastrar la
# hora original para poder ir haciendo timeouts.
if @site.building? if @site.building?
DeployJob.perform_in(60, site, notify) if 10.minutes.ago >= time
@site.update status: 'waiting'
raise DeployException,
"#{@site.name} la tarea estuvo más de 10 minutos esperando, volviendo al estado original"
end
DeployJob.perform_in(60, site, notify, time)
return return
end end
@ -29,8 +39,11 @@ class DeployJob < ApplicationJob
end end
deploy_others deploy_others
notify_usuaries if notify
# Volver a la espera
@site.update status: 'waiting' @site.update status: 'waiting'
notify_usuaries if notify
end end
end end
# rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/MethodLength

55
app/jobs/periodic_job.rb Normal file
View file

@ -0,0 +1,55 @@
# 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

View file

@ -0,0 +1,67 @@
# 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

View file

@ -0,0 +1,106 @@
# 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

View file

@ -1,4 +1,12 @@
# 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

View file

@ -62,10 +62,15 @@ class DeployLocal < Deploy
'AIRBRAKE_PROJECT_ID' => site.id.to_s, 'AIRBRAKE_PROJECT_ID' => site.id.to_s,
'AIRBRAKE_PROJECT_KEY' => site.airbrake_api_key, 'AIRBRAKE_PROJECT_KEY' => site.airbrake_api_key,
'JEKYLL_ENV' => Rails.env, 'JEKYLL_ENV' => Rails.env,
'LANG' => ENV['LANG'] 'LANG' => ENV['LANG'],
'YARN_CACHE_FOLDER' => yarn_cache_dir
} }
end end
def yarn_cache_dir
Rails.root.join('_yarn_cache').to_s
end
def yarn_lock def yarn_lock
File.join(site.path, 'yarn.lock') File.join(site.path, 'yarn.lock')
end end
@ -80,9 +85,9 @@ class DeployLocal < Deploy
# Corre yarn dentro del repositorio # Corre yarn dentro del repositorio
def yarn def yarn
return unless yarn_lock? return true unless yarn_lock?
run 'yarn' run 'yarn install --production'
end end
def bundle def bundle

View file

@ -25,10 +25,19 @@ class MetadataBoolean < MetadataTemplate
# * false # * false
# * true # * true
def value def value
return document.data.fetch(name.to_s, default_value) if self[:value].nil? case self[:value]
return self[:value] unless self[:value].is_a? String when NilClass
document.data.fetch(name.to_s, default_value)
when String
true_values.include? self[:value]
else
self[:value]
end
end
self[:value] = true_values.include? self[:value] # Siempre guardar el valor de este campo a menos que sea nulo
def empty?
value.nil?
end end
private private

View file

@ -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.hostname.end_with? Site.domain element.remove unless uri.scheme == 'https' && uri.hostname.end_with?(Site.domain)
rescue URI::Error rescue URI::Error
element.remove element.remove
end end

View file

@ -0,0 +1,31 @@
# 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

View file

@ -2,6 +2,12 @@
# 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.sub(%r{\A/}, '') unless post.new?
end
# Los permalinks nunca pueden ser privados # Los permalinks nunca pueden ser privados
def private? def private?
false false

View file

@ -0,0 +1,24 @@
# 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

View file

@ -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: 50..160 } validates :description, length: { in: 10..160 }
validate :deploy_local_presence validate :deploy_local_presence
validate :compatible_layouts, on: :update validate :compatible_layouts, on: :update
@ -37,6 +37,7 @@ 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
@ -65,9 +66,6 @@ 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
@ -180,29 +178,28 @@ 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?
@jekyll.reader.read_data run_in_path do
jekyll.reader.read_data
# Define los valores por defecto según la llave buscada jekyll.data['layouts'] ||= {}
@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
@jekyll.reader.read_collections run_in_path do
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
@ -290,7 +287,9 @@ class Site < ApplicationRecord
# #
# @return [Hash] # @return [Hash]
def theme_layouts def theme_layouts
@jekyll.reader.read_layouts run_in_path do
jekyll.reader.read_layouts
end
end end
# Trae todos los valores disponibles para un campo # Trae todos los valores disponibles para un campo
@ -332,6 +331,12 @@ 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
@ -345,10 +350,7 @@ 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
@ -526,4 +528,8 @@ 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

10
app/models/stat.rb Normal file
View file

@ -0,0 +1,10 @@
# 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

View file

@ -0,0 +1,27 @@
# 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
def host?
index?
end
def resources?
index?
end
def uris?
index?
end
end

View file

@ -122,6 +122,8 @@ 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)

View file

@ -0,0 +1,3 @@
%tr{ id: attribute }
%th= post_label_t(attribute, post: post)
%td{ dir: dir, lang: locale }= metadata.value

View file

@ -0,0 +1,3 @@
%tr{ id: attribute }
%th= post_label_t(attribute, post: post)
%td{ dir: dir, lang: locale }= metadata.value

View file

@ -0,0 +1,6 @@
.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

View file

@ -0,0 +1,7 @@
.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

View file

@ -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 %form{ action: site_posts_path }
- @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 t("locales.#{locale}.name"), site_posts_path(@site, **@filter_params.merge(locale: locale)), = link_to @site.data.dig(locale.to_s, 'locale') || locale, 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,19 +67,24 @@
%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' }
= submit_tag t('posts.reorder.submit'), class: 'btn' .d-flex.flex-row.justify-content-between
%button.btn{ data: { action: 'reorder#unselect' } } %div
= t('posts.reorder.unselect') = submit_tag t('posts.reorder.submit'), class: 'btn'
%span.badge{ data: { target: 'reorder.counter' } } 0 %button.btn{ data: { action: 'reorder#unselect' } }
%button.btn{ data: { action: 'reorder#up' } }= t('posts.reorder.up') = t('posts.reorder.unselect')
%button.btn{ data: { action: 'reorder#down' } }= t('posts.reorder.down') %span.badge{ data: { target: 'reorder.counter' } } 0
%button.btn{ data: { action: 'reorder#top' } }= t('posts.reorder.top') %button.btn{ data: { action: 'reorder#up' } }= t('posts.reorder.up')
%button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom') %button.btn{ data: { action: 'reorder#down' } }= t('posts.reorder.down')
%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
@ -104,19 +109,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')
- if post.front_matter['categories'].present? %br
%br %small
%small = link_to @site.layouts[post.layout].humanized_name, site_posts_path(@site, **@filter_params.merge(layout: post.layout))
- 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 %td.text-nowrap
= post.created_at.strftime('%F') = post.created_at.strftime('%F')
%br/ %br/
= post.order = post.order
%td %td.text-nowrap
- 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?

View file

@ -38,4 +38,4 @@
- cache [metadata, I18n.locale] do - cache [metadata, I18n.locale] do
%section.editor{ id: attr, dir: dir } %section.editor{ id: attr, dir: dir }
= @post.public_send(attr).to_s.html_safe = @post.public_send(attr).value.html_safe

View file

@ -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: 50, required: true maxlength: 160, minlength: 10, 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/

View file

@ -1,17 +1,43 @@
= 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}.
%table.table.table-condensed .mb-5
%tbody - Stat::INTERVALS.each do |interval|
%tr = link_to t(".#{interval}"), site_stats_path(interval: interval, urls: params[:urls]), class: "btn #{'btn-primary active' if @interval == interval}"
%td= t('.build.average')
%td= distance_of_time_in_words_if_more_than_a_minute @build_avg .mb-5
%tr %h2= t('.host.title', count: @hostnames.size)
%td= t('.build.maximum') %p.lead= t('.host.description')
%td= distance_of_time_in_words_if_more_than_a_minute @build_max = line_chart site_stats_host_path(@chart_params), **@chart_options
.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])

View file

@ -11,6 +11,8 @@ 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|
@ -24,4 +26,6 @@ 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

View file

@ -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: Exit logout: Log out
mutual_aid: Mutual aid mutual_aid: Mutual aid
collaborations: collaborations:
collaborate: collaborate:
@ -252,9 +252,38 @@ 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.
build: last_update: 'Updated every hour. Last update on '
average: 'Average building time' empty: 'There is no enough information yet. We invite you to come back in %{please_return_at}!'
maximum: 'Maximum building time' loading: 'Loading...'
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/'
@ -376,6 +405,8 @@ 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:
@ -413,6 +444,8 @@ 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:

View file

@ -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: _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: Salir logout: Cerrar sesión
mutual_aid: Ayuda mutua mutual_aid: Ayuda mutua
collaborations: collaborations:
collaborate: collaborate:
@ -257,9 +257,38 @@ 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.
build: last_update: 'Actualizadas cada hora. Última actualización hace '
average: 'Tiempo promedio de generación' empty: 'Todavía no hay información suficiente. Te invitamos a volver en %{please_return_at} :)'
maximum: 'Tiempo máximo de generación' loading: 'Cargando...'
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/'
@ -384,6 +413,8 @@ 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:
@ -421,6 +452,8 @@ 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:

View file

@ -58,9 +58,10 @@ Rails.application.routes.draw do
# Gestionar artículos según idioma # Gestionar artículos según idioma
nested do nested do
scope '(:locale)' do scope '/(:locale)', constraint: /[a-z]{2}/ 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
@ -74,5 +75,10 @@ Rails.application.routes.draw do
# Compilar el sitio # Compilar el sitio
post 'enqueue', to: 'sites#enqueue' post 'enqueue', to: 'sites#enqueue'
post 'reorder_posts', to: 'sites#reorder_posts' post 'reorder_posts', to: 'sites#reorder_posts'
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

View file

@ -0,0 +1,10 @@
# frozen_string_literal: true
# Agrega la columna de request_uri a la tabla de logs
class AddRequestUriToAccessLogs < ActiveRecord::Migration[6.1]
def change
return unless Rails.env.production?
add_column :access_logs, :request_uri, :string, default: ''
end
end

View file

@ -0,0 +1,15 @@
# 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

View file

@ -0,0 +1,18 @@
# 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

View file

@ -0,0 +1,10 @@
# 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

View file

@ -0,0 +1,12 @@
# 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

View file

@ -13,8 +13,8 @@
"@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": "2.9.3", "chart.js": "^3.5.1",
"chartkick": "3.2.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",

View file

@ -2119,33 +2119,24 @@ 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@2.9.3: chart.js@>=3.0.2, chart.js@^3.5.1:
version "2.9.3" version "3.5.1"
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.3.tgz#ae3884114dafd381bc600f5b35a189138aac1ef7" resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.5.1.tgz#73e24d23a4134a70ccdb5e79a917f156b6f3644a"
integrity sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw== integrity sha512-m5kzt72I1WQ9LILwQC4syla/LD/N413RYv2Dx2nnTkRS9iv/ey1xLTt0DnPc/eWV4zI+BgEgDYBIzbQhZHc/PQ==
dependencies:
chartjs-color "^2.1.0"
moment "^2.10.2"
chartjs-color-string@^0.6.0: chartjs-adapter-date-fns@>=2.0.0:
version "0.6.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71" resolved "https://registry.yarnpkg.com/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz#5e53b2f660b993698f936f509c86dddf9ed44c6b"
integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A== integrity sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw==
dependencies:
color-name "^1.0.0"
chartjs-color@^2.1.0: chartkick@^4.0.5:
version "2.4.1" version "4.0.5"
resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.4.1.tgz#6118bba202fe1ea79dd7f7c0f9da93467296c3b0" resolved "https://registry.yarnpkg.com/chartkick/-/chartkick-4.0.5.tgz#310a60c931e8ceedc39adee2ef8e9d1e474cb0e6"
integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w== integrity sha512-xKak4Fsgfvp1hj/LykRKkniDMaZASx2A4TdVc/sfsiNFFNf1m+D7PGwP1vgj1UsbsCjOCSfGWWyJpOYxkUCBug==
dependencies: optionalDependencies:
chartjs-color-string "^0.6.0" chart.js ">=3.0.2"
color-convert "^1.9.3" chartjs-adapter-date-fns ">=2.0.0"
date-fns ">=2.0.0"
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: chokidar@^2.1.8:
version "2.1.8" version "2.1.8"
@ -2772,6 +2763,11 @@ 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"