Files
warp_engine/app/admin/softwares.rb
T
mr.zeroandClaude Opus 5 19d142ef64 WarpEngine 0.7.0: a képtár és a CI is a hoszté, adapteren keresztül
Két dolog volt beépítve az engine-be, ami nem az övé.

A **képtár** eddig `WarpEngine::Image` volt, pedig a modell teljesen általános:
a teletypegames-ben a tagok arcképét is ez hordozza, nem csak a katalógus
borítóit. Az `Image` modell, a feltöltött fájlok, az `/api/image/:id` végpont
és az admin oldal ezért átkerült a hosztba, az engine pedig adapteren szól
hozzá (`WarpEngine::Images`): `url_for` adja a katalógus JSON `imageUrl`-jét,
`select_options` a software-form képválasztóját, `build_from_upload` a
"tölts fel új képet" ágat. Az alapértelmezés az `Image` osztály, tehát a
default útvonal bitre a régi. A `SoftwareImage` (a katalógus-kapcsolat)
maradt az engine-ben, és **az `images` tábla nem mozdult**: az engine csak
abbahagyta a létrehozását, a generátor írja meg hoszt-kódként.

A **CI** eddig végig Woodpecker volt: kliens, aláírás-ellenőrzés,
pipeline-receptek, repo-szinkron, secret-kiosztás. Mindez egy adapter mögé
került (`WarpEngine.ci`), a Woodpecker-implementáció pedig az engine-ben
maradt `WarpEngine::CI::Woodpecker` néven — kliens, adapter, httpsig-ellenőrző
és a platformonkénti pipeline-receptek, mert a YAML-dialektus a szolgáltatóé.
Az engine saját kódja már nem nevez szolgáltatót: `CI::Repo` és `CI::Run`
értékeket kap, `CI::ConnectionError`/`ApiError`/`NotConfigured` hibákat dob, a
`Pipeline` pedig `remote_repo_id`-t ad a történelmi `woodpecker_repo_id`
kolumna fölött (a tábla itt sem mozdult). `c.ci_adapter = :none` azt jelenti,
hogy ez a hoszt nem buildel: az `/api/ci/*` 503, a `/build/config` elutasít,
az admin akciók elbújnak.

Mindkét seam a hoszt initializerében van kimondva, nem alapértelmezésre
hagyva — a hoszt megnevezi, mi a képtára és mi a CI-ja.

Törés a 0.6-hoz képest: `image_container_path`, `image_owners`,
`ci_platforms`, `ci_update_server`, `ci_extension_public_key(_url)`,
`woodpecker_url`, `woodpecker_api_token`, `woodpecker_repo_owner` és a
`WarpEngine.woodpecker_configured?` megszűnt; a helyük `c.image_class_name` /
`c.image_adapter` és `c.ci_adapter`. Az `/api/ci/*` `latest_run`/`trigger`
válasza a normalizált `CI::Run` alakot adja (number, status, branch, message,
createdAt, url), a `pipelines` lista pedig `repo_id`-t is közöl a megtartott
`woodpecker_repo_id` mellett.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 00:56:01 +02:00

160 lines
5.7 KiB
Ruby

