359 lines
13 KiB
Plaintext
359 lines
13 KiB
Plaintext
|
<!DOCTYPE html>
|
||
|
<html lang="en">
|
||
|
<head>
|
||
|
<meta charset="UTF-8">
|
||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
<title>Admin Panel - aTweet</title>
|
||
|
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
|
||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||
|
<style>
|
||
|
.admin-panel {
|
||
|
max-width: 1200px;
|
||
|
margin: 0 auto;
|
||
|
padding: 20px;
|
||
|
}
|
||
|
.admin-stats {
|
||
|
display: flex;
|
||
|
justify-content: space-between;
|
||
|
margin-bottom: 30px;
|
||
|
}
|
||
|
.stat-card {
|
||
|
background-color: var(--primary-color);
|
||
|
color: white;
|
||
|
border-radius: 10px;
|
||
|
padding: 20px;
|
||
|
text-align: center;
|
||
|
flex: 1;
|
||
|
margin: 0 10px;
|
||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||
|
}
|
||
|
.admin-section {
|
||
|
background-color: var(--background-color);
|
||
|
border-radius: 10px;
|
||
|
padding: 20px;
|
||
|
margin-bottom: 40px;
|
||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||
|
}
|
||
|
.admin-table {
|
||
|
width: 100%;
|
||
|
border-collapse: collapse;
|
||
|
}
|
||
|
.admin-table th, .admin-table td {
|
||
|
padding: 12px;
|
||
|
text-align: left;
|
||
|
border-bottom: 1px solid var(--border-color);
|
||
|
}
|
||
|
.admin-table th {
|
||
|
background-color: var(--primary-color);
|
||
|
color: white;
|
||
|
}
|
||
|
.admin-table tr:hover {
|
||
|
background-color: var(--hover-color);
|
||
|
}
|
||
|
.admin-actions {
|
||
|
display: flex;
|
||
|
gap: 10px;
|
||
|
}
|
||
|
.admin-button {
|
||
|
padding: 6px 12px;
|
||
|
border: none;
|
||
|
border-radius: 4px;
|
||
|
cursor: pointer;
|
||
|
transition: background-color 0.3s;
|
||
|
color: white;
|
||
|
}
|
||
|
.admin-button-view { background-color: #4CAF50; }
|
||
|
.admin-button-edit { background-color: #2196F3; }
|
||
|
.admin-button-delete { background-color: #f44336; }
|
||
|
.search-bar {
|
||
|
margin-bottom: 20px;
|
||
|
}
|
||
|
.search-bar input {
|
||
|
width: 100%;
|
||
|
padding: 10px;
|
||
|
border: 1px solid var(--border-color);
|
||
|
border-radius: 4px;
|
||
|
}
|
||
|
.pagination {
|
||
|
display: flex;
|
||
|
justify-content: center;
|
||
|
margin-top: 20px;
|
||
|
}
|
||
|
.pagination button {
|
||
|
margin: 0 5px;
|
||
|
padding: 5px 10px;
|
||
|
background-color: var(--primary-color);
|
||
|
color: white;
|
||
|
border: none;
|
||
|
border-radius: 4px;
|
||
|
cursor: pointer;
|
||
|
}
|
||
|
</style>
|
||
|
</head>
|
||
|
<body>
|
||
|
<div class="container admin-panel">
|
||
|
<h1>Admin Panel</h1>
|
||
|
|
||
|
<div class="admin-stats">
|
||
|
<div class="stat-card">
|
||
|
<h3>{{ user_count }}</h3>
|
||
|
<p>Total Users</p>
|
||
|
</div>
|
||
|
<div class="stat-card">
|
||
|
<h3>{{ tweet_count }}</h3>
|
||
|
<p>Total Tweets</p>
|
||
|
</div>
|
||
|
<div class="stat-card">
|
||
|
<h3>{{ group_count }}</h3>
|
||
|
<p>Total Groups</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="admin-section">
|
||
|
<h2>Users</h2>
|
||
|
<div class="search-bar">
|
||
|
<input type="text" id="userSearch" placeholder="Search users...">
|
||
|
</div>
|
||
|
<table class="admin-table" id="userTable">
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th>Username</th>
|
||
|
<th>Email</th>
|
||
|
<th>Joined</th>
|
||
|
<th>Actions</th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
{% for user in users %}
|
||
|
<tr>
|
||
|
<td>{{ user.username }}</td>
|
||
|
<td>{{ user.email }}</td>
|
||
|
<td>{{ user.created_at }}</td>
|
||
|
<td class="admin-actions">
|
||
|
<button class="admin-button admin-button-view" onclick="viewUser({{ user.id }})">View</button>
|
||
|
<button class="admin-button admin-button-edit" onclick="editUser({{ user.id }})">Edit</button>
|
||
|
<button class="admin-button admin-button-delete" onclick="deleteUser({{ user.id }})">Delete</button>
|
||
|
</td>
|
||
|
</tr>
|
||
|
{% endfor %}
|
||
|
</tbody>
|
||
|
</table>
|
||
|
<div class="pagination">
|
||
|
<button onclick="changePage(-1)">Previous</button>
|
||
|
<span id="currentPage">1</span>
|
||
|
<button onclick="changePage(1)">Next</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="admin-section">
|
||
|
<h2>Tweets</h2>
|
||
|
<div class="search-bar">
|
||
|
<input type="text" id="tweetSearch" placeholder="Search tweets...">
|
||
|
</div>
|
||
|
<table class="admin-table" id="tweetTable">
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th>Content</th>
|
||
|
<th>User</th>
|
||
|
<th>Created At</th>
|
||
|
<th>Actions</th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
{% for tweet in tweets %}
|
||
|
<tr>
|
||
|
<td>{{ tweet.content }}</td>
|
||
|
<td>{{ tweet.username }}</td>
|
||
|
<td>{{ tweet.created_at }}</td>
|
||
|
<td class="admin-actions">
|
||
|
<button class="admin-button admin-button-view" onclick="viewTweet({{ tweet.id }})">View</button>
|
||
|
<button class="admin-button admin-button-delete" onclick="deleteTweet({{ tweet.id }})">Delete</button>
|
||
|
</td>
|
||
|
</tr>
|
||
|
{% endfor %}
|
||
|
</tbody>
|
||
|
</table>
|
||
|
</div>
|
||
|
|
||
|
<div class="admin-section">
|
||
|
<h2>Groups</h2>
|
||
|
<div class="search-bar">
|
||
|
<input type="text" id="groupSearch" placeholder="Search groups...">
|
||
|
</div>
|
||
|
<table class="admin-table" id="groupTable">
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th>Name</th>
|
||
|
<th>Description</th>
|
||
|
<th>Members</th>
|
||
|
<th>Created At</th>
|
||
|
<th>Actions</th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
{% for group in groups %}
|
||
|
<tr>
|
||
|
<td>{{ group.name }}</td>
|
||
|
<td>{{ group.description }}</td>
|
||
|
<td>{{ group.member_count }}</td>
|
||
|
<td>{{ group.created_at }}</td>
|
||
|
<td class="admin-actions">
|
||
|
<button class="admin-button admin-button-view" onclick="viewGroup({{ group.id }})">View</button>
|
||
|
<button class="admin-button admin-button-edit" onclick="editGroup({{ group.id }})">Edit</button>
|
||
|
<button class="admin-button admin-button-delete" onclick="deleteGroup({{ group.id }})">Delete</button>
|
||
|
</td>
|
||
|
</tr>
|
||
|
{% endfor %}
|
||
|
</tbody>
|
||
|
</table>
|
||
|
</div>
|
||
|
</div>
|
||
|
<script>
|
||
|
function viewUser(userId) {
|
||
|
window.location.href = `/profile/${userId}`;
|
||
|
}
|
||
|
|
||
|
function editUser(userId) {
|
||
|
const newUsername = prompt("Enter new username:");
|
||
|
const newEmail = prompt("Enter new email:");
|
||
|
if (newUsername && newEmail) {
|
||
|
fetch(`/admin/edit_user/${userId}`, {
|
||
|
method: 'POST',
|
||
|
headers: {
|
||
|
'Content-Type': 'application/json',
|
||
|
},
|
||
|
body: JSON.stringify({ username: newUsername, email: newEmail }),
|
||
|
})
|
||
|
.then(response => response.json())
|
||
|
.then(data => {
|
||
|
if (data.success) {
|
||
|
location.reload();
|
||
|
} else {
|
||
|
alert('Failed to edit user');
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function deleteUser(userId) {
|
||
|
if (confirm('Are you sure you want to delete this user?')) {
|
||
|
fetch(`/admin/delete_user/${userId}`, { method: 'POST' })
|
||
|
.then(response => response.json())
|
||
|
.then(data => {
|
||
|
if (data.success) {
|
||
|
location.reload();
|
||
|
} else {
|
||
|
alert('Failed to delete user');
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function viewTweet(tweetId) {
|
||
|
window.location.href = `/tweet/${tweetId}`;
|
||
|
}
|
||
|
|
||
|
function deleteTweet(tweetId) {
|
||
|
if (confirm('Are you sure you want to delete this tweet?')) {
|
||
|
fetch(`/admin/delete_tweet/${tweetId}`, { method: 'POST' })
|
||
|
.then(response => response.json())
|
||
|
.then(data => {
|
||
|
if (data.success) {
|
||
|
location.reload();
|
||
|
} else {
|
||
|
alert('Failed to delete tweet');
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function viewGroup(groupId) {
|
||
|
window.location.href = `/group/${groupId}`;
|
||
|
}
|
||
|
|
||
|
function editGroup(groupId) {
|
||
|
const newName = prompt("Enter new group name:");
|
||
|
const newDescription = prompt("Enter new group description:");
|
||
|
if (newName && newDescription) {
|
||
|
fetch(`/admin/edit_group/${groupId}`, {
|
||
|
method: 'POST',
|
||
|
headers: {
|
||
|
'Content-Type': 'application/json',
|
||
|
},
|
||
|
body: JSON.stringify({ name: newName, description: newDescription }),
|
||
|
})
|
||
|
.then(response => response.json())
|
||
|
.then(data => {
|
||
|
if (data.success) {
|
||
|
location.reload();
|
||
|
} else {
|
||
|
alert('Failed to edit group');
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function deleteGroup(groupId) {
|
||
|
if (confirm('Are you sure you want to delete this group?')) {
|
||
|
fetch(`/admin/delete_group/${groupId}`, { method: 'POST' })
|
||
|
.then(response => response.json())
|
||
|
.then(data => {
|
||
|
if (data.success) {
|
||
|
location.reload();
|
||
|
} else {
|
||
|
alert('Failed to delete group');
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function searchTable(inputId, tableId) {
|
||
|
const input = document.getElementById(inputId);
|
||
|
const filter = input.value.toUpperCase();
|
||
|
const table = document.getElementById(tableId);
|
||
|
const tr = table.getElementsByTagName("tr");
|
||
|
|
||
|
for (let i = 1; i < tr.length; i++) {
|
||
|
let txtValue = tr[i].textContent || tr[i].innerText;
|
||
|
if (txtValue.toUpperCase().indexOf(filter) > -1) {
|
||
|
tr[i].style.display = "";
|
||
|
} else {
|
||
|
tr[i].style.display = "none";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
document.getElementById("userSearch").addEventListener("keyup", () => searchTable("userSearch", "userTable"));
|
||
|
document.getElementById("tweetSearch").addEventListener("keyup", () => searchTable("tweetSearch", "tweetTable"));
|
||
|
document.getElementById("groupSearch").addEventListener("keyup", () => searchTable("groupSearch", "groupTable"));
|
||
|
|
||
|
let currentPage = 1;
|
||
|
const itemsPerPage = 10;
|
||
|
|
||
|
function changePage(direction) {
|
||
|
currentPage += direction;
|
||
|
if (currentPage < 1) currentPage = 1;
|
||
|
document.getElementById("currentPage").textContent = currentPage;
|
||
|
updateTableDisplay();
|
||
|
}
|
||
|
|
||
|
function updateTableDisplay() {
|
||
|
const table = document.getElementById("userTable");
|
||
|
const tr = table.getElementsByTagName("tr");
|
||
|
const start = (currentPage - 1) * itemsPerPage + 1;
|
||
|
const end = start + itemsPerPage;
|
||
|
|
||
|
for (let i = 1; i < tr.length; i++) {
|
||
|
if (i >= start && i < end) {
|
||
|
tr[i].style.display = "";
|
||
|
} else {
|
||
|
tr[i].style.display = "none";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
updateTableDisplay();
|
||
|
</script>
|
||
|
</body>
|
||
|
</html>
|