diff --git a/.mise.toml b/.mise.toml new file mode 100644 index 000000000..f0f6aada7 --- /dev/null +++ b/.mise.toml @@ -0,0 +1,2 @@ +[env] +PROMETHEUS_EXPORTER_URL = "http://127.0.0.1:9306/metrics" diff --git a/.ruby-version b/.ruby-version index 4f5e69734..2aa513199 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.4.5 +3.4.7 diff --git a/Dockerfile b/Dockerfile index f0bb65b55..73a2b3cbd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax = docker/dockerfile:1 -# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile -ARG RUBY_VERSION=3.4.5 +# Make sure RUBY_VERSION matches the Ruby version in .ruby-version +ARG RUBY_VERSION=3.4.7 FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim AS base # Rails app lives here @@ -19,7 +19,7 @@ FROM base AS build # Install packages needed to build gems RUN apt-get update -qq && \ - apt-get install -y --no-install-recommends -y build-essential pkg-config git libvips libyaml-dev && \ + apt-get install -y --no-install-recommends -y build-essential pkg-config git libvips libyaml-dev libssl-dev && \ rm -rf /var/lib/apt/lists /var/cache/apt/archives # Install application gems diff --git a/Gemfile.lock b/Gemfile.lock index 22ef0f15f..d55e8038d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -171,8 +171,8 @@ GEM ruby-next-core (~> 1.0) ast (2.4.3) aws-eventstream (1.4.0) - aws-partitions (1.1159.0) - aws-sdk-core (3.232.0) + aws-partitions (1.1170.0) + aws-sdk-core (3.233.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) @@ -180,10 +180,10 @@ GEM bigdecimal jmespath (~> 1, >= 1.6.1) logger - aws-sdk-kms (1.112.0) + aws-sdk-kms (1.113.0) aws-sdk-core (~> 3, >= 3.231.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.199.0) + aws-sdk-s3 (1.199.1) aws-sdk-core (~> 3, >= 3.231.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -193,7 +193,7 @@ GEM bcrypt (3.1.20) bcrypt_pbkdf (1.1.1) benchmark (0.4.1) - bigdecimal (3.2.3) + bigdecimal (3.3.0) bootsnap (1.18.6) msgpack (~> 1.2) brakeman (7.1.0) @@ -228,12 +228,12 @@ GEM drb (2.2.3) dry-initializer (3.2.0) ed25519 (1.4.0) - erb (5.0.2) + erb (5.0.3) erubi (1.13.1) - et-orbi (1.3.0) + et-orbi (1.4.0) tzinfo event_stream_parser (1.0.0) - faraday (2.13.4) + faraday (2.14.0) faraday-net_http (>= 2.0, < 3.5) json logger @@ -247,13 +247,14 @@ GEM ffi (1.17.2-arm64-darwin) ffi (1.17.2-x86_64-darwin) ffi (1.17.2-x86_64-linux-gnu) + ffi (1.17.2-x86_64-linux-musl) fugit (1.11.2) et-orbi (~> 1, >= 1.2.11) raabro (~> 1.4) geared_pagination (1.2.0) activesupport (>= 5.0) addressable (>= 2.5.0) - globalid (1.2.1) + globalid (1.3.0) activesupport (>= 6.1) hashdiff (1.2.1) i18n (1.14.7) @@ -274,7 +275,7 @@ GEM actionview (>= 7.0.0) activesupport (>= 7.0.0) jmespath (1.6.2) - json (2.13.2) + json (2.15.1) jwt (3.1.2) base64 kamal (2.7.0) @@ -307,13 +308,13 @@ GEM net-imap net-pop net-smtp - marcel (1.0.4) + marcel (1.1.0) matrix (0.4.3) mini_magick (5.3.1) logger mini_mime (1.1.5) mini_portile2 (2.8.9) - minitest (5.25.5) + minitest (5.26.0) mission_control-jobs (1.1.0) actioncable (>= 7.1) actionpack (>= 7.1) @@ -332,7 +333,7 @@ GEM uri net-http-persistent (4.0.6) connection_pool (~> 2.2, >= 2.2.4) - net-imap (0.5.10) + net-imap (0.5.12) date net-protocol net-pop (0.1.2) @@ -356,7 +357,9 @@ GEM racc (~> 1.4) nokogiri (1.18.10-x86_64-linux-gnu) racc (~> 1.4) - openssl (3.3.0) + nokogiri (1.18.10-x86_64-linux-musl) + racc (~> 1.4) + openssl (3.3.1) ostruct (0.6.3) parallel (1.27.0) parser (3.3.9.0) @@ -365,10 +368,10 @@ GEM platform_agent (1.0.1) activesupport (>= 5.2.0) useragent (~> 0.16.3) - pp (0.6.2) + pp (0.6.3) prettyprint prettyprint (0.2.0) - prism (1.5.1) + prism (1.4.0) prometheus-client-mmap (1.2.10) base64 bigdecimal @@ -389,6 +392,11 @@ GEM bigdecimal logger rb_sys (~> 0.9.109) + prometheus-client-mmap (1.2.10-x86_64-linux-musl) + base64 + bigdecimal + logger + rb_sys (~> 0.9.109) propshaft (1.3.1) actionpack (>= 7.0.0) activesupport (>= 7.0.0) @@ -397,11 +405,11 @@ GEM date stringio public_suffix (6.0.2) - puma (7.0.3) + puma (7.0.4) nio4r (~> 2.0) raabro (1.4.0) racc (1.8.1) - rack (3.2.1) + rack (3.2.2) rack-session (2.1.1) base64 (>= 0.1.0) rack (>= 3.0.0) @@ -421,20 +429,21 @@ GEM rake-compiler-dock (1.9.1) rb_sys (0.9.117) rake-compiler-dock (= 1.9.1) - rdoc (6.14.2) + rdoc (6.15.0) erb psych (>= 4.0.0) + tsort redcarpet (3.6.1) regexp_parser (2.11.3) reline (0.6.2) io-console (~> 0.5) rexml (3.4.4) - rouge (4.6.0) + rouge (4.6.1) rqrcode (3.1.0) chunky_png (~> 1.0) rqrcode_core (~> 2.0) rqrcode_core (2.0.0) - rubocop (1.80.2) + rubocop (1.81.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -442,17 +451,17 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.46.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.46.0) + rubocop-ast (1.47.1) parser (>= 3.3.7.2) prism (~> 1.4) rubocop-performance (1.26.0) lint_roller (~> 1.1) rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.44.0, < 2.0) - rubocop-rails (2.33.3) + rubocop-rails (2.33.4) activesupport (>= 4.2.0) lint_roller (~> 1.1) rack (>= 1.1) @@ -468,7 +477,7 @@ GEM ffi (~> 1.12) logger ruby2_keywords (0.0.5) - rubyzip (3.1.0) + rubyzip (3.1.1) securerandom (0.4.1) selenium-webdriver (4.35.0) base64 (~> 0.2) @@ -476,10 +485,10 @@ GEM rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 4.0) websocket (~> 1.0) - sentry-rails (5.27.0) + sentry-rails (5.28.0) railties (>= 5.0) - sentry-ruby (~> 5.27.0) - sentry-ruby (5.27.0) + sentry-ruby (~> 5.28.0) + sentry-ruby (5.28.0) bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) sniffer (0.5.0) @@ -511,6 +520,7 @@ GEM sqlite3 (2.7.4-arm64-darwin) sqlite3 (2.7.4-x86_64-darwin) sqlite3 (2.7.4-x86_64-linux-gnu) + sqlite3 (2.7.4-x86_64-linux-musl) sshkit (1.24.0) base64 logger @@ -531,6 +541,7 @@ GEM tiktoken_ruby (0.0.12-arm64-darwin) tiktoken_ruby (0.0.12-x86_64-darwin) tiktoken_ruby (0.0.12-x86_64-linux) + tiktoken_ruby (0.0.12-x86_64-linux-musl) timeout (0.4.3) tsort (0.2.0) turbo-rails (2.0.17) @@ -572,7 +583,7 @@ GEM yabeda-prometheus-mmap (0.4.0) prometheus-client-mmap yabeda (~> 0.10) - yabeda-puma-plugin (0.7.1) + yabeda-puma-plugin (0.8.0) json puma yabeda (~> 0.5) @@ -588,6 +599,8 @@ PLATFORMS arm64-linux x86_64-darwin x86_64-linux + x86_64-linux-gnu + x86_64-linux-musl x86_64-mingw32 DEPENDENCIES @@ -648,4 +661,4 @@ DEPENDENCIES yabeda-rails BUNDLED WITH - 2.7.0 + 2.7.2 diff --git a/app/assets/stylesheets/card-columns.css b/app/assets/stylesheets/card-columns.css index da138aac1..20674f264 100644 --- a/app/assets/stylesheets/card-columns.css +++ b/app/assets/stylesheets/card-columns.css @@ -2,7 +2,7 @@ /* Column container /* ------------------------------------------------------------------------ */ - .card-columns { + #main:has(.card-columns) { --bubble-size: 3.5rem; --cards-gap: min(1.2cqi, 1.7rem); --column-gap: 8px; @@ -13,18 +13,22 @@ --progress-max-cards: 20; --progress-max-height: 50dvh; - align-items: stretch; + padding-inline: var(--column-gap); + } + + .card-columns { container-type: inline-size; display: grid; gap: var(--column-gap); grid-template-columns: 1fr var(--column-width-expanded) 1fr; - justify-content: center; - margin: var(--cards-gap) auto 0; + margin-inline: auto; max-inline-size: var(--main-width); + overflow-x: auto; position: relative; + /* When it has something expanded */ &:has(.card-columns__left .cards:not(.is-collapsed), .card-columns__right .cards:not(.is-collapsed)) { - grid-template-columns: auto auto auto; + grid-template-columns: auto var(--column-width-expanded) auto; } &:has(.cards) { @@ -209,7 +213,7 @@ @media (any-hover: hover) { .btn--circle, ~ .btn--circle, - ~ .cards__expander-menu .btn--circle { + ~ .cards__expander-menu .btn--circle:not(label) { transition: opacity 300ms ease-in-out; opacity: 0; @@ -277,10 +281,6 @@ inline-size: var(--column-width-collapsed); } - .panel { - --panel-padding: 1ch; - } - .cards.is-collapsed & { display: none; } diff --git a/app/controllers/collections/columns_controller.rb b/app/controllers/collections/columns_controller.rb index 41ab1a56a..01f1704bf 100644 --- a/app/controllers/collections/columns_controller.rb +++ b/app/controllers/collections/columns_controller.rb @@ -26,6 +26,6 @@ class Collections::ColumnsController < ApplicationController end def column_params - params.require(:column).permit(:name) + params.require(:column).permit(:name, :color) end end diff --git a/app/helpers/cards_helper.rb b/app/helpers/cards_helper.rb index 03b0f3206..07a6b931f 100644 --- a/app/helpers/cards_helper.rb +++ b/app/helpers/cards_helper.rb @@ -35,6 +35,6 @@ module CardsHelper end def cacheable_preview_parts_for(card, *options) - [ card, card.collection, card.collection.entropy_configuration, card.collection.publication, *options ] + [ card, card.collection, card.collection.entropy_configuration, card.collection.publication, card.column&.color, *options ] end end diff --git a/app/models/card/cacheable.rb b/app/models/card/cacheable.rb index c0aac0dd5..a3047c88d 100644 --- a/app/models/card/cacheable.rb +++ b/app/models/card/cacheable.rb @@ -21,7 +21,7 @@ module Card::Cacheable end def for_preview(*other) - [ card, card.collection.entropy_configuration, card.collection.publication, *other ] + [ card, card.collection.entropy_configuration, card.collection.publication, card.column&.color, *other ] end end end diff --git a/app/views/active_storage/blobs/_blob.html.erb b/app/views/active_storage/blobs/_blob.html.erb index 6a35805e0..02a8b1997 100644 --- a/app/views/active_storage/blobs/_blob.html.erb +++ b/app/views/active_storage/blobs/_blob.html.erb @@ -18,9 +18,7 @@ <% else %>
- <%= link_to rails_blob_path(blob, disposition: :attachment), class: "attachment__figure", download: blob.filename, title: "Download #{blob.filename}" do %> - <%= render "active_storage/blobs/web/representation", blob: blob %> - <% end %> + <%= render "active_storage/blobs/web/representation", blob: blob %>
diff --git a/app/views/cards/_card.json.jbuilder b/app/views/cards/_card.json.jbuilder index 15926a921..8bb61330c 100644 --- a/app/views/cards/_card.json.jbuilder +++ b/app/views/cards/_card.json.jbuilder @@ -1,4 +1,4 @@ -json.cache! card do +json.cache! [ card, card.column&.color ] do json.(card, :id, :title, :status) json.image_url card.image.presence && url_for(card.image) diff --git a/app/views/collections/show/_columns.html.erb b/app/views/collections/show/_columns.html.erb index a290cf221..06862a158 100644 --- a/app/views/collections/show/_columns.html.erb +++ b/app/views/collections/show/_columns.html.erb @@ -8,7 +8,6 @@ dragenter->drag-and-strum#dragEnter drop->drag-and-drop#drop dragend->drag-and-drop#dragEnd" } do %> -
<%= render "collections/show/not_now", collection: collection %> diff --git a/app/views/collections/show/menu/_column.html.erb b/app/views/collections/show/menu/_column.html.erb index 156642b31..5ba6bde6a 100644 --- a/app/views/collections/show/menu/_column.html.erb +++ b/app/views/collections/show/menu/_column.html.erb @@ -7,8 +7,8 @@
diff --git a/app/views/collections/show/menu/_column_form.html.erb b/app/views/collections/show/menu/_column_form.html.erb index c98091d0b..0b4cb854c 100644 --- a/app/views/collections/show/menu/_column_form.html.erb +++ b/app/views/collections/show/menu/_column_form.html.erb @@ -1,5 +1,15 @@ <%= form_with model: [collection, column], data: { controller: "form", action: "turbo:submit-end->dialog#close turbo:submit-end->form#reset" } do |form| %> <%= form.text_field :name, class: "input", placeholder: "Column name", value: column.name, required: true, autocomplete: "off", pattern: ".*\\S.*", title: "Column name cannot be blank" %> +
+ <% Card::COLORS.each do |color| %> + + <% end %> +
+ <%= form.submit label, class: "btn btn--link" %> <% end %> diff --git a/app/views/public/collections/show/_stream.html.erb b/app/views/public/collections/show/_stream.html.erb index 6e045af5d..1d22d0c7b 100644 --- a/app/views/public/collections/show/_stream.html.erb +++ b/app/views/public/collections/show/_stream.html.erb @@ -2,6 +2,10 @@ data-action="turbo:before-morph-attribute->collapsible-columns#preventToggle">
+
+

