its 7am i havent slept i have no idea
All checks were successful
CI (Gitea) / php-tests (push) Successful in 10m5s
CI (Gitea) / docker-image (push) Successful in 2m22s

This commit is contained in:
2025-11-28 06:40:10 +00:00
parent 336dcc4d3a
commit f77f3a9e40
34 changed files with 1142 additions and 183 deletions

View File

@@ -5,7 +5,7 @@
{% for msg in app.flashes('success') %}<div class="alert alert-success">{{ msg }}</div>{% endfor %}
{% for msg in app.flashes('info') %}<div class="alert alert-info">{{ msg }}</div>{% endfor %}
<div class="card">
<div class="card mb-4">
<div class="card-body">
{{ form_start(form, {attr: {class: 'vstack gap-3', novalidate: 'novalidate'}}) }}
<div>
@@ -34,6 +34,62 @@
{{ form_end(form) }}
</div>
</div>
<div class="card mb-4">
<div class="card-body">
<h2 class="h6 mb-3">Maintenance</h2>
<form method="post" action="{{ path('admin_settings_reset_catalog') }}" onsubmit="return confirm('Delete all albums, tracks, and reviews? Users remain untouched.');">
<input type="hidden" name="_token" value="{{ csrf_token('admin_settings_reset_catalog') }}">
<button class="btn btn-outline-danger" type="submit">Reset album & review data</button>
<div class="form-text mt-1">Deletes all albums (and tracks) plus reviews. Users and settings stay intact.</div>
</form>
</div>
</div>
<div class="card">
<div class="card-body">
<h2 class="h6 mb-3">Generate demo data</h2>
<div class="vstack gap-4">
{% for key, command in demoCommands %}
<form class="p-3 border rounded" method="post" action="{{ path('admin_settings_generate_demo', {type: key}) }}">
<div class="row gy-3 align-items-center">
<div class="col-12 col-xl-4">
<div class="fw-semibold">{{ command.label }}</div>
<div class="text-secondary small">{{ command.description }}</div>
<div class="text-secondary small">Command: <code>php bin/console {{ command.command }}</code></div>
</div>
<div class="col-12 col-xl-8">
<div class="row g-3 align-items-end">
{% for field in command.fields %}
{% if field.type != 'checkbox' %}
<div class="col-auto">
<label class="form-label small mb-1" for="{{ key }}_{{ field.name }}">{{ field.label }}</label>
<input class="form-control form-control-sm" id="{{ key }}_{{ field.name }}" type="{{ field.type }}" name="{{ field.name }}" placeholder="{{ field.placeholder|default('') }}" value="{{ field.default|default('') }}">
</div>
{% endif %}
{% endfor %}
</div>
<div class="d-flex flex-wrap gap-3 mt-3">
{% for field in command.fields %}
{% if field.type == 'checkbox' %}
<div class="form-check form-switch" style="min-width: 220px;">
<input class="form-check-input" type="checkbox" name="{{ field.name }}" id="{{ key }}_{{ field.name }}" {% if field.default is defined and field.default %}checked{% endif %}>
<label class="form-check-label" for="{{ key }}_{{ field.name }}">{{ field.label }}</label>
</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
<div class="text-end mt-3">
<input type="hidden" name="_token" value="{{ csrf_token('admin_settings_generate_' ~ key) }}">
<button class="btn btn-outline-primary btn-sm" type="submit">Run</button>
</div>
</form>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,7 +1,15 @@
{% extends 'base.html.twig' %}
{% block title %}Site Dashboard{% endblock %}
{% block body %}
<h1 class="h4 mb-3">Site dashboard</h1>
<div class="d-flex flex-wrap align-items-center gap-3 mb-3">
<h1 class="h4 mb-0">Site dashboard</h1>
<form method="post" action="{{ path('admin_dashboard_refresh_spotify') }}" class="ms-auto">
<input type="hidden" name="_token" value="{{ csrf_token('dashboard_refresh_spotify') }}">
<button class="btn btn-outline-success" type="submit">
Refresh Spotify metadata
</button>
</form>
</div>
<div class="row g-3 mb-4">
<div class="col-sm-4">

View File

@@ -5,6 +5,7 @@
{{ form_start(form, {attr: {class: 'vstack gap-3', novalidate: 'novalidate'}}) }}
<div>{{ form_label(form.name) }}{{ form_widget(form.name, {attr: {class: 'form-control'}}) }}{{ form_errors(form.name) }}</div>
<div>{{ form_label(form.artistsCsv) }}{{ form_widget(form.artistsCsv, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.genresCsv) }}{{ form_widget(form.genresCsv, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.releaseDate) }}{{ form_widget(form.releaseDate, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.totalTracks) }}{{ form_widget(form.totalTracks, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.coverUpload) }}{{ form_widget(form.coverUpload, {attr: {class: 'form-control'}}) }}</div>