ActiveAdmin.register WarpEngine::Software, as: "Software" do
permit_params :name, :title, :author, :desc, :story,
:license, :platform, :status, :highlighted,
software_images_attributes: [ :id, :image_id, :is_default, :position, :file_upload, :_destroy ],
external_links_attributes: [ :id, :label, :url, :_destroy ],
releases_attributes: [ :id, :version, :_destroy,
{ release_assets_attributes: [ :id, :kind, :path, :_destroy ] } ]
menu parent: "🌀 WarpEngine", priority: 2, label: "🎮 Softwares"
actions :all, except: [ :edit ]
config.sort_order = "title_asc"
index do
selectable_column
id_column
column :name do |sw|
link_to sw.name, admin_software_path(sw)
end
column :title
column :platform
column :status do |sw|
status_tag sw.status
end
column :highlighted
column(:releases) { |sw| sw.releases.size }
column(:image) do |sw|
si = sw.software_images.detect(&:is_default?) || sw.software_images.first
if si&.image && WarpEngine::Images.available?(si.image)
image_tag WarpEngine::Images.url_for(si.image_id), style: "max-height:40px;max-width:80px;object-fit:contain;"
end
end
column :created_at
actions
end
filter :name
filter :title
filter :author
filter :platform, as: :select, collection: %w[tic80 ebitengine love c64 godot bevy phaser]
filter :status, as: :select, collection: %w[development demo released archived]
filter :highlighted
sidebar "Quick Links", only: :show, priority: 0 do
site_url = ENV.fetch("SITE_URL", "https://teletypegames.org")
catalog_url = "#{site_url}/catalog/#{resource.name}"
div style: "margin-bottom:8px;" do
a href: catalog_url, target: "_blank", style: "display:inline-flex;align-items:center;gap:6px;font-weight:bold;color:#5850ec;" do
span "🌐", style: "font-size:16px;"
text_node "View on site"
end
end
if resource.pipeline
div style: "margin-bottom:8px;" do
a href: admin_pipeline_path(resource.pipeline), style: "display:inline-flex;align-items:center;gap:6px;font-weight:bold;color:#5850ec;" do
span "🚀", style: "font-size:16px;"
text_node "Pipelines"
end
end
end
end
sidebar "Download Statistics", only: :show do
sw = resource
total = WarpEngine::Download.joins(:release).where(releases: { software_id: sw.id }).count
div style: "margin-bottom:12px;" do
strong "Total downloads: "
span total.to_s, style: "font-size:18px;font-weight:bold;"
end
sw.releases.each do |rel|
file_stats = WarpEngine::Download.where(release_id: rel.id)
.group(:file_path)
.select("file_path, COUNT(*) as dl_count, MAX(created_at) as last_dl")
.order("dl_count DESC")
rel_total = file_stats.sum(&:dl_count)
div style: "margin-bottom:16px;" do
h4 do
link_to "v#{rel.version}", admin_software_release_path(sw, rel)
span " (#{rel_total})", style: "color:#666;"
end
if file_stats.any?
table_for file_stats do
column("File") { |row| span row.file_path.split("/").last, style: "font-family:monospace;font-size:11px;" }
column("DL") { |row| row.dl_count }
column("Last") { |row| row.last_dl&.strftime("%m-%d") }
end
else
para "No downloads", style: "color:#999;font-style:italic;"
end
end
end
end
show title: proc { |sw| sw.title } do
active_admin_form_for [ :admin, resource ], url: admin_software_path(resource), html: { method: :put, multipart: true } do |f|
f.inputs "Details" do
f.input :name
f.input :title
f.input :author
f.input :platform, as: :select, collection: %w[tic80 ebitengine love c64 godot bevy phaser]
f.input :status, as: :select, collection: %w[development demo released archived]
f.input :highlighted
f.input :license
f.input :desc, as: :text, input_html: { rows: 4 }
f.input :story, as: :text, input_html: { rows: 8 }
end
f.inputs "Images" do
f.has_many :software_images, allow_destroy: true, new_record: true do |si|
img_hint = if si.object.image_id.present?
si.template.image_tag(WarpEngine::Images.url_for(si.object.image_id), style: "max-height:80px;max-width:160px;object-fit:contain;margin-top:6px;").html_safe
end
si.input :image_id, as: :select, label: "Image",
collection: WarpEngine::Images.select_options,
include_blank: "— select —",
hint: img_hint
si.input :file_upload, as: :file, label: "Upload new image"
si.input :is_default, as: :boolean
si.input :position, as: :number, input_html: { min: 0 }
end
end
f.inputs "External Links" do
f.has_many :external_links, allow_destroy: true, new_record: true do |el|
el.input :label
el.input :url
end
end
f.inputs "Releases" do
f.has_many :releases, allow_destroy: true, new_record: true do |r|
r.input :version
r.has_many :release_assets, allow_destroy: true, new_record: true do |ra|
ra.input :kind, as: :select, collection: WarpEngine::ReleaseAsset::KINDS, include_blank: false
ra.input :path
end
end
end
f.actions
end
end
controller do
def scoped_collection
super.includes({ releases: :release_assets }, software_images: :image)
end
def find_resource
scoped_collection.find(params[:id])
end
end
end