mirror of
https://github.com/wizarrrr/wizarr.git
synced 2026-08-02 16:27:28 -04:00
Migration Changes: - Renamed migration file from 20251004 to 20251005 - Updated down_revision from fd5a34530162 to 08a6c8fb44db - Ensures migration runs after upstream notification events migration - Fixes migration chain branching issue - Removed incorrect merge migration (1e83c67d9785) Migration chain is now: fd5a34530162 (disabled attribute) → 08a6c8fb44db (notification events) → 20251005_add_category_to_wizard_step (wizard category) Wizard Refactoring: - Enhanced uniqueness constraint for WizardStep from (server_type, position) to (server_type, category, position) - Ensures steps in different categories (pre_invite vs post_invite) can have the same position indexing without conflict - Updated wizard state management documentation to explicitly list and describe data-* attributes used in steps.html template Code Quality: - Applied djlint formatting to 8 template files to minimize rebase conflicts - Applied ruff format and ruff check --fix (no changes needed) - Removed redundant requirement comments from spec-driven development - Fixed case sensitivity in test assertion (POST vs post) - Updated test references from 20251004 to 20251005 to match corrected migration revision ID
227 lines
15 KiB
HTML
227 lines
15 KiB
HTML
{# templates/settings/wizard/steps.html #}
|
|
<div id="step-modal"></div>
|
|
<script>
|
|
// Clear modal when content is loaded/refreshed
|
|
document.getElementById('step-modal').innerHTML = '';
|
|
</script>
|
|
<section class="space-y-6">
|
|
{% set is_default = True %}
|
|
{% include 'settings/wizard/_sub_nav.html' with context %}
|
|
{% if not grouped %}<p class="text-gray-600 dark:text-gray-300">{{ _("No steps found.") }}</p>{% endif %}
|
|
{% for server, categories in grouped.items() %}
|
|
<div class="border rounded-md p-4 dark:border-gray-700">
|
|
{# Server header with actions #}
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h2 class="text-lg font-semibold text-gray-800 dark:text-gray-100">{{ server|capitalize }}</h2>
|
|
<div class="flex items-center gap-2">
|
|
<form method="post"
|
|
action="{{ url_for('wizard_admin.reset_server_steps', server_type=server) }}"
|
|
class="inline"
|
|
hx-post="{{ url_for('wizard_admin.reset_server_steps', server_type=server) }}"
|
|
hx-target="#tab-body"
|
|
hx-swap="innerHTML"
|
|
hx-confirm="{{ _('Are you sure you want to reset all steps for %(server)s to defaults? This will delete all custom steps and reimport the default ones.', server=server|capitalize) }}">
|
|
<button type="submit"
|
|
class="inline-flex items-center justify-center p-2 text-orange-600 rounded-lg hover:text-white hover:bg-orange-500 dark:text-orange-400 dark:hover:text-white dark:hover:bg-orange-600"
|
|
title="{{ _('Reset %(server)s Steps to Defaults', server=server|capitalize) }}">
|
|
<svg class="w-5 h-5"
|
|
aria-hidden="true"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24">
|
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.651 7.65a7.131 7.131 0 0 0-12.68 3.15M18.001 4v4h-4m-7.652 8.35a7.13 7.13 0 0 0 12.68-3.15M6 20v-4h4" />
|
|
</svg>
|
|
</button>
|
|
</form>
|
|
<a href="{{ url_for('wizard_admin.export_server_steps', server_type=server) }}"
|
|
class="inline-flex items-center justify-center p-2 text-gray-600 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-600"
|
|
title="{{ _('Export %(server)s Steps', server=server|capitalize) }}">
|
|
<svg class="w-5 h-5"
|
|
aria-hidden="true"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="currentColor"
|
|
viewBox="0 0 24 24">
|
|
<path fill-rule="evenodd" d="M9 7V2.221a2 2 0 0 0-.5.365L4.586 6.5a2 2 0 0 0-.365.5H9Zm2 0V2h7a2 2 0 0 1 2 2v9.293l-2-2a1 1 0 0 0-1.414 1.414l.293.293h-6.586a1 1 0 1 0 0 2h6.586l-.293.293A1 1 0 0 0 18 16.707l2-2V20a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V9h5a2 2 0 0 0 2-2Z" clip-rule="evenodd" />
|
|
</svg>
|
|
</a>
|
|
<a href="{{ url_for('wizard.step', server=server, idx=0) }}"
|
|
target="_blank"
|
|
class="inline-flex items-center justify-center p-2 text-gray-800 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:text-white dark:hover:bg-gray-600"
|
|
title="{{ _('Preview Wizard') }}">
|
|
<svg class="w-5 h-5"
|
|
aria-hidden="true"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24">
|
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 14v4.833A1.166 1.166 0 0 1 16.833 20H5.167A1.167 1.167 0 0 1 4 18.833V7.167A1.166 1.166 0 0 1 5.167 6h4.618m4.447-2H20v5.768m-7.889 2.121 7.778-7.778" />
|
|
</svg>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{# Two-column layout for pre-invite and post-invite steps #}
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{# Left column: Pre-Invite Steps #}
|
|
<div class="wizard-category-section border rounded-lg p-4 bg-gray-50 dark:bg-gray-900 dark:border-gray-600">
|
|
<h3 class="text-base font-semibold text-gray-800 dark:text-gray-100 mb-2">{{ _("Before Invite Acceptance") }}</h3>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
|
{{ _("Steps shown to users before they accept the invitation") }}
|
|
</p>
|
|
{% set pre_steps = categories.get('pre_invite', []) %}
|
|
{# Always render the wizard-steps container, even when empty, so it can be a Sortable.js drop target #}
|
|
<ol class="wizard-steps bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700
|
|
{% if not pre_steps %}min-h-[120px] flex items-center justify-center{% endif %}"
|
|
data-server="{{ server }}"
|
|
data-category="pre_invite"
|
|
data-reorder-url="{{ url_for('wizard_admin.reorder_steps') }}">
|
|
{% if pre_steps %}
|
|
{% for step in pre_steps %}
|
|
<li class="flex items-center justify-between px-4 py-3"
|
|
data-id="{{ step.id }}">
|
|
<svg class="drag w-4 h-4 cursor-grab text-gray-400 mr-3"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor">
|
|
<path fill-rule="evenodd" d="M7 4a1 1 0 112 0 1 1 0 01-2 0zm4 0a1 1 0 112 0 1 1 0 01-2 0zM7 9a1 1 0 112 0 1 1 0 01-2 0zm4 0a1 1 0 112 0 1 1 0 01-2 0zM7 14a1 1 0 112 0 1 1 0 01-2 0zm4 0a1 1 0 112 0 1 1 0 01-2 0z" clip-rule="evenodd" />
|
|
</svg>
|
|
<span class="flex-1 text-sm font-medium text-gray-900 dark:text-white">{{ step.title|render_jinja if step.title else _("Untitled") }}</span>
|
|
<span class="flex items-center gap-2">
|
|
<button hx-get="{{ url_for('wizard_admin.edit_step', step_id=step.id) }}"
|
|
hx-target="#step-modal"
|
|
hx-trigger="click"
|
|
class="inline-flex items-center justify-center p-2 text-gray-800 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:text-white dark:hover:bg-gray-600">
|
|
<svg class="w-5 h-5"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z">
|
|
</path>
|
|
</svg>
|
|
</button>
|
|
<form method="post"
|
|
action="{{ url_for('wizard_admin.delete_step', step_id=step.id) }}"
|
|
class="inline"
|
|
hx-post="{{ url_for('wizard_admin.delete_step', step_id=step.id) }}"
|
|
hx-target="#tab-body"
|
|
hx-swap="innerHTML"
|
|
hx-confirm="{{ _('Are you sure you want to delete this step?') }}">
|
|
<button type="submit"
|
|
class="inline-flex items-center justify-center p-2 text-red-500 rounded-lg hover:text-white hover:bg-red-500 dark:text-red-400 dark:hover:text-white dark:hover:bg-red-600">
|
|
<svg class="w-5 h-5"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
xmlns="http://www.w3.org/2000/svg">
|
|
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd">
|
|
</path>
|
|
</svg>
|
|
</button>
|
|
</form>
|
|
</span>
|
|
<svg class="drag w-4 h-4 cursor-grab text-gray-400 ml-3"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor">
|
|
<path fill-rule="evenodd" d="M7 4a1 1 0 112 0 1 1 0 01-2 0zm4 0a1 1 0 112 0 1 1 0 01-2 0zM7 9a1 1 0 112 0 1 1 0 01-2 0zm4 0a1 1 0 112 0 1 1 0 01-2 0zM7 14a1 1 0 112 0 1 1 0 01-2 0zm4 0a1 1 0 112 0 1 1 0 01-2 0z" clip-rule="evenodd" />
|
|
</svg>
|
|
</li>
|
|
{% endfor %}
|
|
{% else %}
|
|
{# Empty state message displayed inside the sortable container #}
|
|
<li class="empty-state-item flex flex-col items-center justify-center text-center py-8 text-gray-400 dark:text-gray-500 w-full list-none">
|
|
<svg class="w-12 h-12 mb-2 opacity-50"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
xmlns="http://www.w3.org/2000/svg">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">
|
|
</path>
|
|
</svg>
|
|
<p class="text-sm">{{ _("No pre-invite steps configured") }}</p>
|
|
</li>
|
|
{% endif %}
|
|
</ol>
|
|
</div>
|
|
{# Right column: Post-Invite Steps #}
|
|
<div class="wizard-category-section border rounded-lg p-4 bg-gray-50 dark:bg-gray-900 dark:border-gray-600">
|
|
<h3 class="text-base font-semibold text-gray-800 dark:text-gray-100 mb-2">{{ _("After Invite Acceptance") }}</h3>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
|
{{ _("Steps shown to users after they accept the invitation") }}
|
|
</p>
|
|
{% set post_steps = categories.get('post_invite', []) %}
|
|
{# Always render the wizard-steps container, even when empty, so it can be a Sortable.js drop target #}
|
|
<ol class="wizard-steps bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700
|
|
{% if not post_steps %}min-h-[120px] flex items-center justify-center{% endif %}"
|
|
data-server="{{ server }}"
|
|
data-category="post_invite"
|
|
data-reorder-url="{{ url_for('wizard_admin.reorder_steps') }}">
|
|
{% if post_steps %}
|
|
{% for step in post_steps %}
|
|
<li class="flex items-center justify-between px-4 py-3"
|
|
data-id="{{ step.id }}">
|
|
<svg class="drag w-4 h-4 cursor-grab text-gray-400 mr-3"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor">
|
|
<path fill-rule="evenodd" d="M7 4a1 1 0 112 0 1 1 0 01-2 0zm4 0a1 1 0 112 0 1 1 0 01-2 0zM7 9a1 1 0 112 0 1 1 0 01-2 0zm4 0a1 1 0 112 0 1 1 0 01-2 0zM7 14a1 1 0 112 0 1 1 0 01-2 0zm4 0a1 1 0 112 0 1 1 0 01-2 0z" clip-rule="evenodd" />
|
|
</svg>
|
|
<span class="flex-1 text-sm font-medium text-gray-900 dark:text-white">{{ step.title|render_jinja if step.title else _("Untitled") }}</span>
|
|
<span class="flex items-center gap-2">
|
|
<button hx-get="{{ url_for('wizard_admin.edit_step', step_id=step.id) }}"
|
|
hx-target="#step-modal"
|
|
hx-trigger="click"
|
|
class="inline-flex items-center justify-center p-2 text-gray-800 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:text-white dark:hover:text-white dark:hover:bg-gray-600">
|
|
<svg class="w-5 h-5"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z">
|
|
</path>
|
|
</svg>
|
|
</button>
|
|
<form method="post"
|
|
action="{{ url_for('wizard_admin.delete_step', step_id=step.id) }}"
|
|
class="inline"
|
|
hx-post="{{ url_for('wizard_admin.delete_step', step_id=step.id) }}"
|
|
hx-target="#tab-body"
|
|
hx-swap="innerHTML"
|
|
hx-confirm="{{ _('Are you sure you want to delete this step?') }}">
|
|
<button type="submit"
|
|
class="inline-flex items-center justify-center p-2 text-red-500 rounded-lg hover:text-white hover:bg-red-500 dark:text-red-400 dark:hover:text-white dark:hover:bg-red-600">
|
|
<svg class="w-5 h-5"
|
|
fill="currentColor"
|
|
viewBox="0 0 20 20"
|
|
xmlns="http://www.w3.org/2000/svg">
|
|
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd">
|
|
</path>
|
|
</svg>
|
|
</button>
|
|
</form>
|
|
</span>
|
|
<svg class="drag w-4 h-4 cursor-grab text-gray-400 ml-3"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor">
|
|
<path fill-rule="evenodd" d="M7 4a1 1 0 112 0 1 1 0 01-2 0zm4 0a1 1 0 112 0 1 1 0 01-2 0zM7 9a1 1 0 112 0 1 1 0 01-2 0zm4 0a1 1 0 112 0 1 1 0 01-2 0zM7 14a1 1 0 112 0 1 1 0 01-2 0zm4 0a1 1 0 112 0 1 1 0 01-2 0z" clip-rule="evenodd" />
|
|
</svg>
|
|
</li>
|
|
{% endfor %}
|
|
{% else %}
|
|
{# Empty state message displayed inside the sortable container #}
|
|
<li class="empty-state-item flex flex-col items-center justify-center text-center py-8 text-gray-400 dark:text-gray-500 w-full list-none">
|
|
<svg class="w-12 h-12 mb-2 opacity-50"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
xmlns="http://www.w3.org/2000/svg">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">
|
|
</path>
|
|
</svg>
|
|
<p class="text-sm">{{ _("No post-invite steps configured") }}</p>
|
|
</li>
|
|
{% endif %}
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</section>
|