View File

@@ -5,6 +5,7 @@
{{ form_start(form, {attr: {class: 'vstack gap-3', novalidate: 'novalidate'}}) }}
<div>{{ form_label(form.name) }}{{ form_widget(form.name, {attr: {class: 'form-control'}}) }}{{ form_errors(form.name) }}</div>
<div>{{ form_label(form.artistsCsv) }}{{ form_widget(form.artistsCsv, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.genresCsv) }}{{ form_widget(form.genresCsv, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.releaseDate) }}{{ form_widget(form.releaseDate, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.totalTracks) }}{{ form_widget(form.totalTracks, {attr: {class: 'form-control'}}) }}</div>
<div>{{ form_label(form.coverUpload) }}{{ form_widget(form.coverUpload, {attr: {class: 'form-control'}}) }}</div>

View File

@@ -6,9 +6,10 @@
{% set artist_value = artist|default('') %}
{% set year_from_value = year_from|default('') %}
{% set year_to_value = year_to|default('') %}
{% set genre_value = genre|default('') %}
{% set source_value = source|default('all') %}
{% set has_search = (query_value is not empty) or (album_value is not empty) or (artist_value is not empty) or (year_from_value is not empty) or (year_to_value is not empty) or (source_value != 'all') %}
{% set advanced_open = (album_value is not empty) or (artist_value is not empty) or (year_from_value is not empty) or (year_to_value is not empty) or (source_value != 'all') %}
{% set has_search = (query_value is not empty) or (album_value is not empty) or (artist_value is not empty) or (genre_value is not empty) or (year_from_value is not empty) or (year_to_value is not empty) or (source_value != 'all') %}
{% set advanced_open = (album_value is not empty) or (artist_value is not empty) or (genre_value is not empty) or (year_from_value is not empty) or (year_to_value is not empty) or (source_value != 'all') %}
{% set landing_view = not has_search %}
{% if landing_view %}
@@ -52,6 +53,9 @@
<div class="col-sm-4">
<input class="form-control" type="text" name="artist" value="{{ artist_value }}" placeholder="Artist" />
</div>
<div class="col-sm-4">
<input class="form-control" type="text" name="genre" value="{{ genre_value }}" placeholder="Genre" />
</div>
<div class="col-sm-2">
<input class="form-control" type="number" name="year_from" value="{{ year_from_value }}" placeholder="Year from" min="1900" max="2100" />
</div>
@@ -84,6 +88,9 @@
<div class="col-sm-4">
<input class="form-control" type="text" name="artist" value="{{ artist_value }}" placeholder="Artist" />
</div>
<div class="col-sm-4">
<input class="form-control" type="text" name="genre" value="{{ genre_value }}" placeholder="Genre" />
</div>
<div class="col-sm-2">
<input class="form-control" type="number" name="year_from" value="{{ year_from_value }}" placeholder="Year from" min="1900" max="2100" />
</div>
@@ -109,7 +116,12 @@
<div class="card-body d-flex flex-column">
<h5 class="card-title"><a href="{{ path('album_show', {id: album.id}) }}" class="text-decoration-none">{{ album.name }}</a></h5>
<p class="card-text text-secondary">{{ album.artists|map(a => a.name)|join(', ') }}</p>
<p class="card-text text-secondary">Released {{ album.release_date }}{{ album.total_tracks }} tracks</p>
<p class="card-text text-secondary">
Released {{ album.release_date }}{{ album.total_tracks }} tracks
{% if album.genres is defined and album.genres is not empty %}
<br><small>Genre: {{ album.genres|join(', ') }}</small>
{% endif %}
</p>
{% set s = stats[album.id] ?? { 'avg': 0, 'count': 0 } %}
<p class="card-text"><small class="text-secondary">User score: {{ s.avg }}/10 ({{ s.count }})</small></p>
<div class="mt-auto">
@@ -133,7 +145,7 @@
</div>
{% endfor %}
</div>
{% elseif query or album or artist or year_from or year_to %}
{% elseif query or album or artist or genre or year_from or year_to %}
<p>No albums found.</p>
{% endif %}
{% endblock %}

View File

@@ -24,6 +24,9 @@
</small>
{% endif %}
</p>
{% if album.genres is defined and album.genres is not empty %}
<p class="text-secondary mb-2">Genres: {{ album.genres|join(', ') }}</p>
{% endif %}
<p class="mb-2"><strong>User score:</strong> {{ avg }}/10 ({{ count }})</p>
{% if album.external_urls.spotify %}
<a class="btn btn-outline-success btn-sm" href="{{ album.external_urls.spotify }}" target="_blank" rel="noopener">Open in Spotify</a>