feat(templates/projects): add permissions modal and restricted mode logic
Implement permission configuration UI and enforce restricted movement rules
This commit is contained in:
2 files changed
+206
-6
No files matched your search
@@ -33,7 +33,10 @@
|
||||
newTicketOpen: false,
|
||||
exportOpen: false,
|
||||
filterOpen: false,
|
||||
filterOpen: false,
|
||||
manageStatusesOpen: false,
|
||||
permissionsOpen: false,
|
||||
allowedMoveToStatusIds: {{ allowed_move_to_status_ids }},
|
||||
|
||||
createStatusName: '',
|
||||
createStatusColor: '#6B7280',
|
||||
@@ -199,8 +202,8 @@
|
||||
touch.clientY >= rect.top && touch.clientY <= rect.bottom) targetColumn = col;
|
||||
col.classList.remove('bg-blue-50', 'dark:bg-blue-900/20');
|
||||
});
|
||||
{% if not can_move_to_completed %}
|
||||
if (targetColumn && targetColumn.getAttribute('data-status-completed') === 'true') targetColumn = null;
|
||||
{% if can_move %}
|
||||
if (targetColumn && targetColumn.getAttribute('data-status-id') && !this.allowedMoveToStatusIds.includes(parseInt(targetColumn.getAttribute('data-status-id')))) targetColumn = null;
|
||||
{% endif %}
|
||||
if (targetColumn && targetColumn !== originalColumn)
|
||||
this.moveTicketToStatus(this.draggingTicket, targetColumn.getAttribute('data-status-id'));
|
||||
@@ -224,8 +227,8 @@
|
||||
col.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
col.classList.remove('bg-blue-50', 'dark:bg-blue-900/20');
|
||||
{% if not can_move_to_completed %}
|
||||
if (col.getAttribute('data-status-completed') === 'true') return;
|
||||
{% if can_move %}
|
||||
if (col.getAttribute('data-status-id') && !this.allowedMoveToStatusIds.includes(parseInt(col.getAttribute('data-status-id')))) return;
|
||||
{% endif %}
|
||||
if (this.draggingTicket) {
|
||||
this.moveTicketToStatus(this.draggingTicket, col.getAttribute('data-status-id'));
|
||||
@@ -316,6 +319,9 @@ class="space-y-6"
|
||||
<i class="fas fa-filter"></i> Filter {% if selected_user_ids %}<span class="ml-1 bg-blue-100 text-blue-800 text-xs font-semibold px-2 py-0.5 rounded">{{ selected_user_ids|length }}</span>{% endif %}
|
||||
</button>
|
||||
{% if is_admin %}
|
||||
<button @click="permissionsOpen = true" class="dk-btn dk-btn-ghost text-sm text-orange-600">
|
||||
<i class="fas fa-shield-halved"></i> Permissions
|
||||
</button>
|
||||
<button @click="manageStatusesOpen = true" class="dk-btn dk-btn-ghost text-sm text-purple-600">
|
||||
<i class="fas fa-cog"></i> Manage Statuses
|
||||
</button>
|
||||
@@ -368,10 +374,10 @@ class="space-y-6"
|
||||
<i class="fas fa-eye"></i><span class="text-sm font-medium">View Only Mode</span><span class="text-xs opacity-75">You can view tickets but cannot move them</span>
|
||||
</div>
|
||||
</div>
|
||||
{% elif not can_move_to_completed %}
|
||||
{% elif can_move and allowed_move_to_status_ids|length < columns|length %}
|
||||
<div class="flex justify-center mb-4">
|
||||
<div class="inline-flex items-center gap-2 px-4 py-2 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg text-blue-800 dark:text-blue-200">
|
||||
<i class="fas fa-info-circle"></i><span class="text-sm font-medium">Restricted Mode</span><span class="text-xs opacity-75">You can move tickets to active statuses only</span>
|
||||
<i class="fas fa-info-circle"></i><span class="text-sm font-medium">Restricted Mode</span><span class="text-xs opacity-75">You can only move tickets to certain statuses</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -758,6 +764,101 @@ class="space-y-6"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Board Permissions Modal -->
|
||||
<div x-cloak x-show="permissionsOpen" class="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto"
|
||||
@keydown.escape.window="permissionsOpen = false">
|
||||
<div class="fixed inset-0 bg-black/50" @click="permissionsOpen = false"></div>
|
||||
<div class="relative bg-white dark:bg-gray-950 rounded-xl shadow-xl border border-gray-200 dark:border-gray-600 w-full max-w-6xl mx-auto my-auto max-h-[90vh] overflow-y-auto z-10 p-6 md:p-8">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Board Permissions</h3>
|
||||
<button @click="permissionsOpen = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"><i class="fas fa-times text-xl"></i></button>
|
||||
</div>
|
||||
|
||||
<!-- Add Permission Form -->
|
||||
<div class="rounded-lg bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 p-4 mb-4">
|
||||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-3">Add / Update Permission</h4>
|
||||
<form method="post" action="{% url 'board-permissions' selected_project.id %}">
|
||||
{% csrf_token %}
|
||||
<div class="grid grid-cols-12 gap-3 items-start">
|
||||
<div class="col-span-3">
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Role</label>
|
||||
<select name="role" required
|
||||
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-950 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none">
|
||||
<option value="">Select a role</option>
|
||||
{% for role_key, role_label in role_choices %}
|
||||
{% if role_key not in admin_roles %}
|
||||
<option value="{{ role_key }}">{{ role_label }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-span-7">
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Can move to</label>
|
||||
<div class="grid grid-cols-3 gap-x-4 gap-y-1.5">
|
||||
{% for col in columns %}
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" name="move_to_statuses" value="{{ col.status.id }}"
|
||||
class="w-3.5 h-3.5 text-blue-600 rounded border-gray-300 focus:ring-blue-500">
|
||||
<span class="w-2.5 h-2.5 rounded-full inline-block flex-shrink-0" style="background-color:{{ col.status.color }}"></span>
|
||||
<span class="text-xs text-gray-700 dark:text-gray-300 truncate">{{ col.status.name }}</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2 pt-5">
|
||||
<button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors w-full">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Permissions List -->
|
||||
<div class="rounded-xl bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 dark:bg-gray-800">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Role</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Can move to</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
{% for perm in permissions %}
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30">
|
||||
<td class="px-4 py-3 font-medium text-gray-900 dark:text-white">{{ perm.get_role_display }}</td>
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
{% for status in perm.move_to_statuses.all %}
|
||||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium" style="background-color:{{ status.color }}20;color:{{ status.color }}">
|
||||
<span class="w-1.5 h-1.5 rounded-full" style="background-color:{{ status.color }}"></span>
|
||||
{{ status.name }}
|
||||
</span>
|
||||
{% empty %}
|
||||
<span class="text-xs text-gray-400 italic">None</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
<form method="post" action="{% url 'board-permission-delete' selected_project.id perm.id %}" class="inline">
|
||||
{% csrf_token %}
|
||||
<button type="button" @click="confirmAction({title:'Delete Permission', message:'Delete permission for <strong>' + '{{ perm.get_role_display|escapejs }}' + '</strong>?', confirmText:'Delete', danger:true, onConfirm:function(){ $el.closest('form').submit() }})" class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium text-red-600 dark:text-red-400 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors shadow-sm">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="3" class="px-4 py-8 text-center text-sm text-gray-400">No permissions configured. All non-admin roles are restricted by default.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-4"><i class="fas fa-info-circle"></i> Superadmin and manager roles always have full access. Select the statuses a developer role can move tickets to.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -175,6 +175,9 @@
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-white">Ticket Statuses</h3>
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
{% if is_admin %}
|
||||
<button type="button" @click="$store.projectDetail.permissionsOpen = true" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-orange-600 bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800 rounded-lg hover:bg-orange-100 dark:hover:bg-orange-900/30 transition-colors">
|
||||
<i class="fas fa-shield-halved"></i> Permissions
|
||||
</button>
|
||||
<button type="button" @click="$store.projectDetail.manageStatusesOpen = true" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-purple-600 bg-purple-50 dark:bg-purple-900/20 border border-purple-200 dark:border-purple-800 rounded-lg hover:bg-purple-100 dark:hover:bg-purple-900/30 transition-colors">
|
||||
<i class="fas fa-cog"></i> Manage
|
||||
</button>
|
||||
@@ -698,6 +701,101 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Board Permissions Modal -->
|
||||
<div x-cloak x-show="$store.projectDetail.permissionsOpen" class="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto"
|
||||
@keydown.escape.window="$store.projectDetail.permissionsOpen = false">
|
||||
<div class="fixed inset-0 bg-black/50" @click="$store.projectDetail.permissionsOpen = false"></div>
|
||||
<div class="relative bg-white dark:bg-gray-950 rounded-xl shadow-xl border border-gray-200 dark:border-gray-600 w-full max-w-6xl mx-auto my-auto max-h-[90vh] overflow-y-auto z-10 p-6 md:p-8">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Board Permissions</h3>
|
||||
<button @click="$store.projectDetail.permissionsOpen = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"><i class="fas fa-times text-xl"></i></button>
|
||||
</div>
|
||||
|
||||
<!-- Add Permission Form -->
|
||||
<div class="rounded-lg bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 p-4 mb-4">
|
||||
<h4 class="text-sm font-semibold text-gray-900 dark:text-white mb-3">Add / Update Permission</h4>
|
||||
<form method="post" action="{% url 'board-permissions' project.id %}">
|
||||
{% csrf_token %}
|
||||
<div class="grid grid-cols-12 gap-3 items-start">
|
||||
<div class="col-span-3">
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Role</label>
|
||||
<select name="role" required
|
||||
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-950 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 outline-none">
|
||||
<option value="">Select a role</option>
|
||||
{% for role_key, role_label in role_choices %}
|
||||
{% if role_key not in admin_roles %}
|
||||
<option value="{{ role_key }}">{{ role_label }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-span-7">
|
||||
<label class="block text-xs font-medium text-gray-600 dark:text-gray-400 mb-1">Can move to</label>
|
||||
<div class="grid grid-cols-3 gap-x-4 gap-y-1.5">
|
||||
{% for s in project.ticket_statuses.all %}
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" name="move_to_statuses" value="{{ s.id }}"
|
||||
class="w-3.5 h-3.5 text-blue-600 rounded border-gray-300 focus:ring-blue-500">
|
||||
<span class="w-2.5 h-2.5 rounded-full inline-block flex-shrink-0" style="background-color:{{ s.color }}"></span>
|
||||
<span class="text-xs text-gray-700 dark:text-gray-300 truncate">{{ s.name }}</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-2 pt-5">
|
||||
<button type="submit" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors w-full">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Permissions List -->
|
||||
<div class="rounded-xl bg-white dark:bg-gray-950 border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="bg-gray-50 dark:bg-gray-800">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Role</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Can move to</th>
|
||||
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
{% for perm in permissions %}
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800/30">
|
||||
<td class="px-4 py-3 font-medium text-gray-900 dark:text-white">{{ perm.get_role_display }}</td>
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
{% for status in perm.move_to_statuses.all %}
|
||||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium" style="background-color:{{ status.color }}20;color:{{ status.color }}">
|
||||
<span class="w-1.5 h-1.5 rounded-full" style="background-color:{{ status.color }}"></span>
|
||||
{{ status.name }}
|
||||
</span>
|
||||
{% empty %}
|
||||
<span class="text-xs text-gray-400 italic">None</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
<form method="post" action="{% url 'board-permission-delete' project.id perm.id %}" class="inline">
|
||||
{% csrf_token %}
|
||||
<button type="button" @click="confirmAction({title:'Delete Permission', message:'Delete permission for <strong>' + '{{ perm.get_role_display|escapejs }}' + '</strong>?', confirmText:'Delete', danger:true, onConfirm:function(){ $el.closest('form').submit() }})" class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-medium text-red-600 dark:text-red-400 bg-white dark:bg-gray-900 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors shadow-sm">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="3" class="px-4 py-8 text-center text-sm text-gray-400">No permissions configured. All non-admin roles are restricted by default.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-4"><i class="fas fa-info-circle"></i> Superadmin and manager roles always have full access. Select the statuses a developer role can move tickets to.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
@@ -712,6 +810,7 @@
|
||||
viewNoteOpen: false,
|
||||
viewNoteData: { id: '', title: '', content: '' },
|
||||
manageStatusesOpen: false,
|
||||
permissionsOpen: false,
|
||||
createStatusName: '',
|
||||
createStatusColor: '#6B7280',
|
||||
createStatusOrder: '0',
|
||||
|
||||
Reference in new issue
Block a user