5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-06-20 11:22:19 +00:00

Merge branch 'rails' into production.panel.sutty.nl

This commit is contained in:
f 2024-02-22 12:00:27 -03:00
commit cf77514bb6
No known key found for this signature in database
123 changed files with 2249 additions and 1599 deletions

View file

@ -1,8 +1,8 @@
NODE_OPTIONS=--openssl-legacy-provider
# pwgen -1 32 # pwgen -1 32
RAILS_MASTER_KEY=11111111111111111111111111111111 RAILS_MASTER_KEY=11111111111111111111111111111111
RAILS_GROUPS=assets RAILS_GROUPS=assets
DELEGATE=athshe.sutty.nl DELEGATE=panel.sutty.nl
HAINISH=../haini.sh/haini.sh
DATABASE_URL=postgres://suttier@postgresql.sutty.local/sutty DATABASE_URL=postgres://suttier@postgresql.sutty.local/sutty
RAILS_ENV=development RAILS_ENV=development
IMAP_SERVER= IMAP_SERVER=
@ -37,3 +37,5 @@ AIRBRAKE_API_KEY=
GITLAB_URI=https://0xacab.org GITLAB_URI=https://0xacab.org
GITLAB_PROJECT= GITLAB_PROJECT=
GITLAB_TOKEN= GITLAB_TOKEN=
PGVER=15
PGPID=/run/postgresql.pid

1
.env.development Normal file
View file

@ -0,0 +1 @@
HAINISH=../haini.sh/haini.sh

6
.gitignore vendored
View file

@ -28,7 +28,7 @@
/data/* /data/*
/_storage/* /_storage/*
.env* .env.*
# Ignore master key for decrypting credentials and more. # Ignore master key for decrypting credentials and more.
/config/master.key /config/master.key
@ -48,3 +48,7 @@ yarn-debug.log*
/yarn-error.log /yarn-error.log
yarn-debug.log* yarn-debug.log*
.yarn-integrity .yarn-integrity
/.task
/.yardoc
/public/doc/

View file

@ -1,33 +1,99 @@
image: "gitea.nulo.in/sutty/panel:3.14.10-2.7.8-panel.sutty.nl" .apk-add: &apk-add
- "apk add go-task diffutils"
.disable-hainish: &disable-hainish
- "rm -f .env.development"
.cache-ruby: &cache-ruby
- paths:
- "vendor/ruby"
- ".bundle"
.cache-node: &cache-node
- paths:
- "node_modules"
.cache-task: &cache-task
- paths:
- ".task"
image: "registry.0xacab.org/sutty/sutty:3.17.3-3.1.4-rails"
variables: variables:
RAILS_ENV: "production" RAILS_ENV: "production"
LC_ALL: "C.UTF-8" LC_ALL: "C.UTF-8"
HAINISH: ""
cache: cache:
paths:
- "vendor/ruby"
assets: assets:
stage: "build" stage: "deploy"
rules: only:
- if: "$CI_COMMIT_BRANCH == \"panel.sutty.nl\"" - "rails"
- if: "$CI_COMMIT_BRANCH" - "17.3.alpine.panel.sutty.nl"
changes: except:
compare_to: "refs/heads/rails" - "schedules"
paths: cache:
- "package.json" - *cache-ruby
- "app/javascript/**/*" - *cache-node
- "app/assets/**/*" - *cache-task
before_script: before_script:
- "git config --global user.email \"${GIT_USER_EMAIL:-$GITLAB_USER_EMAIL}\"" - "git config --global user.email \"${GIT_USER_EMAIL:-$GITLAB_USER_EMAIL}\""
- "git config --global user.name \"${GIT_USER_NAME:-$GITLAB_USER_NAME}\"" - "git config --global user.name \"${GIT_USER_NAME:-$GITLAB_USER_NAME}\""
- "git remote set-url --push origin \"https://${GITLAB_USERNAME}:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git\"" - "git remote set-url --push origin \"https://${GITLAB_USERNAME}:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git\""
- "apk add python2 dotenv brotli" - "apk add brotli"
- "mv config/credentials.yml.enc.ci config/credentials.yml.enc" - *apk-add
- "cp .env.example .env" - *disable-hainish
- "dotenv bundle install --path=vendor"
script: script:
- "dotenv RAILS_ENV=production bundle exec rails webpacker:clobber" - "go-task assets"
- "dotenv RAILS_ENV=production bundle exec rails assets:precompile"
- "dotenv RAILS_ENV=production bundle exec rails assets:clean"
after_script: after_script:
- "git add public && git commit -m \"ci: assets [skip ci]\"" - "git add public && git commit -m \"ci: assets [skip ci]\""
- "git push -o ci.skip" - "git push -o ci.skip"
gem-audit:
stage: "test"
only:
- "schedules"
cache:
- *cache-ruby
before_script:
- *apk-add
- *disable-hainish
script:
- "go-task gem-audit"
node-audit:
stage: "test"
only:
- "schedules"
cache:
- *cache-node
before_script:
- *apk-add
- *disable-hainish
script:
- "apk add go-task"
- "go-task node-audit"
brakeman:
stage: "test"
cache:
- *cache-ruby
rules:
- if: "$CI_PIPELINE_SOURCE == 'merge_request_event'"
before_script:
- *apk-add
- *disable-hainish
script:
- "go-task bundle -- exec brakeman"
rubocop:
stage: "test"
cache:
- *cache-ruby
rules:
- if: "$CI_PIPELINE_SOURCE == 'merge_request_event'"
before_script:
- *apk-add
- *disable-hainish
script:
- "./bin/modified_files | ./bin/with_extension rb | xargs -r go-task bundle -- exec rubocop"
haml:
stage: "test"
cache:
- *cache-ruby
rules:
- if: "$CI_PIPELINE_SOURCE == 'merge_request_event'"
before_script:
- *apk-add
- *disable-hainish
script:
- "./bin/modified_files | ./bin/with_extension haml | xargs -r go-task bundle -- exec haml-lint"

View file

@ -19,7 +19,6 @@ pipeline:
when: when:
branch: branch:
- "rails" - "rails"
- "panel.sutty.nl"
- "17.3.alpine.panel.sutty.nl" - "17.3.alpine.panel.sutty.nl"
event: "push" event: "push"
path: path:
@ -27,55 +26,8 @@ pipeline:
- "Dockerfile" - "Dockerfile"
- ".dockerignore" - ".dockerignore"
- ".woodpecker.yml" - ".woodpecker.yml"
assets:
image: "gitea.nulo.in/sutty/panel:3.14.10-2.7.8"
commands:
- "apk add python2 dotenv openssh-client brotli"
- "install -d -m 700 ~/.ssh/"
- "echo \"$${KNOWN_HOSTS}\" | base64 -d >> ~/.ssh/known_hosts"
- "chmod 600 ~/.ssh/known_hosts"
- "eval $(ssh-agent -s)"
- "echo \"$${SSH_KEY}\" | base64 -d | ssh-add -"
- "ssh $${ORIGIN%:*}"
- "git config user.name Woodpecker"
- "git config user.email ci@sutty.coop.ar"
- "git remote add upstream $${ORIGIN}"
- "git checkout -B ${CI_COMMIT_BRANCH}"
- "mv config/credentials.yml.enc.ci config/credentials.yml.enc"
- "yarn"
- "cp .env.example .env"
- "dotenv bundle install --path=vendor"
- "dotenv RAILS_ENV=production bundle exec rails webpacker:clobber"
- "dotenv RAILS_ENV=production bundle exec rails assets:precompile"
- "dotenv RAILS_ENV=production bundle exec rails assets:clean"
- "git add public && git commit -m \"ci: assets [skip ci]\""
- "git push upstream ${CI_COMMIT_BRANCH}"
environment:
- "RUBY_VERSION=${RUBY_VERSION}"
- "GEMS_SOURCE=https://14.3.alpine.gems.sutty.nl"
secrets:
- "SSH_KEY"
- "KNOWN_HOSTS"
- "ORIGIN"
when:
branch:
- "rails"
- "panel.sutty.nl"
path:
include:
- "app/assets/**/*"
- "app/javascript/**/*"
- "package.json"
- "yarn.lock"
matrix:
ALPINE_VERSION: "3.14.10"
RUBY_VERSION: "2.7"
RUBY_PATCH: "8"
matrix: matrix:
include: include:
- ALPINE_VERSION: "3.17.3" - ALPINE_VERSION: "3.17.3"
RUBY_VERSION: "3.1" RUBY_VERSION: "3.1"
RUBY_PATCH: "4" RUBY_PATCH: "4"
- ALPINE_VERSION: "3.14.10"
RUBY_VERSION: "2.7"
RUBY_PATCH: "8"

View file

@ -1,6 +1,6 @@
ARG RUBY_VERSION=2.7 ARG RUBY_VERSION=3.1
ARG RUBY_PATCH=8 ARG RUBY_PATCH=4
ARG ALPINE_VERSION=3.14.10 ARG ALPINE_VERSION=3.17.3
ARG BASE_IMAGE=registry.nulo.in/sutty/rails ARG BASE_IMAGE=registry.nulo.in/sutty/rails
FROM ${BASE_IMAGE}:${ALPINE_VERSION}-${RUBY_VERSION}.${RUBY_PATCH} FROM ${BASE_IMAGE}:${ALPINE_VERSION}-${RUBY_VERSION}.${RUBY_PATCH}
ARG PANDOC_VERSION=2.18 ARG PANDOC_VERSION=2.18

10
Gemfile
View file

@ -1,15 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
if ENV['RAILS_ENV'] != 'production' && ENV['HAIN_ENV'].nil?
puts 'Usa haini.sh para generar un entorno de trabajo reproducible'
end
source ENV.fetch('GEMS_SOURCE', 'https://17.3.alpine.gems.sutty.nl') source ENV.fetch('GEMS_SOURCE', 'https://17.3.alpine.gems.sutty.nl')
ruby "~> #{ENV.fetch('RUBY_VERSION', '3.1')}" ruby "~> #{ENV.fetch('RUBY_VERSION', '3.1')}"
gem 'dotenv-rails', require: 'dotenv/rails-now'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.1.0' gem 'rails', '~> 6.1.0'
# Use Puma as the app server # Use Puma as the app server
@ -112,6 +106,7 @@ end
group :development, :test do group :development, :test do
gem 'derailed_benchmarks' gem 'derailed_benchmarks'
gem 'dotenv-rails'
gem 'pry' gem 'pry'
gem 'capybara' gem 'capybara'
gem 'selenium-webdriver' gem 'selenium-webdriver'
@ -119,8 +114,9 @@ group :development, :test do
end end
group :development do group :development do
gem 'reek' gem 'yard'
gem 'brakeman' gem 'brakeman'
gem 'bundler-audit'
gem 'haml-lint', require: false gem 'haml-lint', require: false
gem 'letter_opener' gem 'letter_opener'
gem 'listen' gem 'listen'

View file

