fix: lint:

This commit is contained in:
Matthieu B
2025-10-23 19:55:07 +02:00
parent 6a3632ec66
commit 560a6f892e
4 changed files with 365 additions and 366 deletions

View File

@@ -3,109 +3,109 @@
role="dialog"
aria-modal="true"
aria-labelledby="modal-title">
<!-- Modal header -->
<div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">{{ _("Remove User") }}: {{ display_name }}</h3>
<button type="button"
onclick="closeModal()"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white">
<svg class="w-3 h-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
</svg>
</button>
<!-- Modal header -->
<div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">{{ _("Remove User") }}: {{ display_name }}</h3>
<button type="button"
onclick="closeModal()"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white">
<svg class="w-3 h-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
</svg>
</button>
</div>
<!-- Modal body -->
<div class="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4 dark:bg-gray-800">
<form hx-post="{{ url_for('admin.process_user_deletion') }}"
hx-include="this">
<!-- User info -->
<div class="mb-4 p-3 bg-gray-50 rounded-lg dark:bg-gray-600">
<div class="flex items-center space-x-3">
<div class="flex-shrink-0 w-10 h-10 bg-gray-200 dark:bg-gray-700 rounded-full flex items-center justify-center">
<span class="text-sm font-medium text-gray-600 dark:text-gray-300">{{ display_name[0]|upper }}</span>
</div>
<div>
<div class="font-medium text-gray-900 dark:text-white">{{ display_name }}</div>
<div class="text-sm text-gray-500 dark:text-gray-400">{{ user_email or _("Home User") }}</div>
</div>
</div>
<!-- Modal body -->
<div class="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4 dark:bg-gray-800">
<form hx-post="{{ url_for('admin.process_user_deletion') }}"
hx-include="this">
<!-- User info -->
<div class="mb-4 p-3 bg-gray-50 rounded-lg dark:bg-gray-600">
<div class="flex items-center space-x-3">
<div class="flex-shrink-0 w-10 h-10 bg-gray-200 dark:bg-gray-700 rounded-full flex items-center justify-center">
<span class="text-sm font-medium text-gray-600 dark:text-gray-300">{{ display_name[0]|upper }}</span>
</div>
<div>
<div class="font-medium text-gray-900 dark:text-white">{{ display_name }}</div>
<div class="text-sm text-gray-500 dark:text-gray-400">{{ user_email or _("Home User") }}</div>
</div>
<p class="mb-4 text-sm text-gray-500 dark:text-gray-400">{{ _("Select which servers to remove the user from:") }}</p>
<!-- Server list -->
<div class="space-y-2 mb-6">
{% for account in accounts %}
<label class="flex items-center p-3 border border-gray-200 rounded-lg hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700 cursor-pointer">
<input type="checkbox"
name="server_accounts"
value="{{ account.id }}"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600">
<div class="ml-3 flex-1">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-2">
<span class="text-sm font-medium text-gray-900 dark:text-white">
{% if account.server %}
{{ account.server.server_type|title }}
{% else %}
{{ _("Local") }}
{% endif %}
</span>
{% if account.server %}
<span class="text-xs px-2 py-1 bg-gray-100 text-gray-600 rounded dark:bg-gray-600 dark:text-gray-300">
{{ account.server.name }}
</span>
{% endif %}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">{{ _("User ID") }}: {{ account.id }}</div>
</div>
</div>
<p class="mb-4 text-sm text-gray-500 dark:text-gray-400">{{ _("Select which servers to remove the user from:") }}</p>
<!-- Server list -->
<div class="space-y-2 mb-6">
{% for account in accounts %}
<label class="flex items-center p-3 border border-gray-200 rounded-lg hover:bg-gray-50 dark:border-gray-600 dark:hover:bg-gray-700 cursor-pointer">
<input type="checkbox"
name="server_accounts"
value="{{ account.id }}"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600">
<div class="ml-3 flex-1">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-2">
<span class="text-sm font-medium text-gray-900 dark:text-white">
{% if account.server %}
{{ account.server.server_type|title }}
{% else %}
{{ _("Local") }}
{% endif %}
</span>
{% if account.server %}
<span class="text-xs px-2 py-1 bg-gray-100 text-gray-600 rounded dark:bg-gray-600 dark:text-gray-300">
{{ account.server.name }}
</span>
{% endif %}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">{{ _("User ID") }}: {{ account.id }}</div>
</div>
</div>
</label>
{% endfor %}
</label>
{% endfor %}
</div>
<!-- Select all checkbox -->
<div class="mb-6 p-3 bg-red-50 border border-red-200 rounded-lg dark:bg-red-900 dark:border-red-700">
<label class="flex items-center cursor-pointer">
<input type="checkbox"
id="select_all_servers"
class="w-4 h-4 text-red-600 bg-gray-100 border-gray-300 rounded focus:ring-red-500 dark:focus:ring-red-600 dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600">
<div class="ml-3">
<div class="text-sm font-medium text-red-800 dark:text-red-300">{{ _("Delete from ALL servers") }}</div>
<div class="text-xs text-red-700 dark:text-red-400">
{{ _("This will remove the user completely and cannot be undone") }}
</div>
<!-- Select all checkbox -->
<div class="mb-6 p-3 bg-red-50 border border-red-200 rounded-lg dark:bg-red-900 dark:border-red-700">
<label class="flex items-center cursor-pointer">
<input type="checkbox"
id="select_all_servers"
class="w-4 h-4 text-red-600 bg-gray-100 border-gray-300 rounded focus:ring-red-500 dark:focus:ring-red-600 dark:ring-offset-gray-800 dark:bg-gray-700 dark:border-gray-600">
<div class="ml-3">
<div class="text-sm font-medium text-red-800 dark:text-red-300">{{ _("Delete from ALL servers") }}</div>
<div class="text-xs text-red-700 dark:text-red-400">
{{ _("This will remove the user completely and cannot be undone") }}
</div>
</div>
</label>
</div>
<!-- Action buttons -->
<div class="flex items-center justify-end space-x-3">
<button type="button"
onclick="closeModal()"
class="px-4 py-2 text-sm font-medium text-gray-500 bg-gray-100 hover:bg-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-300 dark:hover:bg-gray-700">
{{ _("Cancel") }}
</button>
<button type="submit"
id="delete_submit_btn"
disabled
class="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 disabled:bg-gray-400 disabled:cursor-not-allowed rounded-lg">
<span id="delete-btn-text">{{ _("Remove Selected") }}</span>
<span id="delete-btn-loading" class="hidden">
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white inline"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
</path>
</svg>
{{ _("Deleting user...") }}
</span>
</button>
</div>
</form>
</div>
</div>
</label>
</div>
<!-- Action buttons -->
<div class="flex items-center justify-end space-x-3">
<button type="button"
onclick="closeModal()"
class="px-4 py-2 text-sm font-medium text-gray-500 bg-gray-100 hover:bg-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-300 dark:hover:bg-gray-700">
{{ _("Cancel") }}
</button>
<button type="submit"
id="delete_submit_btn"
disabled
class="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 disabled:bg-gray-400 disabled:cursor-not-allowed rounded-lg">
<span id="delete-btn-text">{{ _("Remove Selected") }}</span>
<span id="delete-btn-loading" class="hidden">
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white inline"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
</path>
</svg>
{{ _("Deleting user...") }}
</span>
</button>
</div>
</form>
</div>
</div>
<script>
// Initialize modal functionality immediately when content loads

