Add admin tools: delete publisher and reset password
- DELETE /api/v1/admin/publishers/<id> - delete publisher with optional tool deletion - POST /api/v1/admin/publishers/<id>/reset-password - generate temporary password Safety features: - Cannot delete yourself or other admins - Delete requires typing username to confirm - Reset password shows temp password in modal with copy button Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
@@ -72,7 +72,9 @@
|
||||
{% else %}
|
||||
<button onclick="banPublisher({{ pub.id }}, '{{ pub.slug }}')" class="text-red-600 hover:text-red-900 mr-2">Ban</button>
|
||||
{% endif %}
|
||||
<button onclick="changeRole({{ pub.id }}, '{{ pub.slug }}', '{{ pub.role }}')" class="text-indigo-600 hover:text-indigo-900">Role</button>
|
||||
<button onclick="changeRole({{ pub.id }}, '{{ pub.slug }}', '{{ pub.role }}')" class="text-indigo-600 hover:text-indigo-900 mr-2">Role</button>
|
||||
<button onclick="resetPassword({{ pub.id }}, '{{ pub.slug }}')" class="text-yellow-600 hover:text-yellow-900 mr-2">Reset PW</button>
|
||||
<button onclick="deletePublisher({{ pub.id }}, '{{ pub.slug }}', {{ pub.tool_count }})" class="text-gray-600 hover:text-gray-900">Delete</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -138,6 +140,49 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Temp Password Modal -->
|
||||
<div id="temp-password-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden overflow-y-auto h-full w-full z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||
<div class="mt-3">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">Password Reset</h3>
|
||||
<p class="text-sm text-gray-500 mb-4">Temporary password for <strong id="temp-password-slug"></strong>:</p>
|
||||
<div class="bg-gray-100 p-3 rounded-md font-mono text-center text-lg select-all" id="temp-password-value"></div>
|
||||
<p class="text-xs text-gray-500 mt-2">Share this password securely with the user. They should change it after logging in.</p>
|
||||
<div class="mt-4 flex justify-end space-x-3">
|
||||
<button onclick="copyTempPassword()" class="px-4 py-2 text-sm font-medium text-indigo-600 hover:text-indigo-800">Copy</button>
|
||||
<button onclick="closeTempPasswordModal()" class="px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-md hover:bg-indigo-700">Done</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Delete Modal -->
|
||||
<div id="delete-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden overflow-y-auto h-full w-full z-50">
|
||||
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||
<div class="mt-3">
|
||||
<h3 class="text-lg font-medium text-red-600 mb-2">Delete Publisher</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">
|
||||
You are about to permanently delete <strong id="delete-publisher-name"></strong>.
|
||||
This action cannot be undone.
|
||||
</p>
|
||||
<div id="delete-tools-option" class="mb-4">
|
||||
<label class="flex items-center text-sm">
|
||||
<input type="checkbox" id="delete-tools-checkbox" class="mr-2 h-4 w-4 text-red-600 border-gray-300 rounded">
|
||||
Also delete their <span id="delete-tool-count" class="font-medium mx-1"></span> tools
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Type the username to confirm:</label>
|
||||
<input type="text" id="delete-confirm-input" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-red-500" placeholder="username">
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end space-x-3">
|
||||
<button onclick="closeDeleteModal()" class="px-4 py-2 text-sm font-medium text-gray-700 hover:text-gray-500">Cancel</button>
|
||||
<button onclick="confirmDelete()" class="px-4 py-2 bg-red-600 text-white text-sm font-medium rounded-md hover:bg-red-700">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentPublisherId = null;
|
||||
|
||||
@@ -233,5 +278,87 @@ async function confirmRole() {
|
||||
alert('Network error');
|
||||
}
|
||||
}
|
||||
|
||||
async function resetPassword(pubId, slug) {
|
||||
if (!confirm(`Reset password for @${slug}? A temporary password will be generated.`)) return;
|
||||
try {
|
||||
const response = await fetch(`/api/v1/admin/publishers/${pubId}/reset-password`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer {{ session.get("auth_token") }}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
const tempPw = data.data.temporary_password;
|
||||
// Show modal with temporary password
|
||||
document.getElementById('temp-password-value').textContent = tempPw;
|
||||
document.getElementById('temp-password-slug').textContent = slug;
|
||||
document.getElementById('temp-password-modal').classList.remove('hidden');
|
||||
} else {
|
||||
const data = await response.json();
|
||||
alert(data.error?.message || 'Failed to reset password');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Network error');
|
||||
}
|
||||
}
|
||||
|
||||
function closeTempPasswordModal() {
|
||||
document.getElementById('temp-password-modal').classList.add('hidden');
|
||||
}
|
||||
|
||||
function copyTempPassword() {
|
||||
const pw = document.getElementById('temp-password-value').textContent;
|
||||
navigator.clipboard.writeText(pw).then(() => {
|
||||
alert('Password copied to clipboard');
|
||||
});
|
||||
}
|
||||
|
||||
let deletePublisherData = null;
|
||||
|
||||
function deletePublisher(pubId, slug, toolCount) {
|
||||
deletePublisherData = { pubId, slug, toolCount };
|
||||
document.getElementById('delete-publisher-name').textContent = `@${slug}`;
|
||||
document.getElementById('delete-tool-count').textContent = toolCount;
|
||||
document.getElementById('delete-tools-option').style.display = toolCount > 0 ? 'block' : 'none';
|
||||
document.getElementById('delete-tools-checkbox').checked = false;
|
||||
document.getElementById('delete-modal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeDeleteModal() {
|
||||
document.getElementById('delete-modal').classList.add('hidden');
|
||||
deletePublisherData = null;
|
||||
}
|
||||
|
||||
async function confirmDelete() {
|
||||
if (!deletePublisherData) return;
|
||||
const deleteTools = document.getElementById('delete-tools-checkbox').checked;
|
||||
const confirmText = document.getElementById('delete-confirm-input').value;
|
||||
|
||||
if (confirmText !== deletePublisherData.slug) {
|
||||
alert('Please type the username correctly to confirm');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `/api/v1/admin/publishers/${deletePublisherData.pubId}?delete_tools=${deleteTools}`;
|
||||
const response = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': 'Bearer {{ session.get("auth_token") }}'
|
||||
}
|
||||
});
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
const data = await response.json();
|
||||
alert(data.error?.message || 'Failed to delete publisher');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Network error');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user