@ -25,7 +25,7 @@ GIT
groupdate (>= 5.2) groupdate (>= 5.2)
GEM GEM
remote: https://gems.sutty.nl/ remote: https://17.3.alpine.gems.sutty.nl/
specs: specs:
actioncable (6.1.7.3) actioncable (6.1.7.3)
actionpack (= 6.1.7.3) actionpack (= 6.1.7.3)
@ -89,6 +89,8 @@ GEM
addressable (2.8.4) addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2) ast (2.4.2)
autoprefixer-rails (10.4.13.0)
execjs (~> 2)
bcrypt (3.1.19-x86_64-linux-musl) bcrypt (3.1.19-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.12.0) benchmark-ips (2.12.0)
@ -98,8 +100,15 @@ GEM
chartkick (>= 3.2) chartkick (>= 3.2)
railties (>= 5) railties (>= 5)
safely_block (>= 0.1.1) safely_block (>= 0.1.1)
bootstrap (4.6.2)
autoprefixer-rails (>= 9.1.0)
popper_js (>= 1.16.1, < 2)
sassc-rails (>= 2.0.0)
brakeman (5.4.1) brakeman (5.4.1)
builder (3.2.4) builder (3.2.4)
bundler-audit (0.9.1)
bundler (>= 1.2.0, < 3)
thor (~> 1.0)
capybara (2.18.0) capybara (2.18.0)
addressable addressable
mini_mime (>= 0.1.3) mini_mime (>= 0.1.3)
@ -198,6 +207,7 @@ GEM
exception_notification (4.5.0) exception_notification (4.5.0)
actionmailer (>= 5.2, < 8) actionmailer (>= 5.2, < 8)
activesupport (>= 5.2, < 8) activesupport (>= 5.2, < 8)
execjs (2.8.1)
factory_bot (6.2.1) factory_bot (6.2.1)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
factory_bot_rails (6.2.0) factory_bot_rails (6.2.0)
@ -309,7 +319,6 @@ GEM
rexml rexml
kramdown-parser-gfm (1.1.0) kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0) kramdown (~> 2.0)
kwalify (0.7.2)
launchy (2.5.2) launchy (2.5.2)
addressable (~> 2.8) addressable (~> 2.8)
letter_opener (1.8.1) letter_opener (1.8.1)
@ -373,6 +382,7 @@ GEM
pg_search (2.3.6) pg_search (2.3.6)
activerecord (>= 5.2) activerecord (>= 5.2)
activesupport (>= 5.2) activesupport (>= 5.2)
popper_js (1.16.1)
prometheus_exporter (2.0.8) prometheus_exporter (2.0.8)
webrick webrick
pry (0.14.2) pry (0.14.2)
@ -447,10 +457,6 @@ GEM
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-store (1.9.2) redis-store (1.9.2)
redis (>= 4, < 6) redis (>= 4, < 6)
reek (6.1.4)
kwalify (~> 0.7.0)
parser (~> 3.2.0)
rainbow (>= 2.0, < 4.0)
regexp_parser (2.8.0) regexp_parser (2.8.0)
request_store (1.5.1) request_store (1.5.1)
rack (>= 1.4) rack (>= 1.4)
@ -496,6 +502,12 @@ GEM
errbase (>= 0.1.1) errbase (>= 0.1.1)
sassc (2.4.0-x86_64-linux-musl) sassc (2.4.0-x86_64-linux-musl)
ffi (~> 1.9) ffi (~> 1.9)
sassc-rails (2.1.2)
railties (>= 4.0.0)
sassc (>= 2.0)
sprockets (> 3.0)
sprockets-rails
tilt
selenium-webdriver (4.9.1) selenium-webdriver (4.9.1)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0) rubyzip (>= 1.2.2, < 3.0)
@ -528,7 +540,7 @@ GEM
temple (0.10.1) temple (0.10.1)
terminal-table (2.0.0) terminal-table (2.0.0)
unicode-display_width (~> 1.1, >= 1.1.1) unicode-display_width (~> 1.1, >= 1.1.1)
thor (1.2.2) thor (1.3.0)
tilt (2.1.0) tilt (2.1.0)
timecop (0.9.6) timecop (0.9.6)
timeout (0.3.2) timeout (0.3.2)
@ -537,6 +549,8 @@ GEM
turbolinks-source (5.2.0) turbolinks-source (5.2.0)
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.8.2-x86_64-linux-musl) unf_ext (0.0.8.2-x86_64-linux-musl)
@ -564,6 +578,7 @@ GEM
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
yard (0.9.34)
zeitwerk (2.6.8) zeitwerk (2.6.8)
PLATFORMS PLATFORMS
@ -573,7 +588,9 @@ DEPENDENCIES
bcrypt (~> 3.1.7) bcrypt (~> 3.1.7)
bcrypt_pbkdf bcrypt_pbkdf
blazer blazer
bootstrap (~> 4)
brakeman brakeman
bundler-audit
capybara capybara
chartkick chartkick
commonmarker commonmarker
@ -607,7 +624,6 @@ DEPENDENCIES
jbuilder (~> 2.5) jbuilder (~> 2.5)
jekyll (~> 4.2.0) jekyll (~> 4.2.0)
jekyll-commonmark (~> 1.4.0) jekyll-commonmark (~> 1.4.0)
jekyll-data
jekyll-images jekyll-images
jekyll-include-cache jekyll-include-cache
kaminari kaminari
@ -635,7 +651,6 @@ DEPENDENCIES
rails_warden rails_warden
redis (~> 4.0) redis (~> 4.0)
redis-rails redis-rails
reek
rgl rgl
rollups! rollups!
rubocop-rails rubocop-rails
@ -643,6 +658,7 @@ DEPENDENCIES
rugged (= 1.5.0.1) rugged (= 1.5.0.1)
safe_yaml safe_yaml
safely_block (~> 0.3.0) safely_block (~> 0.3.0)
sassc-rails
selenium-webdriver selenium-webdriver
sourcemap sourcemap
spring spring
@ -654,10 +670,12 @@ DEPENDENCIES
terminal-table terminal-table
timecop timecop
turbolinks (~> 5) turbolinks (~> 5)
uglifier (>= 1.3.0)
validates_hostname validates_hostname
web-console web-console
webpacker webpacker
yaml_db! yaml_db!
yard
RUBY VERSION RUBY VERSION
ruby 3.1.4p223 ruby 3.1.4p223

143
Makefile
View file