View File

@@ -2,146 +2,146 @@
role="dialog"
aria-modal="true"
aria-labelledby="modal-title">
<div class="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4 dark:bg-gray-800">
<div class="flex justify-between items-center pb-3">
<h2 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
{{ _("User Details") }}
</h2>
<button type="button"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
onclick="document.getElementById('modal').classList.add('hidden')">
<svg class="w-3 h-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
</svg>
<span class="sr-only">Close modal</span>
</button>
<div class="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4 dark:bg-gray-800">
<div class="flex justify-between items-center pb-3">
<h2 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
{{ _("User Details") }}
</h2>
<button type="button"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
onclick="document.getElementById('modal').classList.add('hidden')">
<svg class="w-3 h-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
</svg>
<span class="sr-only">Close modal</span>
</button>
</div>
<!-- Header -->
<div class="flex items-center mb-4">
{% if user.photo %}
<img class="w-12 h-12 rounded-full mr-3"
src="{{ user.photo }}"
alt="{{ user.username }}">
{% else %}
<div class="w-12 h-12 rounded-full bg-gray-200 dark:bg-gray-600 flex items-center justify-center mr-3">
<svg class="w-6 h-6 text-gray-500 dark:text-gray-400"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path>
</svg>
</div>
{% endif %}
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">{{ user.username }}</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ user.email or 'Home User' }}</p>
</div>
</div>
<!-- Details body -->
<div class="space-y-6 text-sm text-gray-600 dark:text-gray-300">
<!-- Join date -->
<div class="flex items-center">
<svg class="w-4 h-4 mr-2"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1z">
</path>
</svg>
<span class="font-medium mr-1">{{ _("Joined") }}:</span>
{{ join_date|default(_('Unknown') ) if join_date else _('Unknown') }}
</div>
<!-- Account sections -->
{% for info in accounts_info %}
<div class="border border-gray-200 dark:border-gray-600 rounded-lg p-4">
<div class="flex items-center justify-between mb-2">
<span class="font-medium text-gray-900 dark:text-white">{{ _("Account Details") }}</span>
{{ info.server_type|server_name_tag(info.server_name) }}
</div>
<!-- Header -->
<div class="flex items-center mb-4">
{% if user.photo %}
<img class="w-12 h-12 rounded-full mr-3"
src="{{ user.photo }}"
alt="{{ user.username }}">
<p class="mb-3 text-gray-500 dark:text-gray-400">
<span class="font-medium">{{ _("Username") }}:</span> {{ info.username }}
</p>
<!-- Libraries -->
<div class="mb-4">
<p class="font-medium mb-2">{{ _("Libraries") }}:</p>
{% if info.libraries %}
<div class="flex flex-wrap gap-1">
{% for lib in info.libraries %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z" />
</svg>
{{ lib }}
</span>
{% endfor %}
</div>
{% else %}
<div class="w-12 h-12 rounded-full bg-gray-200 dark:bg-gray-600 flex items-center justify-center mr-3">
<svg class="w-6 h-6 text-gray-500 dark:text-gray-400"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</div>
{{ _("All libraries") }}
</span>
{% endif %}
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">{{ user.username }}</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ user.email or 'Home User' }}</p>
</div>
</div>
<!-- Details body -->
<div class="space-y-6 text-sm text-gray-600 dark:text-gray-300">
<!-- Join date -->
<div class="flex items-center">
<svg class="w-4 h-4 mr-2"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1z">
</path>
</svg>
<span class="font-medium mr-1">{{ _("Joined") }}:</span>
{{ join_date|default(_('Unknown') ) if join_date else _('Unknown') }}
<!-- Permissions -->
<div>
<p class="font-medium mb-2">{{ _("Permissions") }}:</p>
<div class="flex flex-wrap gap-1">
{% if info.is_admin %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 8a6 6 0 01-7.743 5.743L10 14l-0.257-0.257A6 6 0 1118 8zM2 8a6 6 0 1010.743 5.743L12 14l0.257-0.257A6 6 0 012 8zm8-2a2 2 0 100 4 2 2 0 000-4z" clip-rule="evenodd" />
</svg>
{{ _("Admin") }}
</span>
{% endif %}
{% if info.allow_downloads %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
{{ _("Downloads") }}
</span>
{% endif %}
{% if info.allow_live_tv %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M2 3a1 1 0 011-1h14a1 1 0 011 1v14a1 1 0 01-1 1H3a1 1 0 01-1-1V3zm2 2v10h12V5H4zm2 2h2v2H6V7zm6 0h2v2h-2V7zM6 11h2v2H6v-2zm6 0h2v2h-2v-2z" />
</svg>
{{ _("Live TV") }}
</span>
{% endif %}
{% if info.allow_camera_upload %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd" />
</svg>
{{ _("Camera Upload") }}
</span>
{% endif %}
{% if not info.is_admin and not info.allow_downloads and not info.allow_live_tv and not info.allow_camera_upload %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
</svg>
{{ _("Basic Access") }}
</span>
{% endif %}
</div>
<!-- Account sections -->
{% for info in accounts_info %}
<div class="border border-gray-200 dark:border-gray-600 rounded-lg p-4">
<div class="flex items-center justify-between mb-2">
<span class="font-medium text-gray-900 dark:text-white">{{ _("Account Details") }}</span>
{{ info.server_type|server_name_tag(info.server_name) }}
</div>
<p class="mb-3 text-gray-500 dark:text-gray-400">
<span class="font-medium">{{ _("Username") }}:</span> {{ info.username }}
</p>
<!-- Libraries -->
<div class="mb-4">
<p class="font-medium mb-2">{{ _("Libraries") }}:</p>
{% if info.libraries %}
<div class="flex flex-wrap gap-1">
{% for lib in info.libraries %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z" />
</svg>
{{ lib }}
</span>
{% endfor %}
</div>
{% else %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
{{ _("All libraries") }}
</span>
{% endif %}
</div>
<!-- Permissions -->
<div>
<p class="font-medium mb-2">{{ _("Permissions") }}:</p>
<div class="flex flex-wrap gap-1">
{% if info.is_admin %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 8a6 6 0 01-7.743 5.743L10 14l-0.257-0.257A6 6 0 1118 8zM2 8a6 6 0 1010.743 5.743L12 14l0.257-0.257A6 6 0 012 8zm8-2a2 2 0 100 4 2 2 0 000-4z" clip-rule="evenodd" />
</svg>
{{ _("Admin") }}
</span>
{% endif %}
{% if info.allow_downloads %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
{{ _("Downloads") }}
</span>
{% endif %}
{% if info.allow_live_tv %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M2 3a1 1 0 011-1h14a1 1 0 011 1v14a1 1 0 01-1 1H3a1 1 0 01-1-1V3zm2 2v10h12V5H4zm2 2h2v2H6V7zm6 0h2v2h-2V7zM6 11h2v2H6v-2zm6 0h2v2h-2v-2z" />
</svg>
{{ _("Live TV") }}
</span>
{% endif %}
{% if info.allow_camera_upload %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd" />
</svg>
{{ _("Camera Upload") }}
</span>
{% endif %}
{% if not info.is_admin and not info.allow_downloads and not info.allow_live_tv and not info.allow_camera_upload %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300">
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
</svg>
{{ _("Basic Access") }}
</span>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
<!-- Actions -->
<div class="px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6 dark:bg-gray-800 mt-6">
<button type="button"
class="inline-flex w-full justify-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-primary_hover sm:ml-3 sm:w-auto"
onclick="document.getElementById('modal').classList.add('hidden')">{{ _("Close") }}</button>
</div>
</div>
{% endfor %}
</div>
<!-- Actions -->
<div class="px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6 dark:bg-gray-800 mt-6">
<button type="button"
class="inline-flex w-full justify-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-primary_hover sm:ml-3 sm:w-auto"
onclick="document.getElementById('modal').classList.add('hidden')">{{ _("Close") }}</button>
</div>
</div>
</div>

View File

@@ -2,140 +2,140 @@
role="dialog"
aria-modal="true"
aria-labelledby="modal-title">
<div class="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4 dark:bg-gray-800">
<div class="flex justify-between items-center pb-3">
<h2 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
{{ _("Edit expiry") }} {{ user.username or user.email }}
</h2>
<button type="button"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
onclick="document.getElementById('modal').classList.add('hidden')">
<svg class="w-3 h-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
</svg>
<span class="sr-only">Close modal</span>
</button>
</div>
{% if user.identity_id and related_users and related_users|length > 1 %}
<!-- Multi-server user management -->
<div class="mb-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-4">{{ _("Server-specific Expiry Management") }}</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
{{ _("This user exists on multiple servers. You can set different expiry dates for each server.") }}
</p>
<div class="space-y-4">
{% for related_user in related_users %}
<div class="border border-gray-200 dark:border-gray-600 rounded-lg p-4">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center">
{% if related_user.server %}
{{ related_user.server.server_type|server_name_tag(related_user.server.name) }}
{% else %}
{{ 'local'|server_type_tag }}
{% endif %}
</div>
<span class="text-xs text-gray-500 dark:text-gray-400">ID: {{ related_user.id }}</span>
</div>
<form hx-post="{{ url_for('admin.user_detail', db_id=related_user.id) }}"
class="flex items-end gap-3">
<div class="flex-1">
<label for="expires_{{ related_user.id }}"
class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
{{ _("Expiry date") }}
</label>
<input id="expires_{{ related_user.id }}"
name="expires"
type="datetime-local"
value="{{ related_user.expires|default('', true) and related_user.expires.strftime('%Y-%m-%dT%H:%M') or '' }}"
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary focus:border-primary block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">
</div>
<button type="submit"
class="inline-flex items-center px-3 py-2.5 text-sm font-medium text-white bg-primary rounded-lg hover:bg-primary_hover focus:ring-4 focus:outline-none focus:ring-primary_focus dark:bg-primary dark:hover:bg-primary_hover dark:focus:ring-primary_focus">
{{ _("Update") }}
</button>
</form>
<div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
{% if related_user.expires %}
{{ _("Current expiry") }}: {{ related_user.expires|human_date }}
{% else %}
{{ _("No expiry set") }}
{% endif %}
</div>
</div>
{% endfor %}
</div>
<!-- Global Notes section for multi-server users -->
<div class="mt-6 pt-4 border-t border-gray-200 dark:border-gray-600">
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-2">{{ _("Notes") }}</h4>
<form hx-post="{{ url_for('admin.user_detail', db_id=user.id) }}"
class="space-y-3">
<div>
<textarea id="notes"
name="notes"
rows="3"
placeholder="{{ _('Add notes about this user...') }}"
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary focus:border-primary block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">{{ user.notes|default('', true) }}</textarea>
</div>
<div class="flex justify-end">
<button type="submit"
class="inline-flex items-center px-3 py-2 text-sm font-medium text-white bg-primary rounded-lg hover:bg-primary_hover focus:ring-4 focus:outline-none focus:ring-primary_focus dark:bg-primary dark:hover:bg-primary_hover dark:focus:ring-primary_focus">
{{ _("Update Notes") }}
</button>
</div>
</form>
</div>
</div>
{% else %}
<!-- Single server user management -->
<form hx-post="{{ url_for('admin.user_detail', db_id=user.id) }}"
class="space-y-4 md:space-y-6">
<div>
<label for="expires"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
{{ _("Expiry date (leave blank for never)") }}
</label>
<input id="expires"
name="expires"
type="datetime-local"
value="{{ user.expires|default('', true) and user.expires.strftime('%Y-%m-%dT%H:%M') or '' }}"
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary focus:border-primary block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">
</div>
<div>
<label for="notes"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{ _("Notes") }}</label>
<textarea id="notes"
name="notes"
rows="3"
placeholder="{{ _('Add notes about this user...') }}"
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary focus:border-primary block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">{{ user.notes|default('', true) }}</textarea>
</div>
<div class="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6 dark:bg-gray-800">
<button type="submit"
class="inline-flex w-full justify-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-primary_hover sm:ml-3 sm:w-auto">
{{ _("Save") }}
</button>
<button type="button"
class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-xs ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
onclick="document.getElementById('modal').classList.add('hidden')">{{ _("Cancel") }}</button>
</div>
</form>
{% endif %}
{% if invitation %}
<!-- Additional invitation context -->
<div class="mt-6 pt-4 border-t border-gray-200 dark:border-gray-600">
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-2">{{ _("Invitation Details") }}</h4>
<div class="text-xs text-gray-500 dark:text-gray-400 space-y-1">
<div>
{{ _("Code") }}: <span class="font-mono">{{ invitation.code }}</span>
<div class="bg-white px-4 pb-4 pt-5 sm:p-6 sm:pb-4 dark:bg-gray-800">
<div class="flex justify-between items-center pb-3">
<h2 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
{{ _("Edit expiry") }} {{ user.username or user.email }}
</h2>
<button type="button"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
onclick="document.getElementById('modal').classList.add('hidden')">
<svg class="w-3 h-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
</svg>
<span class="sr-only">Close modal</span>
</button>
</div>
{% if user.identity_id and related_users and related_users|length > 1 %}
<!-- Multi-server user management -->
<div class="mb-6">
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-4">{{ _("Server-specific Expiry Management") }}</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
{{ _("This user exists on multiple servers. You can set different expiry dates for each server.") }}
</p>
<div class="space-y-4">
{% for related_user in related_users %}
<div class="border border-gray-200 dark:border-gray-600 rounded-lg p-4">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center">
{% if related_user.server %}
{{ related_user.server.server_type|server_name_tag(related_user.server.name) }}
{% else %}
{{ 'local'|server_type_tag }}
{% endif %}
</div>
{% if invitation.duration %}<div>{{ _("Duration") }}: {{ invitation.duration }} {{ _("days") }}</div>{% endif %}
{% if invitation.expires %}<div>{{ _("Invitation expires") }}: {{ invitation.expires|human_date }}</div>{% endif %}
<span class="text-xs text-gray-500 dark:text-gray-400">ID: {{ related_user.id }}</span>
</div>
<form hx-post="{{ url_for('admin.user_detail', db_id=related_user.id) }}"
class="flex items-end gap-3">
<div class="flex-1">
<label for="expires_{{ related_user.id }}"
class="block mb-1 text-sm font-medium text-gray-700 dark:text-gray-300">
{{ _("Expiry date") }}
</label>
<input id="expires_{{ related_user.id }}"
name="expires"
type="datetime-local"
value="{{ related_user.expires|default('', true) and related_user.expires.strftime('%Y-%m-%dT%H:%M') or '' }}"
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary focus:border-primary block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">
</div>
<button type="submit"
class="inline-flex items-center px-3 py-2.5 text-sm font-medium text-white bg-primary rounded-lg hover:bg-primary_hover focus:ring-4 focus:outline-none focus:ring-primary_focus dark:bg-primary dark:hover:bg-primary_hover dark:focus:ring-primary_focus">
{{ _("Update") }}
</button>
</form>
<div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
{% if related_user.expires %}
{{ _("Current expiry") }}: {{ related_user.expires|human_date }}
{% else %}
{{ _("No expiry set") }}
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
</div>
<!-- Global Notes section for multi-server users -->
<div class="mt-6 pt-4 border-t border-gray-200 dark:border-gray-600">
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-2">{{ _("Notes") }}</h4>
<form hx-post="{{ url_for('admin.user_detail', db_id=user.id) }}"
class="space-y-3">
<div>
<textarea id="notes"
name="notes"
rows="3"
placeholder="{{ _('Add notes about this user...') }}"
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary focus:border-primary block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">{{ user.notes|default('', true) }}</textarea>
</div>
<div class="flex justify-end">
<button type="submit"
class="inline-flex items-center px-3 py-2 text-sm font-medium text-white bg-primary rounded-lg hover:bg-primary_hover focus:ring-4 focus:outline-none focus:ring-primary_focus dark:bg-primary dark:hover:bg-primary_hover dark:focus:ring-primary_focus">
{{ _("Update Notes") }}
</button>
</div>
</form>
</div>
</div>
{% else %}
<!-- Single server user management -->
<form hx-post="{{ url_for('admin.user_detail', db_id=user.id) }}"
class="space-y-4 md:space-y-6">
<div>
<label for="expires"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
{{ _("Expiry date (leave blank for never)") }}
</label>
<input id="expires"
name="expires"
type="datetime-local"
value="{{ user.expires|default('', true) and user.expires.strftime('%Y-%m-%dT%H:%M') or '' }}"
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary focus:border-primary block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">
</div>
<div>
<label for="notes"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{ _("Notes") }}</label>
<textarea id="notes"
name="notes"
rows="3"
placeholder="{{ _('Add notes about this user...') }}"
class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary focus:border-primary block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">{{ user.notes|default('', true) }}</textarea>
</div>
<div class="bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6 dark:bg-gray-800">
<button type="submit"
class="inline-flex w-full justify-center rounded-md bg-primary px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-primary_hover sm:ml-3 sm:w-auto">
{{ _("Save") }}
</button>
<button type="button"
class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-xs ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
onclick="document.getElementById('modal').classList.add('hidden')">{{ _("Cancel") }}</button>
</div>
</form>
{% endif %}
{% if invitation %}
<!-- Additional invitation context -->
<div class="mt-6 pt-4 border-t border-gray-200 dark:border-gray-600">
<h4 class="text-sm font-medium text-gray-900 dark:text-white mb-2">{{ _("Invitation Details") }}</h4>
<div class="text-xs text-gray-500 dark:text-gray-400 space-y-1">
<div>
{{ _("Code") }}: <span class="font-mono">{{ invitation.code }}</span>
</div>
{% if invitation.duration %}<div>{{ _("Duration") }}: {{ invitation.duration }} {{ _("days") }}</div>{% endif %}
{% if invitation.expires %}<div>{{ _("Invitation expires") }}: {{ invitation.expires|human_date }}</div>{% endif %}
</div>
</div>
{% endif %}
</div>
</div>

View File

@@ -304,8 +304,7 @@
aria-modal="true">
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity dark:bg-gray-900 dark:bg-opacity-80"></div>
<div class="flex min-h-full items-center justify-center p-4">
<div id="modal-user"
class="relative z-10"></div>
<div id="modal-user" class="relative z-10"></div>
</div>
</div>
<script>