The Stream

+
+ <% if page.used? %> <%= with_automatic_pagination "the-stream", @page do %> <%= render "public/collections/columns/list", cards: page.records %> diff --git a/bin/bundle b/bin/bundle deleted file mode 100755 index d318828c2..000000000 --- a/bin/bundle +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -# -# This file was generated by Bundler. -# -# The application 'bundle' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -require 'rubygems' - -m = Module.new do - module_function - - def invoked_as_script? - File.expand_path($0) == File.expand_path(__FILE__) - end - - def env_var_version - ENV['BUNDLER_VERSION'] - end - - def cli_arg_version - return unless invoked_as_script? # don't want to hijack other binstubs - return unless 'update'.start_with?(ARGV.first || ' ') # must be running `bundle update` - - bundler_version = nil - update_index = nil - ARGV.each_with_index do |a, i| - bundler_version = a if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) - next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ - - bundler_version = Regexp.last_match(1) - update_index = i - end - bundler_version - end - - def gemfile - gemfile = ENV['BUNDLE_GEMFILE'] - return gemfile if gemfile && !gemfile.empty? - - File.expand_path('../Gemfile', __dir__) - end - - def lockfile - lockfile = - case File.basename(gemfile) - when 'gems.rb' then gemfile.sub(/\.rb$/, '.locked') - else "#{gemfile}.lock" - end - File.expand_path(lockfile) - end - - def lockfile_version - return unless File.file?(lockfile) - - lockfile_contents = File.read(lockfile) - return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ - - Regexp.last_match(1) - end - - def bundler_requirement - @bundler_requirement ||= - env_var_version || - cli_arg_version || - bundler_requirement_for(lockfile_version) - end - - def bundler_requirement_for(version) - return "#{Gem::Requirement.default}.a" unless version - - bundler_gem_version = Gem::Version.new(version) - - bundler_gem_version.approximate_recommendation - end - - def load_bundler! - ENV['BUNDLE_GEMFILE'] ||= gemfile - - activate_bundler - end - - def activate_bundler - gem_error = activation_error_handling do - gem 'bundler', bundler_requirement - end - return if gem_error.nil? - - require_error = activation_error_handling do - require 'bundler/version' - end - if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) - return - end - - warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" - exit 42 - end - - def activation_error_handling - yield - nil - rescue StandardError, LoadError => e - e - end -end - -m.load_bundler! - -load Gem.bin_path('bundler', 'bundle') if m.invoked_as_script? diff --git a/bin/setup b/bin/setup index 6fff382dd..1abc9b0cc 100755 --- a/bin/setup +++ b/bin/setup @@ -1,26 +1,73 @@ -#!/usr/bin/env ruby +#!/usr/bin/env bash +set -eo pipefail -require "mkmf" -def system!(*args) system(*args, exception: true) end +# Prefer app executables +app_root="$(cd "$(dirname "$0")/.."; pwd)" +export PATH="$app_root/bin:$PATH" -puts "\n== Installing Ruby and Node ==" -system("mise install -y") +# Install gum if needed +if ! command -v gum &> /dev/null; then + echo + echo "▸ Installing gum" + if command -v pacman &> /dev/null; then + sudo pacman -S --noconfirm gum + elif command -v brew &> /dev/null; then + brew install gum + else + echo "Please install gum: https://github.com/charmbracelet/gum" + exit 1 + fi + echo +fi -puts "\n== Installing dependencies ==" -if MakeMakefile.find_executable0("pacman") - packages = "imagemagick openslide" - system("pacman -Q #{packages} || sudo pacman -S --noconfirm --needed #{packages}") -end -system("gem install bundler --conservative") -system("bundle config set --local auto_install true") -system("bundle check") || system!("bundle install") +step() { + local no_spin=false + if [[ "$1" == "--no-spin" ]]; then + no_spin=true + shift + fi -puts "\n== Preparing database ==" -if ARGV.include?("--reset") - system! "bin/rails db:drop db:create db:schema:load db:prepare db:seed" + local step_name="$1" + shift + + gum style --foreground 135 --bold "▸ $step_name" + gum style --foreground 240 "$*" + + if $no_spin; then + "$@" + else + gum spin --spinner dot --title "" --show-output -- "$@" + fi + + local exit_code=$? + echo + return $exit_code +} + +echo +gum style --foreground 153 " ˚ ∘ ∘ ˚ " +gum style --foreground 111 --bold " ∘˚˳°∘° 𝒻𝒾𝓏𝓏𝓎 °∘°˳˚∘ " +echo + +step --no-spin "Installing Ruby" mise install --yes +eval "$(mise hook-env)" + +if which pacman >/dev/null 2>&1; then + packages=(imagemagick openslide) + if ! pacman -Q "${packages[@]}" >/dev/null 2>&1; then + step "Installing packages" sudo pacman -S --noconfirm --needed "${packages[@]}" + fi +fi + +bundle config set --local auto_install true +step "Installing RubyGems" bundle install + +if [[ $* == *--reset* ]]; then + step "Resetting the database" rails db:reset else - system! "bin/rails db:prepare" -end + step "Preparing the database" rails db:prepare +fi -puts "\n== Removing old logs and tempfiles ==" -system! "bin/rails log:clear tmp:clear" +step "Cleaning up logs and tempfiles" rails log:clear tmp:clear + +gum style --foreground 46 "✓ Done (${SECONDS} sec)" diff --git a/config/puma.rb b/config/puma.rb index 0c4c0104e..65ff8b776 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -23,9 +23,9 @@ plugin :tmp_restart # Run Solid Queue with Puma plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"] -if ENV.fetch("PREFORK") { !Rails.env.local? } - # Expose prometheus metrics on port 9394 - activate_control_app - plugin :yabeda - plugin :yabeda_prometheus -end +# Expose Prometheus metrics at http://0.0.0.0:9394/metrics. +# In dev, overridden to http://127.0.0.1:9306/metrics in .mise.toml. +control_uri = Rails.env.local? ? "unix://tmp/pumactl.sock" : "auto" +activate_control_app control_uri, no_token: true +plugin :yabeda +plugin :yabeda_prometheus