@ -1,143 +0,0 @@
SHELL := /bin/bash
.DEFAULT_GOAL := help
# 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 $@ $<
include .env
export
# 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)
container ?= panel
## TODO: Cambiar a otra cosa
branch ?= rails
public ?= public
endif
# Staging es el entorno de panel.staging.sutty.nl
ifeq ($(env),staging)
container := staging
branch := staging
public := staging
endif
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/"
assets: public/packs/manifest.json.br ## Compilar los assets
git add public/assets/ public/packs/ && NADA=true git commit -m "assets [skip ci]" ; true
test: always ## Ejecutar los tests
$(MAKE) rake args="test RAILS_ENV=test $(args)"
postgresql: /etc/hosts ## Iniciar la base de datos
pgrep postgres >/dev/null || $(hain) 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
rails: ## Corre rails dentro del entorno de desarrollo (pasar argumentos con args=).
$(MAKE) bundle args="exec rails $(args)"
rake: ## Corre rake dentro del entorno de desarrollo (pasar argumentos con args=).
$(MAKE) bundle args="exec rake $(args)"
bundle: ## Corre bundle dentro del entorno de desarrollo (pasar argumentos con args=).
$(hain) 'bundle $(args)'
psql := haini.sh 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)'
clean: ## Limpieza
rm -rf _sites/test-* _deploy/test-* log/*.log tmp/cache tmp/letter_opener tmp/miniprofiler tmp/storage
build: Gemfile.lock ## Generar la imagen Docker
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
@echo -e "\a"
save: ## Subir la imagen Docker al nodo delegado
time docker save sutty/$(container):latest | ssh root@$(delegate) docker load
date +%F | xargs -I {} git tag -f $(container)-{}
@echo -e "\a"
ota: ## Actualizar Rails en el nodo delegado
git push
ssh $(delegate) git -C /srv/sutty/srv/http/panel.sutty.nl pull
ssh $(delegate) git -C /srv/sutty/srv/http/panel.sutty.nl lfs prune
ssh $(delegate) chown -R 1000:82 /srv/sutty/srv/http/panel.sutty.nl
ssh $(delegate) docker exec $(container) rails reload
# 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
@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 " api.$(SUTTY)$$" $@ || echo -e "127.0.0.1 api.$(SUTTY)\n::1 api.$(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 $@
# 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

View file

@ -1,5 +1,5 @@
cleanup: bundle exec rake cleanup:everything cleanup: bundle exec rake cleanup:everything
stats: bundle exec rake stats:process_all
distributed_press_tokens_renew: bundle exec rake distributed_press:tokens:renew distributed_press_tokens_renew: bundle exec rake distributed_press:tokens:renew
que: daemonize -c /srv/ -p /srv/tmp/que.pid -u rails /usr/local/bin/syslogize bundle exec que
emergency_cleanup: bundle exec rake cleanup:everything BEFORE=7 emergency_cleanup: bundle exec rake cleanup:everything BEFORE=7
que: daemonize -c /srv/ -p /srv/tmp/que.pid -u rails /usr/local/bin/syslogize bundle exec que
stats: bundle exec rake stats:process_all

View file

@ -17,14 +17,32 @@ Para más información visita el [sitio de Sutty](https://sutty.nl/).
### Desarrollar ### Desarrollar
Todas las tareas se gestionan con `make`, por favor instala GNU Make Para facilitar la gestión de dependencias y entorno de desarrollo,
antes de comenzar. instala [haini.sh](https://0xacab.org/sutty/haini.sh)
Todas las tareas se gestionan con `go-task`. [Instrucciones de
instalación (en inglés)](https://taskfile.dev/installation/)
```bash ```bash
make help go-task
``` ```
[Leer la documentación](https://docs.sutty.nl/) ### Variables de entorno
Las variables de entorno por defecto se encuentran en el archivo `.env`.
Para modificar las opciones, crear o modificar el archivo `.env.local`
con valores distintos.
### Documentación
Para navegar la documentación del código usando YARD:
```bash
go-task doc serve
```
Y luego navegar a <https://panel.sutty.local:3000/doc/>
## English ## English
@ -39,10 +57,29 @@ For more information, visit [Sutty's website](https://sutty.nl/en/).
### Development ### Development
Every task is run via `make`, please install GNU Make before developing.
To facilitate dependencies and dev environment, install
[haini.sh](https://0xacab.org/sutty/haini.sh)
Every task is run via `go-task`. [Installation
instructions](https://taskfile.dev/installation/).
```bash ```bash
make help go-task
``` ```
[Read the documentation](https://docs.sutty.nl/en/) ### Environment variables
Default env vars are store on `.env`. For local options, copy them to
`.env.local`.
### Documentation
To browse documentation using YARD:
```bash
go-task doc serve
```
And then open <https://panel.sutty.local:3000/doc/>

185
Taskfile.yaml Normal file
View file

@ -0,0 +1,185 @@
---
version: "3"
vars:
CURRENT_BRANCH:
sh: "git rev-parse --abbrev-ref HEAD"
shopt:
- "globstar"
dotenv:
- ".env.development"
- ".env"
- ".env.local"
- ".env.development.local"
tasks:
credentials:
desc: "Generate credentials file"
cmds:
- "cp --no-clobber config/credentials.yml.enc.ci config/credentials.yml.enc"
sources:
- "config/credentials.yml.enc.ci"
generates:
- "config/credentials.yml.enc"
gems:
desc: "Install gems"
deps:
- "credentials"
cmds:
- "{{.HAINISH}} bundle config set --local path './vendor'"
- "{{.HAINISH}} bundle install"
sources:
- "Gemfile"
generates:
- "Gemfile.lock"
status:
- "test -d vendor/ruby"
clean:
desc: "Clean"
cmds:
- "rm -rf _sites/test-* _deploy/test-* log/*.log tmp/cache tmp/letter_opener tmp/miniprofiler tmp/storage"
node-modules:
desc: "Install Node modules"
cmds:
- "{{.HAINISH}} yarn"
sources:
- "package.json"
- "yarn.lock"
status:
- "test -d node_modules"
assets:
desc: "Generate assets"
deps:
- "node-modules"
- "gems"
cmds:
- "git lfs fetch"
- "git lfs checkout"
- task: "rails"
vars:
CLI_ARGS: "webpacker:clobber RAILS_ENV=production"
- task: "rails"
vars:
CLI_ARGS: "assets:precompile RAILS_ENV=production"
- task: "rails"
vars:
CLI_ARGS: "assets:clean RAILS_ENV=production"
sources:
- "package.json"
- "yarn.lock"
- "app/assets/**/*"
- "app/javascript/**/*"
generates:
- "public/packs/manifest.json"
hosts:
desc: "Local DNS resolution for hostnames"
interactive: true
cmds:
- "echo -e \"127.0.0.1 panel.{{.SUTTY}} api.{{.SUTTY}} postgresql.{{.SUTTY}}\" | sudo tee -a /etc/hosts"
- "echo -e \"::1 panel.{{.SUTTY}} api.{{.SUTTY}} postgresql.{{.SUTTY}}\" | sudo tee -a /etc/hosts"
status:
- "grep -q \" panel.{{.SUTTY}} \" /etc/hosts"
database-init:
desc: "Database install"
cmds:
- "{{.HAINISH}} /usr/bin/initdb --locale en_US.utf8 -E UTF8 -D /var/lib/postgresql/{{.PGVER}}/data"
- "echo \"host all all samenet trust\" >> ../hain/var/lib/postgresql/{{.PGVER}}/data/pg_hba.conf"
- "echo \"listen_addresses = '*'\" >> ../hain/var/lib/postgresql/{{.PGVER}}/data/postgresql.conf"
- "echo \"external_pid_file = '{{.PGPID}}'\" >> ../hain/var/lib/postgresql/{{.PGVER}}/data/postgresql.conf"
- "install -dm755 ../hain/run/postgresql"
status:
- "test -d ../hain/var/lib/postgresql/{{.PGVER}}/data"
- "test -f ../hain/var/lib/postgresql/{{.PGVER}}/data/postgresql.conf"
database:
desc: "Database"
deps:
- "database-init"
cmds:
- "{{.HAINISH}} daemonize -c /var/lib/postgresql/{{.PGVER}}/data /usr/bin/postgres -D /var/lib/postgresql/{{.PGVER}}/data"
status:
- "test -f ../hain{{.PGPID}}"
- "pgrep -F ../hain{{.PGPID}}"
prepare:
desc: "Create database or run pending migrations"
deps:
- "database"
cmds:
- task: "rails"
vars:
CLI_ARGS: "db:prepare"
serve:
desc: "Run Rails development server"
deps:
- "prepare"
- "gems"
cmds:
- ": == Development server running at https://panel.{{.SUTTY_WITH_PORT}} =="
- task: "rails"
vars:
CLI_ARGS: "server"
status:
- "test -f tmp/pids/server.pid"
- "pgrep -F tmp/pids/server.pid"
yarn:
desc: "Yarn. Call with: go-task yarn -- arguments"
deps:
- "node-modules"
cmds:
- "{{.HAINISH}} yarn {{.CLI_ARGS}}"
- defer:
task: "notify"
bundle:
desc: "Bundle. Call with: go-task bundle -- arguments"
interactive: true
deps:
- "gems"
cmds:
- "{{.HAINISH}} bundle {{.CLI_ARGS}}"
- defer:
task: "notify"
rails:
desc: "Rails. Call with: go-task rails -- arguments"
cmds:
- task: "bundle"
vars:
CLI_ARGS: "exec rails {{.CLI_ARGS}}"
console:
desc: "Rails console"
interactive: true
cmds:
- task: "rails"
vars:
CLI_ARGS: "console"
doc:
desc: "Build documentation"
deps:
- "gems"
cmds:
- task: "bundle"
vars:
CLI_ARGS: "exec yardoc -o public/doc app lib config db"
gem-audit:
desc: "Audit Gem dependencies"
deps:
- "gems"
- "bundler-audit"
cmds:
- task: "bundle"
vars:
CLI_ARGS: "audit --update"
node-audit:
desc: "Audit Node dependencies"
deps:
- "node-modules"
cmds:
- task: "yarn"
vars:
CLI_ARGS: "audit"
notify:
internal: true
cmds:
- "echo -e \"\a\""
bundler-audit:
internal: true
cmds:
- "{{.HAINISH}} gem install bundler-audit"
status:
- "test -f ../hain/usr/bin/bundler-audit"

View file

@ -196,7 +196,7 @@ fieldset {
&[type=button] { &[type=button] {
@extend .btn; @extend .btn;
@extend .btn-info; @extend .btn-secondary;
@extend .m-0; @extend .m-0;
} }
} }
@ -210,10 +210,6 @@ svg {
} }
.btn { .btn {
background-color: var(--foreground);
color: var(--background);
border: none;
border-radius: 0;
margin-right: 0.3rem; margin-right: 0.3rem;
margin-bottom: 0.3rem; margin-bottom: 0.3rem;
@ -253,7 +249,7 @@ svg {
color: $magenta; color: $magenta;
} }
.btn { .btn-secondary {
background-color: $white; background-color: $white;
color: $black; color: $black;
border: none; border: none;

View file

@ -7,3 +7,22 @@ $cyan: #13fefe;
--background: #{$black}; --background: #{$black};
--color: #{$cyan}; --color: #{$cyan};
} }
.btn-secondary {
background-color: $white;
color: $black;
border: none;
&:hover {
color: $black;
background-color: $cyan;
}
&:active {
background-color: $cyan;
}
&:focus {
box-shadow: 0 0 0 0.2rem $cyan;
}
}

View file

@ -16,12 +16,14 @@ module ActiveStorage
end end
end end
rescue_from ActiveRecord::RecordNotFound, with: :page_not_found
# Asociar el archivo subido al sitio correspondiente. Cada sitio # Asociar el archivo subido al sitio correspondiente. Cada sitio
# tiene su propio servicio de subida de archivos. # tiene su propio servicio de subida de archivos.
def update def update
if (token = decode_verified_token) if (token = decode_verified_token)
if acceptable_content?(token) if acceptable_content?(token)
blob = ActiveStorage::Blob.find_by_key token[:key] blob = ActiveStorage::Blob.find_by_key! token[:key]
site = Site.find_by_name token[:service_name] site = Site.find_by_name token[:service_name]
if remote_file?(token) if remote_file?(token)
@ -59,6 +61,11 @@ module ActiveStorage
def remote_file?(token) def remote_file?(token)
token[:content_type] == 'sutty/download-from-url' token[:content_type] == 'sutty/download-from-url'
end end
def page_not_found(exception)
head :not_found
ExceptionNotifier.notify_exception(exception, data: {params: params.to_hash})
end
end end
end end
end end

View file

@ -68,7 +68,7 @@ module Api
# respuesta de error a plataformas # respuesta de error a plataformas
def platforms_answer(exception) def platforms_answer(exception)
ExceptionNotifier.notify_exception(exception, data: { headers: request.headers.to_h } ExceptionNotifier.notify_exception(exception, data: { headers: request.headers.to_h })
head :forbidden head :forbidden
end end

View file

@ -110,27 +110,6 @@ class SitesController < ApplicationController
redirect_to sites_path redirect_to sites_path
end end
# Obtiene y streamea archivos estáticos desde el repositorio mismo,
# pero sólo los públicos (es decir los archivos subidos desde Sutty).
def static_file
authorize site
file = params.require(:file) + '.' + params.require(:format)
raise ActionController::RoutingError.new(nil, nil) unless file.start_with? 'public/'
path = site.relative_path file
raise ActionController::RoutingError.new(nil, nil) unless File.exist? path
# TODO: Hacer esto usa recursos, pero menos que generar el sitio
# cada vez. Para poder usar X-Accel tendríamos que montar los
# repositorios en el servidor web, cosa que no queremos, o hacer
# links simbólicos desde todos los public, o usar un servidor web
# local que soporte sendfile mejor que Rails (nghttpd?)
send_file path
end
private private
def site def site

View file

@ -96,7 +96,7 @@ module ActiveStorage
end end
def blob_for(key) def blob_for(key)
ActiveStorage::Blob.find_by(key: key, service_name: name) ActiveStorage::Blob.find_by!(key: key, service_name: name)
end end
end end
end end

View file

@ -55,20 +55,9 @@ class Deploy < ApplicationRecord
site.path site.path
end end
# Un entorno que solo tiene lo que necesitamos # XXX: Ver DeployLocal#bundle
# def gems_dir
# @return [Hash] @gems_dir ||= Rails.root.join('_storage', 'gems', site.name)
def env
# XXX: This doesn't support Windows paths :B
paths = [File.dirname(`which bundle`), '/usr/local/bin', '/usr/bin', '/bin']
# Las variables de entorno extra no pueden superponerse al local.
extra_env.merge({
'HOME' => home_dir,
'PATH' => paths.join(':'),
'JEKYLL_ENV' => Rails.env,
'LANG' => ENV['LANG'],
})
end end
# Un entorno que solo tiene lo que necesitamos # Un entorno que solo tiene lo que necesitamos

View file

@ -7,11 +7,6 @@ class DeployLocal < Deploy
before_destroy :remove_destination! before_destroy :remove_destination!
def git_lfs(output: false)
run %(git lfs fetch), output: output
run %(git lfs checkout), output: output
end
def bundle(output: false) def bundle(output: false)
run %(bundle config set --local clean 'true'), output: output run %(bundle config set --local clean 'true'), output: output
run %(bundle config set --local deployment 'true'), output: output if site.gemfile_lock_path? run %(bundle config set --local deployment 'true'), output: output if site.gemfile_lock_path?
@ -21,6 +16,11 @@ class DeployLocal < Deploy
run %(bundle install), output: output run %(bundle install), output: output
end end
def git_lfs(output: false)
run %(git lfs fetch), output: output
run %(git lfs checkout), output: output
end
# Realizamos la construcción del sitio usando Jekyll y un entorno # Realizamos la construcción del sitio usando Jekyll y un entorno
# limpio para no pasarle secretos # limpio para no pasarle secretos
# #

View file

@ -107,8 +107,10 @@ class Post
src = element.attributes['src'] src = element.attributes['src']
next unless src&.value&.start_with? 'public/' next unless src&.value&.start_with? 'public/'
file = MetadataFile.new(site: site, post: self, document: document, layout: layout)
file.value['path'] = src.value
src.value = Rails.application.routes.url_helpers.site_static_file_url(site, file: src.value) src.value = Rails.application.routes.url_helpers.url_for(file.static_file)
end end
# Notificar a les usuaries que están viendo una previsualización # Notificar a les usuaries que están viendo una previsualización

View file

@ -158,19 +158,19 @@ class Site < ApplicationRecord
# Traer la ruta del sitio # Traer la ruta del sitio
def path def path
File.join(Site.site_path, name) ::File.join(Site.site_path, name)
end end
# La ruta anterior # La ruta anterior
def path_was def path_was
File.join(Site.site_path, name_was) ::File.join(Site.site_path, name_was)
end end
# Limpiar la ruta y unirla con el separador de directorios del # Limpiar la ruta y unirla con el separador de directorios del
# sistema operativo. Como si algún día fuera a cambiar o # sistema operativo. Como si algún día fuera a cambiar o
# soportáramos Windows :P # soportáramos Windows :P
def relative_path(suspicious_path) def relative_path(suspicious_path)
File.join(path, *suspicious_path.gsub('..', '/').gsub('./', '').squeeze('/').split('/')) ::File.join(path, *suspicious_path.gsub('..', '/').gsub('./', '').squeeze('/').split('/'))
end end
# Obtiene la lista de traducciones actuales # Obtiene la lista de traducciones actuales
@ -359,7 +359,7 @@ class Site < ApplicationRecord
end end
def jekyll? def jekyll?
File.directory? path ::File.directory? path
end end
def jekyll def jekyll
@ -377,7 +377,7 @@ class Site < ApplicationRecord
# documentos de Jekyll hacia Sutty para que podamos leer los datos que # documentos de Jekyll hacia Sutty para que podamos leer los datos que
# necesitamos. # necesitamos.
def load_jekyll def load_jekyll
return unless name.present? && File.directory?(path) return unless name.present? && ::File.directory?(path)
reload_jekyll! reload_jekyll!
end end
@ -405,7 +405,7 @@ class Site < ApplicationRecord
# metadatos de Document # metadatos de Document
@configuration = @configuration =
::Jekyll.configuration('source' => path, ::Jekyll.configuration('source' => path,
'destination' => File.join(path, '_site'), 'destination' => ::File.join(path, '_site'),
'safe' => true, 'watch' => false, 'safe' => true, 'watch' => false,
'quiet' => true, 'excerpt_separator' => '') 'quiet' => true, 'excerpt_separator' => '')
@ -430,7 +430,7 @@ class Site < ApplicationRecord
# El directorio donde se almacenan los sitios # El directorio donde se almacenan los sitios
def self.site_path def self.site_path
@site_path ||= File.realpath(ENV.fetch('SITE_PATH', Rails.root.join('_sites'))) @site_path ||= ::File.realpath(ENV.fetch('SITE_PATH', Rails.root.join('_sites')))
end end
def self.default def self.default
@ -461,7 +461,7 @@ class Site < ApplicationRecord
end end
def gemfile_lock_path? def gemfile_lock_path?
File.exist? gemfile_lock_path ::File.exist? gemfile_lock_path
end end
private private
@ -552,6 +552,12 @@ class Site < ApplicationRecord
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)
Site.one_at_a_time.synchronize do
Dir.chdir path, &block
end
end
# Instala las gemas cuando es necesario: # Instala las gemas cuando es necesario:
# #
# * El sitio existe # * El sitio existe
@ -567,6 +573,7 @@ class Site < ApplicationRecord
if !gems_installed? || gemfile_updated? || gemfile_lock_updated? if !gems_installed? || gemfile_updated? || gemfile_lock_updated?
deploy_local.bundle deploy_local.bundle
touch touch
::File.touch(gemfile_path)
end end
end end
@ -587,12 +594,16 @@ class Site < ApplicationRecord
# Detecta si el Gemfile fue modificado # Detecta si el Gemfile fue modificado
def gemfile_updated? def gemfile_updated?
updated_at < File.mtime(File.join(path, 'Gemfile')) updated_at < ::File.mtime(gemfile_path)
end
def gemfile_path
@gemfile_path ||= ::File.join(path, 'Gemfile')
end end
# @return [String] # @return [String]
def gemfile_lock_path def gemfile_lock_path
@gemfile_lock_path ||= File.join(path, 'Gemfile.lock') @gemfile_lock_path ||= ::File.join(path, 'Gemfile.lock')
end end
# Detecta si el Gemfile.lock fue modificado con respecto al sitio o al # Detecta si el Gemfile.lock fue modificado con respecto al sitio o al
@ -600,8 +611,8 @@ class Site < ApplicationRecord
def gemfile_lock_updated? def gemfile_lock_updated?
return false unless gemfile_lock_path? return false unless gemfile_lock_path?
[updated_at, File.mtime(File.join(path, 'Gemfile'))].any? do |compare| [updated_at, ::File.mtime(::File.join(path, 'Gemfile'))].any? do |compare|
compare < File.mtime(gemfile_lock_path) compare < ::File.mtime(gemfile_lock_path)
end end
end end
end end

View file

@ -21,7 +21,7 @@ class Site
# Leer el archivo de configuración y setear los atributos en el # Leer el archivo de configuración y setear los atributos en el
# objeto actual, creando los metodos de ostruct # objeto actual, creando los metodos de ostruct
def read def read
data = YAML.safe_load(File.read(path)) data = YAML.safe_load(File.read(path), permitted_classes: [Time])
@hash = data.hash @hash = data.hash
data.each do |key, value| data.each do |key, value|

View file

@ -16,7 +16,7 @@ class Site
# #
# @return [nil] # @return [nil]
def generate_private_key_pem! def generate_private_key_pem!
self.private_key_pem ||= DistributedPress::V1::Social::Client.new(public_key_url: nil, key_size: 2048).private_key.export self.private_key_pem ||= ::DistributedPress::V1::Social::Client.new(public_key_url: nil, key_size: 2048).private_key.export
end end
end end
end end

View file

@ -31,6 +31,7 @@ class CleanupService
site.deploys.find_each(&:cleanup!) site.deploys.find_each(&:cleanup!)
site.repository.gc site.repository.gc
lfs_cleanup
site.touch site.touch
end end
end end
@ -45,7 +46,14 @@ class CleanupService
Rails.logger.info "Limpiando repositorio git de #{site.name}" Rails.logger.info "Limpiando repositorio git de #{site.name}"
site.repository.gc site.repository.gc
lfs_cleanup
site.touch site.touch
end end
end end
private
def lfs_cleanup
site.repository.git_sh("git", "lfs", "prune")
site.repository.git_sh("git", "lfs", "dedup")
end
end end

View file

@ -25,4 +25,4 @@
class: 'form-control' class: 'form-control'
.form-group .form-group
= f.submit t('.submit'), class: 'btn btn-lg btn-block' = f.submit t('.submit'), class: 'btn btn-secondary btn-lg btn-block'

View file

@ -30,5 +30,5 @@
placeholder: t('activerecord.attributes.usuarie.email') placeholder: t('activerecord.attributes.usuarie.email')
.actions .actions
= f.submit t('.resend_confirmation_instructions'), = f.submit t('.resend_confirmation_instructions'),
class: 'btn btn-lg btn-block' class: 'btn btn-secondary btn-lg btn-block'
= render 'devise/shared/links' = render 'devise/shared/links'

View file

@ -32,4 +32,4 @@
placeholder: t('activerecord.attributes.usuarie.password') placeholder: t('activerecord.attributes.usuarie.password')
.actions .actions
= f.submit t('devise.invitations.edit.submit_button'), = f.submit t('devise.invitations.edit.submit_button'),
class: 'btn btn-lg btn-block' class: 'btn btn-secondary btn-lg btn-block'

View file

@ -16,4 +16,4 @@
= f.text_field field, class: 'form-control' = f.text_field field, class: 'form-control'
.actions .actions
= f.submit t('devise.invitations.new.submit_button'), = f.submit t('devise.invitations.new.submit_button'),
class: 'btn btn-lg btn-block' class: 'btn btn-secondary btn-lg btn-block'

View file

@ -39,6 +39,6 @@
.actions .actions
= f.submit t('.change_my_password'), = f.submit t('.change_my_password'),
class: 'btn btn-lg btn-block' class: 'btn btn-secondary btn-lg btn-block'
= render 'devise/shared/links' = render 'devise/shared/links'

View file

@ -20,5 +20,5 @@
placeholder: t('activerecord.attributes.usuarie.email') placeholder: t('activerecord.attributes.usuarie.email')
.actions .actions
= f.submit t('.send_me_reset_password_instructions'), = f.submit t('.send_me_reset_password_instructions'),
class: 'btn btn-lg btn-block' class: 'btn btn-secondary btn-lg btn-block'
= render 'devise/shared/links' = render 'devise/shared/links'

View file

@ -55,7 +55,7 @@
= t('.we_need_your_current_password_to_confirm_your_changes') = t('.we_need_your_current_password_to_confirm_your_changes')
.actions .actions
= f.submit t('.update'), = f.submit t('.update'),
class: 'btn btn-lg btn-block' class: 'btn btn-secondary btn-lg btn-block'
%hr/ %hr/
.sr-only .sr-only
@ -63,4 +63,4 @@
= button_to t('.cancel_my_account'), = button_to t('.cancel_my_account'),
registration_path(resource_name), registration_path(resource_name),
data: { confirm: t('.are_you_sure') }, data: { confirm: t('.are_you_sure') },
method: :delete, class: 'btn btn-block' method: :delete, class: 'btn btn-secondary btn-block'

View file

@ -56,6 +56,6 @@
.actions .actions
= f.submit t('.sign_up'), = f.submit t('.sign_up'),
class: 'btn btn-lg btn-block' class: 'btn btn-secondary btn-lg btn-block'
= render 'devise/shared/links' = render 'devise/shared/links'

View file

@ -32,5 +32,5 @@
= t('login.remember_me', remember_for: distance_of_time_in_words(Usuarie.remember_for)) = t('login.remember_me', remember_for: distance_of_time_in_words(Usuarie.remember_for))
.actions .actions
= f.submit t('.sign_in'), = f.submit t('.sign_in'),
class: 'btn btn-lg btn-block' class: 'btn btn-secondary btn-lg btn-block'
= render 'devise/shared/links' = render 'devise/shared/links'

View file

@ -4,12 +4,12 @@
- if controller_name != 'sessions' - if controller_name != 'sessions'
= link_to t('.sign_in'), new_session_path(resource_name, params: locale), = link_to t('.sign_in'), new_session_path(resource_name, params: locale),
class: 'btn btn-lg btn-block btn-success' class: 'btn btn-lg btn-block btn-secondary'
%br/ %br/
- if devise_mapping.registerable? && controller_name != 'registrations' - if devise_mapping.registerable? && controller_name != 'registrations'
= link_to t('.sign_up'), new_registration_path(resource_name, params: locale), = link_to t('.sign_up'), new_registration_path(resource_name, params: locale),
class: 'btn btn-lg btn-block btn-success' class: 'btn btn-lg btn-block btn-secondary'
%br/ %br/
- if devise_mapping.recoverable? - if devise_mapping.recoverable?

View file

@ -20,5 +20,5 @@
placeholder: t('activerecord.attributes.usuarie.email') placeholder: t('activerecord.attributes.usuarie.email')
.actions .actions
= f.submit t('.resend_unlock_instructions'), = f.submit t('.resend_unlock_instructions'),
class: 'btn btn-lg btn-block' class: 'btn btn-secondary btn-lg btn-block'
= render 'devise/shared/links' = render 'devise/shared/links'

View file

@ -11,7 +11,7 @@
= select_tag 'to', = select_tag 'to',
options_for_select(@options, @lang_to), options_for_select(@options, @lang_to),
class: 'form-control' class: 'form-control'
= submit_tag t('i18n.translate'), class: 'btn', name: nil = submit_tag t('i18n.translate'), class: 'btn btn-secondary', name: nil
- else - else
= t('i18n.translating.from') = t('i18n.translating.from')
= select_tag 'from', = select_tag 'from',
@ -21,7 +21,7 @@
= select_tag 'to', = select_tag 'to',
options_for_select(@options, @lang_to), options_for_select(@options, @lang_to),
class: 'form-control' class: 'form-control'
= submit_tag t('i18n.change'), class: 'btn', name: nil = submit_tag t('i18n.change'), class: 'btn btn-secondary', name: nil
= render 'layouts/help', help: t('help.i18n.index') = render 'layouts/help', help: t('help.i18n.index')
@ -33,16 +33,16 @@
= hidden_field 'i18n', 'lang_to', value: @lang_to = hidden_field 'i18n', 'lang_to', value: @lang_to
.form-group .form-group
.dropdown.inline .dropdown.inline
%button.btn.dropdown-toggle{type: 'button', %button.btn.btn-secondary.dropdown-toggle{type: 'button',
data: { toggle: 'dropdown' }, data: { toggle: 'dropdown' },
aria: { haspopup: 'true', expanded: 'false' }} aria: { haspopup: 'true', expanded: 'false' }}
= t('i18n.jump') = t('i18n.jump')
.dropdown-menu{aria: { labelledby: t('i18n.jump') }} .dropdown-menu{aria: { labelledby: t('i18n.jump') }}
- @site.data.dig(@lang_from).each_pair do |section, content| - @site.data.dig(@lang_from).each_pair do |section, content|
%a.dropdown-item{href: "##{section}"}= t("help.i18n.#{section}") %a.dropdown-item{href: "##{section}"}= t("help.i18n.#{section}")
= submit_tag t('i18n.save'), class: 'btn' = submit_tag t('i18n.save'), class: 'btn btn-secondary'
= render 'i18n/recursive', data: @site.data.dig(@lang_from), superkeys: [] = render 'i18n/recursive', data: @site.data.dig(@lang_from), superkeys: []
.form-group .form-group
= submit_tag t('i18n.save'), class: 'btn' = submit_tag t('i18n.save'), class: 'btn btn-secondary'

View file

@ -18,15 +18,15 @@
- if @site&.tienda? - if @site&.tienda?
%li.nav-item %li.nav-item
= link_to t('.tienda'), @site.tienda_url, = link_to t('.tienda'), @site.tienda_url,
role: 'button', class: 'btn' role: 'button', class: 'btn btn-secondary'
%li.nav-item %li.nav-item
= link_to t('.contact_us'), t('.contact_us_href'), = link_to t('.contact_us'), t('.contact_us_href'),
class: 'btn', rel: 'me', target: '_blank' class: 'btn btn-secondary', rel: 'me', target: '_blank'
%li.nav-item %li.nav-item
= link_to t('.logout'), main_app.destroy_usuarie_session_path, = link_to t('.logout'), main_app.destroy_usuarie_session_path,
method: :delete, role: 'button', class: 'btn' method: :delete, role: 'button', class: 'btn btn-secondary'
- else - else
- params.permit! - params.permit!
- I18n.available_locales.each do |locale| - I18n.available_locales.each do |locale|

View file

@ -1,2 +1,2 @@
= link_to text, link, class: 'btn', = link_to text, link, class: 'btn btn-secondary',
data: { toggle: 'tooltip' }, 'aria-role': 'button', title: tooltip data: { toggle: 'tooltip' }, 'aria-role': 'button', title: tooltip

View file

@ -1,7 +1,7 @@
- invalid_help = site.config.fetch('invalid_help', t('.invalid_help')) - invalid_help = site.config.fetch('invalid_help', t('.invalid_help'))
- sending_help = site.config.fetch('sending_help', t('.sending_help')) - sending_help = site.config.fetch('sending_help', t('.sending_help'))
.form-group .form-group
= submit_tag t('.save'), class: 'btn submit-post' = submit_tag t('.save'), class: 'btn btn-secondary submit-post'
= render 'bootstrap/alert', class: 'invalid-help d-none' do = render 'bootstrap/alert', class: 'invalid-help d-none' do
= invalid_help = invalid_help
= render 'bootstrap/alert', class: 'sending-help d-none' do = render 'bootstrap/alert', class: 'sending-help d-none' do

View file

@ -20,82 +20,82 @@
TODO: Eliminar todo el espacio en blanco para minificar HTML TODO: Eliminar todo el espacio en blanco para minificar HTML
.editor-toolbar{ style: 'z-index: 1' } .editor-toolbar{ style: 'z-index: 1' }
.editor-primary-toolbar.scrollbar-black .editor-primary-toolbar.scrollbar-black
%button.btn{ type: 'button', title: t('editor.multimedia'), data: { editor_button: 'multimedia' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.multimedia'), data: { editor_button: 'multimedia' } }>
%i.fa.fa-fw.fa-upload> %i.fa.fa-fw.fa-upload>
%span.sr-only>= t('editor.multimedia') %span.sr-only>= t('editor.multimedia')
%button.btn{ type: 'button', title: t('editor.bold'), data: { editor_button: 'mark-bold' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.bold'), data: { editor_button: 'mark-bold' } }>
%i.fa.fa-fw.fa-bold> %i.fa.fa-fw.fa-bold>
%span.sr-only>= t('editor.bold') %span.sr-only>= t('editor.bold')
%button.btn{ type: 'button', title: t('editor.italic'), data: { editor_button: 'mark-italic' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.italic'), data: { editor_button: 'mark-italic' } }>
%i.fa.fa-fw.fa-italic> %i.fa.fa-fw.fa-italic>
%span.sr-only>= t('editor.italic') %span.sr-only>= t('editor.italic')
%button.btn{ type: 'button', title: t('editor.mark'), data: { editor_button: 'mark-mark' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.mark'), data: { editor_button: 'mark-mark' } }>
%i.fa.fa-fw.fa-tint> %i.fa.fa-fw.fa-tint>
%span.sr-only>= t('editor.mark') %span.sr-only>= t('editor.mark')
%button.btn{ type: 'button', title: t('editor.link'), data: { editor_button: 'mark-link' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.link'), data: { editor_button: 'mark-link' } }>
%i.fa.fa-fw.fa-link> %i.fa.fa-fw.fa-link>
%span.sr-only>= t('editor.link') %span.sr-only>= t('editor.link')
%button.btn{ type: 'button', title: t('editor.deleted'), data: { editor_button: 'mark-deleted' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.deleted'), data: { editor_button: 'mark-deleted' } }>
%i.fa.fa-fw.fa-strikethrough> %i.fa.fa-fw.fa-strikethrough>
%span.sr-only>= t('editor.deleted') %span.sr-only>= t('editor.deleted')
%button.btn{ type: 'button', title: t('editor.underline'), data: { editor_button: 'mark-underline' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.underline'), data: { editor_button: 'mark-underline' } }>
%i.fa.fa-fw.fa-underline> %i.fa.fa-fw.fa-underline>
%span.sr-only>= t('editor.underline') %span.sr-only>= t('editor.underline')
%button.btn{ type: 'button', title: t('editor.super'), data: { editor_button: 'mark-super' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.super'), data: { editor_button: 'mark-super' } }>
%i.fa.fa-fw.fa-superscript> %i.fa.fa-fw.fa-superscript>
%span.sr-only>= t('editor.super') %span.sr-only>= t('editor.super')
%button.btn{ type: 'button', title: t('editor.sub'), data: { editor_button: 'mark-sub' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.sub'), data: { editor_button: 'mark-sub' } }>
%i.fa.fa-fw.fa-subscript> %i.fa.fa-fw.fa-subscript>
%span.sr-only>= t('editor.sub') %span.sr-only>= t('editor.sub')
%button.btn{ type: 'button', title: t('editor.small'), data: { editor_button: 'mark-small' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.small'), data: { editor_button: 'mark-small' } }>
%i.fa.fa-fw.fa-subscript> %i.fa.fa-fw.fa-subscript>
%span.sr-only>= t('editor.small') %span.sr-only>= t('editor.small')
%button.btn.mr-0{ type: 'button', title: t('editor.h1'), data: { editor_button: 'block-h1' } }> %button.btn.btn-secondary.mr-0{ type: 'button', title: t('editor.h1'), data: { editor_button: 'block-h1' } }>
%i.fa.fa-fw.fa-heading> %i.fa.fa-fw.fa-heading>
1 1
%span.sr-only>= t('editor.h1') %span.sr-only>= t('editor.h1')
%details.d-inline> %details.d-inline>
%summary.d-inline> %summary.d-inline>
%span.btn.ml-0{ role: 'button', title: t('editor.more') }> %span.btn.btn-secondary.ml-0{ role: 'button', title: t('editor.more') }>
%i.fa.fa-caret-right> %i.fa.fa-caret-right>
%span.sr-only= t('editor.more') %span.sr-only= t('editor.more')
.d-inline> .d-inline>
%button.btn{ type: 'button', title: t('editor.h2'), data: { editor_button: 'block-h2' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.h2'), data: { editor_button: 'block-h2' } }>
%i.fa.fa-fw.fa-heading> %i.fa.fa-fw.fa-heading>
2 2
%span.sr-only>= t('editor.h2') %span.sr-only>= t('editor.h2')
%button.btn{ type: 'button', title: t('editor.h3'), data: { editor_button: 'block-h3' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.h3'), data: { editor_button: 'block-h3' } }>
%i.fa.fa-fw.fa-heading> %i.fa.fa-fw.fa-heading>
3 3
%span.sr-only>= t('editor.h3') %span.sr-only>= t('editor.h3')
%button.btn{ type: 'button', title: t('editor.h4'), data: { editor_button: 'block-h4' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.h4'), data: { editor_button: 'block-h4' } }>
%i.fa.fa-fw.fa-heading> %i.fa.fa-fw.fa-heading>
4 4
%span.sr-only>= t('editor.h4') %span.sr-only>= t('editor.h4')
%button.btn{ type: 'button', title: t('editor.h5'), data: { editor_button: 'block-h5' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.h5'), data: { editor_button: 'block-h5' } }>
%i.fa.fa-fw.fa-heading> %i.fa.fa-fw.fa-heading>
5 5
%span.sr-only>= t('editor.h5') %span.sr-only>= t('editor.h5')
%button.btn{ type: 'button', title: t('editor.h6'), data: { editor_button: 'block-h6' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.h6'), data: { editor_button: 'block-h6' } }>
%i.fa.fa-fw.fa-heading> %i.fa.fa-fw.fa-heading>
6 6
%span.sr-only>= t('editor.h6') %span.sr-only>= t('editor.h6')
%button.btn{ type: 'button', title: t('editor.ul'), data: { editor_button: 'block-unordered_list' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.ul'), data: { editor_button: 'block-unordered_list' } }>
%i.fa.fa-fw.fa-list-ul> %i.fa.fa-fw.fa-list-ul>
%span.sr-only>= t('editor.ul') %span.sr-only>= t('editor.ul')
%button.btn{ type: 'button', title: t('editor.ol'), data: { editor_button: 'block-ordered_list' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.ol'), data: { editor_button: 'block-ordered_list' } }>
%i.fa.fa-fw.fa-list-ol> %i.fa.fa-fw.fa-list-ol>
%span.sr-only>= t('editor.ol') %span.sr-only>= t('editor.ol')
%button.btn{ type: 'button', title: t('editor.left'), data: { editor_button: 'parentBlock-left' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.left'), data: { editor_button: 'parentBlock-left' } }>
%i.fa.fa-fw.fa-align-left> %i.fa.fa-fw.fa-align-left>
%span.sr-only>= t('editor.left') %span.sr-only>= t('editor.left')
%button.btn{ type: 'button', title: t('editor.center'), data: { editor_button: 'parentBlock-center' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.center'), data: { editor_button: 'parentBlock-center' } }>
%i.fa.fa-fw.fa-align-center> %i.fa.fa-fw.fa-align-center>
%span.sr-only>= t('editor.center') %span.sr-only>= t('editor.center')
%button.btn{ type: 'button', title: t('editor.right'), data: { editor_button: 'parentBlock-right' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.right'), data: { editor_button: 'parentBlock-right' } }>
%i.fa.fa-fw.fa-align-right> %i.fa.fa-fw.fa-align-right>
%span.sr-only>= t('editor.right') %span.sr-only>= t('editor.right')
%button.btn{ type: 'button', title: t('editor.blockquote'), data: { editor_button: 'block-blockquote' } }> %button.btn.btn-secondary{ type: 'button', title: t('editor.blockquote'), data: { editor_button: 'block-blockquote' } }>
%i.fa.fa-fw.fa-quote-left> %i.fa.fa-fw.fa-quote-left>
%span.sr-only>= t('editor.blockquote') %span.sr-only>= t('editor.blockquote')
@ -116,8 +116,8 @@
%label{ for: 'multimedia-alt' }= t('editor.description') %label{ for: 'multimedia-alt' }= t('editor.description')
%input.form-control{ type: 'text', id: 'multimedia-alt', name: 'multimedia-alt' }/ %input.form-control{ type: 'text', id: 'multimedia-alt', name: 'multimedia-alt' }/
.form-group .form-group
%button.btn{ type: 'button', id: 'multimedia-file-upload', name: 'multimedia-file-upload' }= t('editor.multimedia-upload') %button.btn.btn-secondary{ type: 'button', id: 'multimedia-file-upload', name: 'multimedia-file-upload' }= t('editor.multimedia-upload')
%button.btn{ type: 'button', id: 'multimedia-remove', name: 'multimedia-remove' }= t('editor.multimedia-remove') %button.btn.btn-secondary{ type: 'button', id: 'multimedia-remove', name: 'multimedia-remove' }= t('editor.multimedia-remove')
.form-group{ data: { editor_auxiliary: 'link' } } .form-group{ data: { editor_auxiliary: 'link' } }
%label{ for: 'link-url' }= t('editor.url') %label{ for: 'link-url' }= t('editor.url')

View file

@ -22,13 +22,13 @@
= render 'schemas/row', site: @site, schema: schema, filter: @filter_params = render 'schemas/row', site: @site, schema: schema, filter: @filter_params
- if policy(@site_stat).index? - if policy(@site_stat).index?
= link_to t('stats.index.title'), site_stats_path(@site), class: 'btn' = link_to t('stats.index.title'), site_stats_path(@site), class: 'btn btn-secondary'
- if policy(@site).edit? - if policy(@site).edit?
= link_to t('sites.edit.btn', site: @site.title), edit_site_path(@site), class: 'btn' = link_to t('sites.edit.btn', site: @site.title), edit_site_path(@site), class: 'btn btn-secondary'
- if policy(@site).private? - if policy(@site).private?
= link_to t('sites.private'), '../private/' + @site.name, class: 'btn', target: '_blank', rel: 'noopener' = link_to t('sites.private'), '../private/' + @site.name, class: 'btn btn-secondary', target: '_blank', rel: 'noopener'
- if policy(SiteUsuarie.new(@site, current_usuarie)).index? - if policy(SiteUsuarie.new(@site, current_usuarie)).index?
= render 'layouts/btn_with_tooltip', = render 'layouts/btn_with_tooltip',
@ -40,9 +40,9 @@
- if @site.design.credits - if @site.design.credits
= render 'bootstrap/alert' do = render 'bootstrap/alert' do
= sanitize_markdown @site.design.credits = sanitize_markdown @site.design.credits
= link_to t('sites.donations.text'), t('sites.donations.url'), class: 'btn' = link_to t('sites.donations.text'), t('sites.donations.url'), class: 'btn btn-secondary'
- if @site.design.designer_url - if @site.design.designer_url
= link_to t('sites.designer_url'), @site.design.designer_url, class: 'btn' = link_to t('sites.designer_url'), @site.design.designer_url, class: 'btn btn-secondary'
%section.col %section.col
.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
@ -83,19 +83,19 @@
%div %div
- if reorder_allowed - if reorder_allowed
= submit_tag t('posts.reorder.submit'), class: 'btn' = submit_tag t('posts.reorder.submit'), class: 'btn'
%button.btn{ data: { action: 'reorder#unselect' } } %button.btn.btn-secondary{ data: { action: 'reorder#unselect' } }
= t('posts.reorder.unselect') = t('posts.reorder.unselect')
%span.badge{ data: { target: 'reorder.counter' } } 0 %span.badge{ data: { target: 'reorder.counter' } } 0
%button.btn{ data: { action: 'reorder#up' } }= t('posts.reorder.up') %button.btn.btn-secondary{ data: { action: 'reorder#up' } }= t('posts.reorder.up')
%button.btn{ data: { action: 'reorder#down' } }= t('posts.reorder.down') %button.btn.btn-secondary{ data: { action: 'reorder#down' } }= t('posts.reorder.down')
%button.btn{ data: { action: 'reorder#top' } }= t('posts.reorder.top') %button.btn.btn-secondary{ data: { action: 'reorder#top' } }= t('posts.reorder.top')
%button.btn{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom') %button.btn.btn-secondary{ data: { action: 'reorder#bottom' } }= t('posts.reorder.bottom')
%input{ type: 'hidden', name: 'post[lang]', value: @locale } %input{ type: 'hidden', name: 'post[lang]', value: @locale }
- if @site.pagination - if @site.pagination
%div %div
= link_to_prev_page @posts, t('posts.prev'), class: 'btn' = link_to_prev_page @posts, t('posts.prev'), class: 'btn btn-secondary'
= link_to_next_page @posts, t('posts.next'), class: 'btn' = link_to_next_page @posts, t('posts.next'), class: 'btn btn-secondary'
%tbody %tbody
- dir = @site.data.dig(params[:locale], 'dir') - dir = @site.data.dig(params[:locale], 'dir')
- size = @posts.size - size = @posts.size
@ -136,9 +136,9 @@
= post.order = post.order
%td.text-nowrap %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-secondary btn-block'
- if @usuarie || policy(post).destroy? - if @usuarie || policy(post).destroy?
= link_to t('posts.destroy'), site_post_path(@site, post.path), class: 'btn btn-block', method: :delete, data: { confirm: t('posts.confirm_destroy') } = link_to t('posts.destroy'), site_post_path(@site, post.path), class: 'btn btn-secondary btn-block', method: :delete, data: { confirm: t('posts.confirm_destroy') }
-# -#
Rescatar cualquier error en un post, notificarlo e Rescatar cualquier error en un post, notificarlo e
ignorar su renderización. ignorar su renderización.

View file

@ -4,7 +4,7 @@
%article.content.table-responsive-md %article.content.table-responsive-md
= link_to t('posts.edit'), = link_to t('posts.edit'),
edit_site_post_path(@site, @post.id), edit_site_post_path(@site, @post.id),
class: 'btn btn-block' class: 'btn btn-secondary btn-block'
%table.table.table-condensed %table.table.table-condensed
%thead %thead

View file

@ -3,7 +3,7 @@
method: :post, method: :post,
class: 'form-inline inline' do class: 'form-inline inline' do
= submit_tag site.enqueued? ? t('sites.enqueued') : t('sites.enqueue'), = submit_tag site.enqueued? ? t('sites.enqueued') : t('sites.enqueue'),
class: "btn no-border-radius #{local_assigns[:class]}", class: "btn btn-secondary #{local_assigns[:class]}",
title: site.enqueued? ? t('help.sites.enqueued') : t('help.sites.enqueue'), title: site.enqueued? ? t('help.sites.enqueued') : t('help.sites.enqueue'),
data: { disable_with: t('sites.enqueued') }, data: { disable_with: t('sites.enqueued') },
disabled: site.enqueued? disabled: site.enqueued?

View file

@ -72,10 +72,10 @@
.btn-group{ role: 'group', 'aria-label': t('.design.actions') } .btn-group{ role: 'group', 'aria-label': t('.design.actions') }
- if design.url - if design.url
= link_to t('.design.url'), design.url, = link_to t('.design.url'), design.url,
target: '_blank', class: 'btn' target: '_blank', class: 'btn btn-secondary'
- if design.license - if design.license
= link_to t('.design.license'), design.license, = link_to t('.design.license'), design.license,
target: '_blank', class: 'btn' target: '_blank', class: 'btn btn-secondary'
%hr/ %hr/
.form-group.licenses#license_id .form-group.licenses#license_id
@ -99,7 +99,7 @@
tags: %w[p a strong em ul ol li h1 h2 h3 h4 h5 h6] tags: %w[p a strong em ul ol li h1 h2 h3 h4 h5 h6]
- unless licencia.custom? - unless licencia.custom?
= link_to t('.licencia.url'), licencia.url, target: '_blank', class: 'btn', rel: 'noopener' = link_to t('.licencia.url'), licencia.url, target: '_blank', class: 'btn btn-secondary', rel: 'noopener'
%hr/ %hr/
@ -164,4 +164,4 @@
deploy: deploy_fields, site: site deploy: deploy_fields, site: site
.form-group .form-group
= f.submit submit, class: 'btn btn-lg btn-block' = f.submit submit, class: 'btn btn-secondary btn-lg btn-block'

View file

@ -27,4 +27,4 @@
.row.justify-content-center .row.justify-content-center
.col-12.col-lg-8 .col-12.col-lg-8
= link_to t('.merge.request'), site_pull_path(@site), = link_to t('.merge.request'), site_pull_path(@site),
method: 'post', class: 'btn btn-lg' method: 'post', class: 'btn btn-secondary btn-lg'

View file

@ -4,7 +4,7 @@
%p.lead= t('.help') %p.lead= t('.help')
- if policy(Site).new? - if policy(Site).new?
= link_to t('sites.new.title'), new_site_path, = link_to t('sites.new.title'), new_site_path,
class: 'btn' class: 'btn btn-secondary'
%section.col %section.col
- if @sites.empty? - if @sites.empty?
@ -29,18 +29,18 @@
= site.title = site.title
%p.lead= site.description %p.lead= site.description
%br %br
= link_to t('.visit'), site.url, class: 'btn' = link_to t('.visit'), site.url, class: 'btn btn-secondary'
- if rol.temporal - if rol.temporal
= button_to t('sites.invitations.accept'), = button_to t('sites.invitations.accept'),
site_usuaries_accept_invitation_path(site), site_usuaries_accept_invitation_path(site),
method: :patch, method: :patch,
title: t('help.sites.invitations.accept'), title: t('help.sites.invitations.accept'),
class: 'btn' class: 'btn btn-secondary'
= button_to t('sites.invitations.reject'), = button_to t('sites.invitations.reject'),
site_usuaries_reject_invitation_path(site), site_usuaries_reject_invitation_path(site),
method: :patch, method: :patch,
title: t('help.sites.invitations.reject'), title: t('help.sites.invitations.reject'),
class: 'btn' class: 'btn btn-secondary'
- else - else
- if policy(site).show? - if policy(site).show?
= render 'layouts/btn_with_tooltip', = render 'layouts/btn_with_tooltip',

View file

@ -11,11 +11,11 @@
%form.mb-5.form-inline{ method: 'get' } %form.mb-5.form-inline{ method: 'get' }
- Stat::INTERVALS.each do |interval| - Stat::INTERVALS.each do |interval|
= link_to t(".#{interval}"), site_stats_path(interval: interval, urls: params[:urls], period_start: params[:period_start].to_date.try(:"beginning_of_#{interval}").to_date, period_end: params[:period_end]), class: "mb-0 btn #{'btn-primary active' if @interval == interval}" = link_to t(".#{interval}"), site_stats_path(interval: interval, urls: params[:urls], period_start: params[:period_start].to_date.try(:"beginning_of_#{interval}").to_date, period_end: params[:period_end]), class: "mb-0 btn #{@interval == interval ? 'btn-primary active' : 'btn-secondary' }"
%input.form-control{ type: 'date', name: :period_start, value: params[:period_start] } %input.form-control{ type: 'date', name: :period_start, value: params[:period_start] }
%input.form-control{ type: 'date', name: :period_end, value: params[:period_end] } %input.form-control{ type: 'date', name: :period_end, value: params[:period_end] }
%button.btn.mb-0{ type: 'submit' }= t('.filter') %button.btn.btn-secondary.mb-0{ type: 'submit' }= t('.filter')
.mb-5 .mb-5
%h2= t('.host.title', count: @hostnames.size) %h2= t('.host.title', count: @hostnames.size)
@ -34,7 +34,7 @@
%textarea#urls.form-control{ name: 'urls', autocomplete: 'on', required: true, rows: @normalized_urls.size + 1, aria_describedby: 'help-urls' }= @normalized_urls.join("\n") %textarea#urls.form-control{ name: 'urls', autocomplete: 'on', required: true, rows: @normalized_urls.size + 1, aria_describedby: 'help-urls' }= @normalized_urls.join("\n")
%small#help-urls.feedback.form-text.text-muted= t('.urls.help') %small#help-urls.feedback.form-text.text-muted= t('.urls.help')
.form-group .form-group
%button.btn{ type: 'submit' }= t('.urls.submit') %button.btn.btn-secondary{ type: 'submit' }= t('.urls.submit')
- if @normalized_urls.present? - if @normalized_urls.present?
= line_chart site_stats_uris_path(urls: @normalized_urls, **@chart_params), **@chart_options = line_chart site_stats_uris_path(urls: @normalized_urls, **@chart_params), **@chart_options

View file

@ -9,13 +9,13 @@
- if @policy.invite? - if @policy.invite?
= link_to t('.invite'), = link_to t('.invite'),
site_usuaries_invite_path(@site, invite_as: u.to_s), site_usuaries_invite_path(@site, invite_as: u.to_s),
class: 'btn', class: 'btn btn-secondary',
data: { toggle: 'tooltip' }, data: { toggle: 'tooltip' },
title: t('.help.invite', invite_as: u.to_s) title: t('.help.invite', invite_as: u.to_s)
- if policy(Collaboration.new(@site)).collaborate? - if policy(Collaboration.new(@site)).collaborate?
= link_to t('.public_invite'), = link_to t('.public_invite'),
site_collaborate_path(@site), site_collaborate_path(@site),
class: 'btn', class: 'btn btn-secondary',
data: { toggle: 'tooltip' }, data: { toggle: 'tooltip' },
title: t('.help.public_invite') title: t('.help.public_invite')
%p.lead= t(".help.#{u}") %p.lead= t(".help.#{u}")
@ -38,7 +38,7 @@
- if @policy.demote? && @site.usuarie?(cuenta) - if @policy.demote? && @site.usuarie?(cuenta)
= link_to t('.demote.text'), = link_to t('.demote.text'),
site_usuarie_demote_path(@site, cuenta), site_usuarie_demote_path(@site, cuenta),
class: 'btn', class: 'btn btn-secondary',
data: { toggle: 'tooltip', data: { toggle: 'tooltip',
confirm: t('.demote.confirm') }, confirm: t('.demote.confirm') },
title: t('.help.demote'), title: t('.help.demote'),
@ -46,7 +46,7 @@
- if @policy.promote? && @site.invitade?(cuenta) - if @policy.promote? && @site.invitade?(cuenta)
= link_to t('.promote.text'), = link_to t('.promote.text'),
site_usuarie_promote_path(@site, cuenta), site_usuarie_promote_path(@site, cuenta),
class: 'btn', class: 'btn btn-secondary',
data: { toggle: 'tooltip', data: { toggle: 'tooltip',
confirm: t('.promote.confirm') }, confirm: t('.promote.confirm') },
title: t('.help.promote'), title: t('.help.promote'),
@ -54,7 +54,7 @@
- if @policy.destroy? - if @policy.destroy?
= link_to t('.destroy.text'), = link_to t('.destroy.text'),
site_usuarie_path(@site, cuenta), site_usuarie_path(@site, cuenta),
class: 'btn', class: 'btn btn-secondary',
data: { toggle: 'tooltip', data: { toggle: 'tooltip',
confirm: t('.destroy.confirm') }, confirm: t('.destroy.confirm') },
title: t('.help.destroy'), title: t('.help.destroy'),

View file

@ -13,4 +13,4 @@
invite_as: invite_as) invite_as: invite_as)
= f.text_area :invitaciones, class: 'form-control' = f.text_area :invitaciones, class: 'form-control'
.form-group .form-group
= f.submit t('.submit'), class: 'btn' = f.submit t('.submit'), class: 'btn btn-secondary'

8
bin/modified_files Executable file
View file

@ -0,0 +1,8 @@
#!/bin/sh
set -e
test -n "${CI_MERGE_REQUEST_DIFF_BASE_SHA}"
git diff --name-status ${CI_MERGE_REQUEST_DIFF_BASE_SHA} \
| grep -v "^D" \
| cut -f 2

4
bin/with_extension Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
grep "\.${1}$"
exit 0

View file

@ -21,6 +21,17 @@ require 'rails/test_unit/railtie'
# you've limited to :test, :development, or :production. # you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups) Bundler.require(*Rails.groups)
if %w[development test].include? ENV['RAILS_ENV']
# https://github.com/bkeepers/dotenv/pull/453
Dotenv::Railtie.class_eval do
def overload
Dotenv.overload(*dotenv_files.reverse)
end
end
Dotenv::Railtie.overload
end
module Sutty module Sutty
# Sutty! # Sutty!
class Application < Rails::Application class Application < Rails::Application

252
config/brakeman.ignore Normal file
View file

@ -0,0 +1,252 @@
{
"ignored_warnings": [
{
"warning_type": "Redirect",
"warning_code": 18,
"fingerprint": "0ae5c3990d49dfbfd4fd61874451f7a576d5056aca913068adf58c314625f810",
"check_name": "Redirect",
"message": "Possible unprotected redirect",
"file": "app/controllers/api/v1/posts_controller.rb",
"line": 20,
"link": "https://brakemanscanner.org/docs/warning_types/redirect/",
"code": "redirect_to((params[:redirect_to] or origin.to_s))",
"render_path": null,
"location": {
"type": "method",
"class": "Api::V1::PostsController",
"method": "create"
},
"user_input": "params[:redirect_to]",
"confidence": "High",
"cwe_id": [
601
],
"note": "https://0xacab.org/sutty/sutty/-/issues/14957"
},
{
"warning_type": "Denial of Service",
"warning_code": 76,
"fingerprint": "1947d1a2ae6e4bf718d0cc563e660efca96897165e9a8dd18186c1d7abe6ddf6",
"check_name": "RegexDoS",
"message": "Model attribute used in regular expression",
"file": "app/controllers/api/v1/base_controller.rb",
"line": 20,
"link": "https://brakemanscanner.org/docs/warning_types/denial_of_service/",
"code": "/\\.#{Site.domain}\\z/",
"render_path": null,
"location": {
"type": "method",
"class": "Api::V1::BaseController",
"method": "site_id"
},
"user_input": "Site.domain",
"confidence": "Medium",
"cwe_id": [
20,
185
],
"note": "No es un atributo, es una variable de entorno"
},
{
"warning_type": "Cross-Site Scripting",
"warning_code": 4,
"fingerprint": "28d98d08a15c4b3ad94a2cfa20a12573de12d99f1a30b3ca51074ee1f1886592",
"check_name": "LinkToHref",
"message": "Potentially unsafe model attribute in `link_to` href",
"file": "app/views/layouts/_breadcrumb.haml",
"line": 19,
"link": "https://brakemanscanner.org/docs/warning_types/link_to_href",
"code": "link_to(t(\".tienda\"), Site.find(params[:site_id]).tienda_url, :role => \"button\", :class => \"btn\")",
"render_path": [
{
"type": "controller",
"class": "Api::V1::NoticesController",
"method": "site",
"line": 31,
"file": "app/controllers/api/v1/notices_controller.rb",
"rendered": {
"name": "layouts/application",
"file": "app/views/layouts/application.html.haml"
}
},
{
"type": "template",
"name": "layouts/application",
"line": 25,
"file": "app/views/layouts/application.html.haml",
"rendered": {
"name": "layouts/_breadcrumb",
"file": "app/views/layouts/_breadcrumb.haml"
}
}
],
"location": {
"type": "template",
"template": "layouts/_breadcrumb"
},
"user_input": "Site.find(params[:site_id]).tienda_url",
"confidence": "Weak",
"cwe_id": [
79
],
"note": ""
},
{
"warning_type": "Redirect",
"warning_code": 18,
"fingerprint": "5034e51aaa1bac06d15fdde5956edffbfd65f94f5620a409526bbea896dc7b5f",
"check_name": "Redirect",
"message": "Possible unprotected redirect",
"file": "app/controllers/api/v1/contact_controller.rb",
"line": 26,
"link": "https://brakemanscanner.org/docs/warning_types/redirect/",
"code": "redirect_to((params[:redirect] or origin.to_s))",
"render_path": null,
"location": {
"type": "method",
"class": "Api::V1::ContactController",
"method": "receive"
},
"user_input": "params[:redirect]",
"confidence": "High",
"cwe_id": [
601
],
"note": "https://0xacab.org/sutty/sutty/-/issues/14957"
},
{
"warning_type": "Mass Assignment",
"warning_code": 70,
"fingerprint": "50582f39f8dfa900d3f2b5b9908b1592f8b8bd9e2d0b9d1cc05d77e5ede2d94e",
"check_name": "MassAssignment",
"message": "Specify exact keys allowed for mass assignment instead of using `permit!` which allows any keys",
"file": "app/views/layouts/_link_rel_alternate.haml",
"line": 2,
"link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
"code": "params.permit!",
"render_path": [
{
"type": "controller",
"class": "Api::V1::BaseController",
"method": "site_id",
"line": 20,
"file": "app/controllers/api/v1/base_controller.rb",
"rendered": {
"name": "layouts/application",
"file": "app/views/layouts/application.html.haml"
}
},
{
"type": "template",
"name": "layouts/application",
"line": 21,
"file": "app/views/layouts/application.html.haml",
"rendered": {
"name": "layouts/_link_rel_alternate",
"file": "app/views/layouts/_link_rel_alternate.haml"
}
}
],
"location": {
"type": "template",
"template": "layouts/_link_rel_alternate"
},
"user_input": null,
"confidence": "Medium",
"cwe_id": [
915
],
"note": "https://0xacab.org/sutty/sutty/-/issues/14958"
},
{
"warning_type": "Mass Assignment",
"warning_code": 70,
"fingerprint": "b8e0aa898288bebb614ccc1340d169caa196d315c6ac2e4744081cc892c2ae97",
"check_name": "MassAssignment",
"message": "Specify exact keys allowed for mass assignment instead of using `permit!` which allows any keys",
"file": "app/views/layouts/_breadcrumb.haml",
"line": 30,
"link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
"code": "params.permit!",
"render_path": [
{
"type": "controller",
"class": "Api::V1::BaseController",
"method": "site_id",
"line": 20,
"file": "app/controllers/api/v1/base_controller.rb",
"rendered": {
"name": "layouts/application",
"file": "app/views/layouts/application.html.haml"
}
},
{
"type": "template",
"name": "layouts/application",
"line": 25,
"file": "app/views/layouts/application.html.haml",
"rendered": {
"name": "layouts/_breadcrumb",
"file": "app/views/layouts/_breadcrumb.haml"
}
}
],
"location": {
"type": "template",
"template": "layouts/_breadcrumb"
},
"user_input": null,
"confidence": "Medium",
"cwe_id": [
915
],
"note": "https://0xacab.org/sutty/sutty/-/issues/14958"
},
{
"warning_type": "Cross-Site Scripting",
"warning_code": 4,
"fingerprint": "c051421c7cf4c2706b8e27bfd2f3b0661ec6a6df873da322a6b634b59e80351b",
"check_name": "LinkToHref",
"message": "Potentially unsafe model attribute in `link_to` href",
"file": "app/views/sites/_form.haml",
"line": 74,
"link": "https://brakemanscanner.org/docs/warning_types/link_to_href",
"code": "link_to(t(\".design.url\"), (Unresolved Model).new.url, :target => \"_blank\", :class => \"btn\")",
"render_path": [
{
"type": "controller",
"class": "SitesController",
"method": "new",
"line": 31,
"file": "app/controllers/sites_controller.rb",
"rendered": {
"name": "sites/new",
"file": "app/views/sites/new.haml"
}
},
{
"type": "template",
"name": "sites/new",
"line": 6,
"file": "app/views/sites/new.haml",
"rendered": {
"name": "sites/_form",
"file": "app/views/sites/_form.haml"
}
}
],
"location": {
"type": "template",
"template": "sites/_form"
},
"user_input": "(Unresolved Model).new.url",
"confidence": "Weak",
"cwe_id": [
79
],
"note": ""
}
],
"updated": "2024-01-11 18:12:14 -0300",
"brakeman_version": "5.4.1"
}

View file

@ -28,9 +28,6 @@ Rails.application.routes.draw do
# alias en nginx sin tener que usar expresiones regulares para # alias en nginx sin tener que usar expresiones regulares para
# detectar el nombre del sitio. # detectar el nombre del sitio.
get '/sites/private/:site_id(*file)', to: 'private#show', constraints: { site_id: %r{[^/]+} } get '/sites/private/:site_id(*file)', to: 'private#show', constraints: { site_id: %r{[^/]+} }
# Obtener archivos estáticos desde el directorio público
get '/sites/:site_id/static_file/(*file)', to: 'sites#static_file', as: 'site_static_file',
constraints: { site_id: %r{[^/]+} }
get '/env.js', to: 'env#index' get '/env.js', to: 'env#index'
match '/api/v3/projects/:site_id/notices' => 'api/v1/notices#create', via: %i[post] match '/api/v3/projects/:site_id/notices' => 'api/v1/notices#create', via: %i[post]

View file

@ -58,6 +58,35 @@ CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public;
COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions'; COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions';
--
-- Name: access_logs_before_insert_row_tr(); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.access_logs_before_insert_row_tr() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
new.created_at := to_timestamp(new.msec);
RETURN NEW;
END;
$$;
--
-- Name: indexed_posts_before_insert_update_row_tr(); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.indexed_posts_before_insert_update_row_tr() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
new.indexed_content := to_tsvector(('pg_catalog.' || new.dictionary)::regconfig, coalesce(new.title, '') || '
' || coalesce(new.content,''));
RETURN NEW;
END;
$$;
-- --
-- Name: que_validate_tags(jsonb); Type: FUNCTION; Schema: public; Owner: - -- Name: que_validate_tags(jsonb); Type: FUNCTION; Schema: public; Owner: -
-- --
@ -117,35 +146,6 @@ WITH (fillfactor='90');
COMMENT ON TABLE public.que_jobs IS '7'; COMMENT ON TABLE public.que_jobs IS '7';
--
-- Name: access_logs_before_insert_row_tr(); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.access_logs_before_insert_row_tr() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
new.created_at := to_timestamp(new.msec);
RETURN NEW;
END;
$$;
--
-- Name: indexed_posts_before_insert_update_row_tr(); Type: FUNCTION; Schema: public; Owner: -
--
CREATE FUNCTION public.indexed_posts_before_insert_update_row_tr() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
new.indexed_content := to_tsvector(('pg_catalog.' || new.dictionary)::regconfig, coalesce(new.title, '') || '
' || coalesce(new.content,''));
RETURN NEW;
END;
$$;
-- --
-- Name: que_determine_job_state(public.que_jobs); Type: FUNCTION; Schema: public; Owner: - -- Name: que_determine_job_state(public.que_jobs); Type: FUNCTION; Schema: public; Owner: -
-- --

View file

@ -19,10 +19,10 @@ check program stats
every "0 1 * * *" every "0 1 * * *"
if status != 0 then alert if status != 0 then alert
check process que with pidfile /srv/tmp/que.pid
start program = "/usr/bin/foreman run -f /srv/Procfile -d /srv que"
stop program = "/bin/sh -c 'cat /srv/tmp/que.pid | xargs -r kill'"
check filesystem root with path / check filesystem root with path /
if space usage > 80% for 5 times within 15 cycles then alert if space usage > 80% for 5 times within 15 cycles then alert
if space usage > 90% for 5 cycles then exec "/usr/bin/foreman run -f /srv/Procfile -d /srv emergency_cleanup" as uid "rails" gid "www-data" if space usage > 90% for 5 cycles then exec "/usr/bin/foreman run -f /srv/Procfile -d /srv emergency_cleanup" as uid "rails" gid "www-data"
check process que with pidfile /srv/tmp/que.pid
start program = "/usr/bin/foreman run -f /srv/Procfile -d /srv que"
stop program = "/bin/sh -c 'cat /srv/tmp/que.pid | xargs -r kill'"

View file

@ -12,8 +12,8 @@
"@rails/actiontext": "^6.0.0", "@rails/actiontext": "^6.0.0",
"@rails/activestorage": "^6.1.3-1", "@rails/activestorage": "^6.1.3-1",
"@rails/ujs": "^6.1.3-1", "@rails/ujs": "^6.1.3-1",
"@rails/webpacker": "5.2.1", "@rails/webpacker": "5.4.4",
"@suttyweb/editor": "^0.1.24", "@suttyweb/editor": "^0.1.25",
"babel-loader": "^8.2.2", "babel-loader": "^8.2.2",
"chart.js": "^3.5.1", "chart.js": "^3.5.1",
"chartkick": "^4.0.5", "chartkick": "^4.0.5",

Binary file not shown.

BIN
public/packs/css/application-1224e21e.css (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/packs/css/application-1224e21e.css.br (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/packs/css/application-1224e21e.css.gz (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/packs/css/application-7d15ae94.css (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/packs/css/application-bb1478c7.css (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/packs/js/application-886df482a133d55527f8.js (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/packs/js/application-886df482a133d55527f8.js.br (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/packs/js/application-886df482a133d55527f8.js.gz (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/packs/js/application-886df482a133d55527f8.js.map (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/packs/js/application-886df482a133d55527f8.js.map.br (Stored with Git LFS) Normal file

Binary file not shown.

BIN
public/packs/js/application-886df482a133d55527f8.js.map.gz (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show more