Inital commit
This commit is contained in:
parent
a5b08abada
commit
2c532c6555
487
css/styles.css
Normal file
487
css/styles.css
Normal file
@ -0,0 +1,487 @@
|
||||
|
||||
/* styles.css */
|
||||
|
||||
html {
|
||||
font-size: 14px; /* Reduce from default 16px */
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000; /* Ensure it stays above other content */
|
||||
background-color: #f1f1f1; /* Match the background color */
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-direction: column; /* Stack tabs vertically */
|
||||
width: 150px; /* Adjust width of the tabs */
|
||||
border-right: 1px solid #ddd; /* Add a vertical separator */
|
||||
background-color: #f5f5f5;
|
||||
height: 100vh; /* Make tabs stretch vertically to fill the viewport */
|
||||
overflow-y: auto; /* Enable scrolling if the tabs exceed the height */
|
||||
position: fixed; /* Keep tabs fixed on the left side */
|
||||
left: 0; /* Align tabs to the left edge */
|
||||
top: 0;
|
||||
z-index: 1000; /* Ensure tabs are above other content */
|
||||
}
|
||||
|
||||
/* Individual Tab */
|
||||
.tab {
|
||||
padding: 15px 20px;
|
||||
font-size: 16px;
|
||||
background: none;
|
||||
border: none;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
border-bottom: 1px solid #ddd; /* Add a separator between tabs */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Active Tab */
|
||||
.tab.active {
|
||||
background-color: #ddd;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Hover Effect */
|
||||
.tab:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
/* Optional: Remove last tab's margin */
|
||||
.tabs .tab:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#content {
|
||||
margin-left: 150px; /* Match the width of the tabs */
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-top: none;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none; /* Hidden by default */
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: 5% auto; /* Reduced top margin for more vertical space */
|
||||
padding: 15px; /* Reduced padding */
|
||||
border: 1px solid #888;
|
||||
width: 35%; /* Reduced width from 50% to 35% */
|
||||
max-width: 500px; /* Added max-width for larger screens */
|
||||
position: relative;
|
||||
border-radius: 8px; /* Optional: Rounded corners for a modern look */
|
||||
}
|
||||
|
||||
#close-modal {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/* Add padding and spacing for form fields inside the Edit Modal */
|
||||
#edit-modal .form-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px; /* Reduced gap between form elements */
|
||||
padding: 0; /* Removed extra padding */
|
||||
background-color: transparent; /* Remove background to match modal */
|
||||
box-shadow: none; /* Remove shadow inside modal */
|
||||
}
|
||||
|
||||
/* Ensure form fields are styled properly */
|
||||
#edit-modal .form-container .form-group {
|
||||
margin-bottom: 10px; /* Add spacing between individual form groups */
|
||||
}
|
||||
|
||||
#edit-modal .form-container label {
|
||||
margin-bottom: 3px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#edit-modal .form-container input[type="text"],
|
||||
#edit-modal .form-container input[type="date"],
|
||||
#edit-modal .form-container input[type="url"],
|
||||
#edit-modal .form-container textarea,
|
||||
#edit-modal .form-container select {
|
||||
width: 100%;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#edit-modal .form-container textarea {
|
||||
resize: vertical;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
/* Buttons at the bottom of the form */
|
||||
#edit-modal .form-container .form-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px; /* Space between buttons */
|
||||
}
|
||||
|
||||
#edit-modal .form-container .form-buttons button {
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#edit-modal .form-container .form-buttons button[type="submit"] {
|
||||
padding: 8px 12px; /* Reduced padding */
|
||||
font-size: 14px; /* Reduced font size */
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#edit-modal .form-container .form-buttons button[type="button"] {
|
||||
background-color: #dc3545;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#edit-modal .form-container .form-buttons button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Style for the Add Record form */
|
||||
#add-form {
|
||||
margin-bottom: 40px;
|
||||
padding: 10px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
tr.completed {
|
||||
background-color: lightgreen !important;
|
||||
}
|
||||
|
||||
.suggested {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
.watching {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
/* Form Container */
|
||||
.form-container-horizontal {
|
||||
max-width: 100%;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
background-color: #f1f1f1; /* Light background */
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-container-horizontal h3 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Form Row */
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 10px; /* space between the columns */
|
||||
}
|
||||
|
||||
/* Optional: make each form-group share space evenly */
|
||||
.form-row .form-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Remove margin from the last form group in a row */
|
||||
.form-row .form-group:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
/* Labels */
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Input fields */
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Checkbox and Buttons Row */
|
||||
#edit-modal .form-container .form-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.form-check-input {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0069d9;
|
||||
}
|
||||
|
||||
.set-complete-button {
|
||||
background-color: #00eb0c;
|
||||
}
|
||||
|
||||
.set-complete-button:hover {
|
||||
background-color: #038c0a;
|
||||
}
|
||||
|
||||
.set-currently-watching-button {
|
||||
background-color: #f6fa02;
|
||||
}
|
||||
|
||||
.set-currently-watching-button:hover {
|
||||
background-color: #bcbf02;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background-color: #f44336;
|
||||
}
|
||||
|
||||
.delete-button:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 15px;
|
||||
font-size: 24px; /* Reduced font size */
|
||||
color: #aaa;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.form-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
.form-row .form-group {
|
||||
margin-right: 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.form-check {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
flex-direction: column;
|
||||
}
|
||||
.tab {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Close Button */
|
||||
.close-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: #aaa;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.record-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.record-table {
|
||||
|
||||
width: 100%;
|
||||
|
||||
border-collapse: collapse;
|
||||
|
||||
font-family: Arial, sans-serif;
|
||||
|
||||
border: 1px solid #000; /* Added black border to the table */
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Column widths using CSS selectors */
|
||||
|
||||
.record-table colgroup col:nth-child(1) { width: 3%; } /* # */
|
||||
|
||||
.record-table colgroup col:nth-child(2) { width: 35%; } /* Name */
|
||||
|
||||
.record-table colgroup col:nth-child(3) { width: 4%; }/* Type */
|
||||
|
||||
.record-table colgroup col:nth-child(4) { width: 37%; }/* Comment */
|
||||
|
||||
.record-table colgroup col:nth-child(5) { width: 6%; }/* Date completed */
|
||||
|
||||
.record-table colgroup col:nth-child(6) { width: 15%; }/* Actions */
|
||||
|
||||
/* Column widths using CSS selectors */
|
||||
|
||||
.record-table colgroup col:nth-child(1) #rewatch { width: 3%; } /* # */
|
||||
|
||||
.record-table colgroup col:nth-child(2) #rewatch { width: 40%; } /* Name */
|
||||
|
||||
.record-table colgroup col:nth-child(3) #rewatch { width: 47%; }/* Comment */
|
||||
|
||||
.record-table colgroup col:nth-child(4) #rewatch { width: 10%; }/* Actions */
|
||||
|
||||
/* Manga table */
|
||||
|
||||
.record-table colgroup col:nth-child(1) #manga { width: 3%; } /* # */
|
||||
|
||||
.record-table colgroup col:nth-child(2) #manga { width: 35%; } /* Name */
|
||||
|
||||
.record-table colgroup col:nth-child(4) #manga { width: 41%; }/* Comment */
|
||||
|
||||
.record-table colgroup col:nth-child(5) #manga { width: 6%; }/* Date completed */
|
||||
|
||||
.record-table colgroup col:nth-child(6) #manga { width: 15%; }/* Actions */
|
||||
|
||||
|
||||
/* Styling table headers and cells */
|
||||
|
||||
.record-table th, .record-table td {
|
||||
|
||||
border: 1px solid #000; /* Changed border color to black */
|
||||
|
||||
padding: 8px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Style for table headers */
|
||||
|
||||
.record-table th {
|
||||
|
||||
background-color: #f2f2f2;
|
||||
|
||||
font-weight: bold;
|
||||
|
||||
}
|
||||
|
||||
.record-table tbody tr:hover {
|
||||
|
||||
background-color: #e9e9e9;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Text alignment in columns */
|
||||
|
||||
.record-table th:nth-child(1), .record-table td:nth-child(1),
|
||||
|
||||
.record-table th:nth-child(3), .record-table td:nth-child(3),
|
||||
|
||||
.record-table th:nth-child(5), .record-table td:nth-child(5),
|
||||
|
||||
.record-table th:nth-child(6), .record-table td:nth-child(6) {
|
||||
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
|
||||
/* Styling action buttons */
|
||||
|
||||
.record-table td button {
|
||||
padding: 4px 6px; /* Reduce button padding */
|
||||
margin: 0; /* Remove margins */
|
||||
font-size: 12px; /* Reduce font size */
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 5px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
th, td {
|
||||
padding: 1px 2px; /* Reduced padding */
|
||||
line-height: 0.8; /* Reduced line height */
|
||||
font-size: 14px; /* Reduced font size */
|
||||
vertical-align: middle; /* Center content vertically */
|
||||
text-align: left; /* Existing alignment */
|
||||
white-space: nowrap; /* Prevent text from wrapping */
|
||||
overflow: hidden; /* Hide overflow */
|
||||
text-overflow: ellipsis; /* Add ellipsis for overflowing text */
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
cursor: pointer;
|
||||
background: linear-gradient(to bottom, lightgrey, white);
|
||||
}
|
||||
|
||||
.record-table table, .record-table th, .record-table td {
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
|
||||
#image-tooltip {
|
||||
position: absolute;
|
||||
border: 1px solid #ccc;
|
||||
background: #fff;
|
||||
padding: 5px;
|
||||
z-index: 1000;
|
||||
max-width: 200px;
|
||||
max-height: 300px;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
BIN
icons/android-chrome-192x192.png
Normal file
BIN
icons/android-chrome-192x192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
BIN
icons/android-chrome-512x512.png
Normal file
BIN
icons/android-chrome-512x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 306 KiB |
BIN
icons/apple-touch-icon.png
Normal file
BIN
icons/apple-touch-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
BIN
icons/favicon-16x16.png
Normal file
BIN
icons/favicon-16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 924 B |
BIN
icons/favicon-32x32.png
Normal file
BIN
icons/favicon-32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
BIN
icons/favicon.ico
Normal file
BIN
icons/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
40
index.html
Normal file
40
index.html
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Backlog of unwatched anime</title>
|
||||
<link rel="stylesheet" href="css/styles.css?v=71">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="./icons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="./icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="./icons/favicon-16x16.png">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Tabs for Years -->
|
||||
<div class="tabs">
|
||||
<div class="tabs-container">
|
||||
<div id="year-tabs" class="tabs"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Content Area -->
|
||||
<div id="content">
|
||||
<!-- Tables and Forms will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<div id="edit-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span id="close-modal" class="close-button">×</span>
|
||||
<form id="edit-form" class="form-container">
|
||||
<!-- Form fields will be loaded here -->
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="js/main.js?v=72"></script>
|
||||
</body>
|
||||
</html>
|
540
js/main.js
Normal file
540
js/main.js
Normal file
@ -0,0 +1,540 @@
|
||||
let currentYear;
|
||||
|
||||
//Adding new element in the array creates new tab
|
||||
const years = ['pre-2009', '2009', '2010', '2011', '2012', '2013', '2014', '2015',
|
||||
'2016','2017','2018','2019','2020','2021','2022','2023', '2024', 're-watch', 'manga'];
|
||||
|
||||
function loadYearTabs() {
|
||||
const yearTabs = document.getElementById('year-tabs');
|
||||
|
||||
// Populate the tabs
|
||||
years.forEach(year => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = year;
|
||||
li.classList.add('tab');
|
||||
li.dataset.year = year; // Set data-year attribute for each tab
|
||||
|
||||
// Add a click event listener to each tab
|
||||
li.addEventListener('click', () => {
|
||||
loadYearContent(year);
|
||||
});
|
||||
|
||||
yearTabs.appendChild(li);
|
||||
// Now check if all entries are completed for this year
|
||||
fetch(`php/check_completed_year.php?year=${encodeURIComponent(year)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.all_completed) {
|
||||
// Strike through the tab if all are completed
|
||||
li.style.textDecoration = 'line-through';
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error checking completed status:', error));
|
||||
});
|
||||
|
||||
// Load the last active tab or default to 'pre-2009'
|
||||
const activeYear = localStorage.getItem('activeYear') || 'pre-2009';
|
||||
loadYearContent(activeYear);
|
||||
}
|
||||
|
||||
function loadYearContent(year) {
|
||||
currentYear = year;
|
||||
// Save the current year in localStorage for persistence
|
||||
localStorage.setItem('activeYear', year);
|
||||
|
||||
// Highlight the active tab
|
||||
const tabs = document.querySelectorAll('#year-tabs .tab');
|
||||
tabs.forEach(tab => {
|
||||
// Add or remove the active class based on the tab's data-year
|
||||
if (tab.dataset.year === year) {
|
||||
tab.classList.add('active');
|
||||
} else {
|
||||
tab.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch and display content for the selected year
|
||||
fetch(`php/fetch_data.php?year=${encodeURIComponent(year)}`)
|
||||
.then(response => response.text())
|
||||
.then(html => {
|
||||
document.getElementById('content').innerHTML = html;
|
||||
setupFormSubmission();
|
||||
setupEditButtons();
|
||||
setupDeleteButtons();
|
||||
setupSuggestButton();
|
||||
setupSetCompleteButtons();
|
||||
setupSetCurrentlyWatchingButtons();
|
||||
})
|
||||
.catch(error => console.error('Error loading content:', error));
|
||||
}
|
||||
|
||||
function setupFormSubmission() {
|
||||
const form = document.getElementById('add-form');
|
||||
|
||||
function handleFormSubmit(event) {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(form);
|
||||
fetch('php/add_record.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
// Если сервер вернул ошибку (например, 403)
|
||||
return response.json().then(errorData => {
|
||||
if (response.status === 403) {
|
||||
alert(errorData.message || 'Access denied: You are not authorized to perform this action.');
|
||||
}
|
||||
throw new Error(errorData.message || 'An error occurred.');
|
||||
});
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then(() => {
|
||||
// Если запись добавлена успешно
|
||||
loadYearContent(currentYear);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('submit', handleFormSubmit);
|
||||
|
||||
form.addEventListener('keydown', event => {
|
||||
if (event.ctrlKey && event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
handleFormSubmit(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function setupEditButtons() {
|
||||
const editButtons = document.querySelectorAll('.edit-button');
|
||||
editButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const recordId = button.dataset.id;
|
||||
openEditModal(recordId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupDeleteButtons() {
|
||||
const deleteButtons = document.querySelectorAll('.delete-button');
|
||||
deleteButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const recordId = button.dataset.id;
|
||||
if (confirm('Are you sure you want to delete this record?')) {
|
||||
fetch(`php/delete_record.php?id=${recordId}`, {
|
||||
method: 'DELETE' // Используем метод DELETE для удаления
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
// Если сервер вернул ошибку (например, 403)
|
||||
return response.json().then(errorData => {
|
||||
if (response.status === 403) {
|
||||
alert(errorData.message || 'Access denied: You are not authorized to delete this record.');
|
||||
}
|
||||
throw new Error(errorData.message || 'An error occurred while deleting the record.');
|
||||
});
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then(() => {
|
||||
// Если удаление прошло успешно
|
||||
loadYearContent(currentYear);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function openEditModal(id) {
|
||||
// Fetch record data
|
||||
fetch(`php/fetch_record.php?id=${id}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const modal = document.getElementById('edit-modal');
|
||||
const form = document.getElementById('edit-form');
|
||||
form.innerHTML = generateEditForm(data);
|
||||
modal.style.display = 'block';
|
||||
setupEditFormSubmission();
|
||||
});
|
||||
}
|
||||
|
||||
function setupModal() {
|
||||
const modal = document.getElementById('edit-modal');
|
||||
const closeModal = document.getElementById('close-modal');
|
||||
closeModal.addEventListener('click', () => modal.style.display = 'none');
|
||||
|
||||
window.addEventListener('keydown', event => {
|
||||
if (event.key === 'Escape') {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Close modal when clicking outside the modal content
|
||||
modal.addEventListener('click', event => {
|
||||
if (event.target === modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function mapLabelToYear(label) {
|
||||
if (label === 'pre-2009') return -1;
|
||||
if (label === 're-watch') return -2;
|
||||
return parseInt(label, 10);
|
||||
}
|
||||
|
||||
function setupEditFormSubmission() {
|
||||
const form = document.getElementById('edit-form');
|
||||
|
||||
function handleEditFormSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const yearLabelSelect = form.querySelector('#year_label');
|
||||
const numericYearInput = form.querySelector('#year_numeric');
|
||||
const selectedLabel = yearLabelSelect.value;
|
||||
|
||||
// Use the mapLabelToYear function
|
||||
numericYearInput.value = mapLabelToYear(selectedLabel);
|
||||
|
||||
const formData = new FormData(form);
|
||||
fetch('php/edit_record.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.json().then(errorData => {
|
||||
if (response.status === 403) {
|
||||
alert(errorData.message || 'Access denied: You are not authorized to edit this record.');
|
||||
}
|
||||
throw new Error(errorData.message || 'An error occurred while editing the record.');
|
||||
});
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then(() => {
|
||||
const modal = document.getElementById('edit-modal');
|
||||
modal.style.display = 'none';
|
||||
loadYearContent(currentYear);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
form.addEventListener('submit', handleEditFormSubmit);
|
||||
|
||||
form.addEventListener('keydown', event => {
|
||||
if (event.ctrlKey && event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
handleEditFormSubmit(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function generateEditForm(data) {
|
||||
|
||||
function mapYearToLabel(yearValue) {
|
||||
if (yearValue == -1) return 'pre-2009';
|
||||
if (yearValue == -2) return 're-watch';
|
||||
if (yearValue == -3) return 'manga';
|
||||
return yearValue.toString();
|
||||
}
|
||||
|
||||
const selectedYearLabel = mapYearToLabel(data.year);
|
||||
|
||||
return `
|
||||
<h3>Edit Record</h3>
|
||||
<input type="hidden" name="id" value="${data.id}">
|
||||
<input type="hidden" name="year" id="year_numeric" value="${data.year}">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" name="name" id="name" class="form-control" value="${data.name || ''}" required>
|
||||
</div>
|
||||
|
||||
<!-- Wrap season, year, and type fields in a single container -->
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="season">Season:</label>
|
||||
<select name="season" id="season" class="form-control">
|
||||
${['winter', 'spring', 'summer', 'fall', 'omake'].map(season => `
|
||||
<option value="${season}" ${data.season === season ? 'selected' : ''}>
|
||||
${season.charAt(0).toUpperCase() + season.slice(1)}
|
||||
</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="year_label">Year:</label>
|
||||
<select name="year_label" id="year_label" class="form-control">
|
||||
${years.map(y => `
|
||||
<option value="${y}" ${y === selectedYearLabel ? 'selected' : ''}>${y}</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="type">Type:</label>
|
||||
<select name="type" id="type" class="form-control">
|
||||
${['tv', 'special', 'short', 'ova', 'movie', 'other'].map(type => `
|
||||
<option value="${type}" ${data.type === type ? 'selected' : ''}>${type}</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="comment">Comment:</label>
|
||||
<input type="text" name="comment" id="comment" class="form-control" value="${data.comment || ''}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="date_completed">Date Completed:</label>
|
||||
<input type="date" name="date_completed" id="date_completed" class="form-control" value="${data.date_completed || ''}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="url">URL:</label>
|
||||
<input type="url" name="url" id="url" class="form-control" value="${data.url || ''}">
|
||||
</div>
|
||||
<div class="form-group form-check">
|
||||
<input type="checkbox" name="is_completed" id="is_completed" class="form-check-input" ${parseInt(data.is_completed) ? 'checked' : ''}>
|
||||
<label for="is_completed" class="form-check-label">Is Completed</label>
|
||||
</div>
|
||||
<div class="form-group form-check">
|
||||
<input type="checkbox" name="currently_watching" id="currently_watching" class="form-check-input" ${parseInt(data.currently_watching) ? 'checked' : ''}>
|
||||
<label for="currently_watching" class="form-check-label">Currently Watching</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
function setupSuggestButton() {
|
||||
const suggestButton = document.getElementById('suggest-button');
|
||||
if (suggestButton) {
|
||||
suggestButton.addEventListener('click', () => {
|
||||
makeSuggestion();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function makeSuggestion() {
|
||||
// Clear any previous suggestions
|
||||
const previousSuggestion = document.getElementById('suggestion-display');
|
||||
if (previousSuggestion) {
|
||||
previousSuggestion.remove();
|
||||
}
|
||||
|
||||
// Get all table rows that are not completed
|
||||
const rows = Array.from(document.querySelectorAll('table tbody tr')).filter(row => {
|
||||
return !row.classList.contains('completed');
|
||||
});
|
||||
|
||||
// Exclude header rows and ensure there are at least two entries
|
||||
if (rows.length < 1) {
|
||||
alert('Not enough uncompleted entries to make a suggestion.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Randomly select two different rows
|
||||
const indices = [];
|
||||
while (indices.length < 1) {
|
||||
const index = Math.floor(Math.random() * rows.length);
|
||||
if (!indices.includes(index)) {
|
||||
indices.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
const selectedRows = [rows[indices[0]]];
|
||||
|
||||
// Get the names of the selected records
|
||||
const names = selectedRows.map(row => row.getAttribute('data-name'));
|
||||
|
||||
// Display the names at the top of the content
|
||||
const content = document.getElementById('content');
|
||||
const suggestionDisplayDiv = document.createElement('div');
|
||||
suggestionDisplayDiv.id = 'suggestion-display';
|
||||
suggestionDisplayDiv.innerHTML = `<p>${names.join('<br> ')}</p>`;
|
||||
content.insertBefore(suggestionDisplayDiv, content.querySelector('button')); // Insert before the add form
|
||||
}
|
||||
|
||||
function setupStickyTabs() {
|
||||
const tabsContainer = document.querySelector('.tabs-container');
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([e]) => e.target.classList.toggle('is-sticky', e.intersectionRatio < 1),
|
||||
{ threshold: [1] }
|
||||
);
|
||||
|
||||
observer.observe(tabsContainer);
|
||||
}
|
||||
|
||||
function setupSetCompleteButtons() {
|
||||
const setCompleteButtons = document.querySelectorAll('.set-complete-button');
|
||||
setCompleteButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const recordId = button.dataset.id;
|
||||
const formData = new FormData();
|
||||
formData.append('id', recordId);
|
||||
fetch('php/set_complete.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.json().then(errorData => {
|
||||
if (response.status === 403) {
|
||||
alert(errorData.message || 'Access denied: You are not authorized to perform this action.');
|
||||
}
|
||||
throw new Error(errorData.message || 'An error occurred while setting the record as complete.');
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(() => {
|
||||
// Reload the table to reflect the changes
|
||||
loadYearContent(currentYear);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setupSetCurrentlyWatchingButtons() {
|
||||
const setCurrentlyWatchingButtons = document.querySelectorAll('.set-currently-watching-button');
|
||||
setCurrentlyWatchingButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const recordId = button.dataset.id;
|
||||
const formData = new FormData();
|
||||
formData.append('id', recordId);
|
||||
fetch('php/set_currently_watching.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
return response.json().then(errorData => {
|
||||
if (response.status === 403) {
|
||||
alert(errorData.message || 'Access denied: You are not authorized to perform this action.');
|
||||
}
|
||||
throw new Error(errorData.message || 'An error occurred while setting the record as currently watching.');
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(() => {
|
||||
// Reload the table to reflect the changes
|
||||
loadYearContent(currentYear);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('DOM fully loaded and parsed');
|
||||
loadYearTabs();
|
||||
setupModal();
|
||||
var imageCache = {};
|
||||
var currentHoverTarget = null;
|
||||
|
||||
function showImageTooltip(event, imageUrl) {
|
||||
var tooltip = document.getElementById('image-tooltip');
|
||||
if (!tooltip) {
|
||||
tooltip = document.createElement('div');
|
||||
tooltip.id = 'image-tooltip';
|
||||
tooltip.style.position = 'absolute';
|
||||
tooltip.style.border = '1px solid #ccc';
|
||||
tooltip.style.background = '#fff';
|
||||
tooltip.style.padding = '5px';
|
||||
tooltip.style.zIndex = 1000;
|
||||
tooltip.style.maxWidth = '200px';
|
||||
tooltip.style.maxHeight = '300px';
|
||||
tooltip.style.overflow = 'hidden';
|
||||
tooltip.style.display = 'none';
|
||||
document.body.appendChild(tooltip);
|
||||
}
|
||||
|
||||
tooltip.innerHTML = '<img src="' + imageUrl + '" style="max-width: 100%; max-height: 100%;">';
|
||||
tooltip.style.left = (event.pageX + 15) + 'px';
|
||||
tooltip.style.top = (event.pageY + 15) + 'px';
|
||||
tooltip.style.display = 'block';
|
||||
}
|
||||
|
||||
function hideImageTooltip() {
|
||||
var tooltip = document.getElementById('image-tooltip');
|
||||
if (tooltip) {
|
||||
tooltip.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем обработчики событий на родительский элемент
|
||||
var contentDiv = document.getElementById('content');
|
||||
|
||||
contentDiv.addEventListener('mouseover', function (event) {
|
||||
var target = event.target;
|
||||
|
||||
// Проверяем, является ли целевой элемент ссылкой с атрибутом data-url
|
||||
if (target.tagName.toLowerCase() === 'a' && target.hasAttribute('data-url')) {
|
||||
var url = target.getAttribute('data-url');
|
||||
currentHoverTarget = target; // Set the current hover target
|
||||
|
||||
if (imageCache[url]) {
|
||||
showImageTooltip(event, imageCache[url]);
|
||||
} else {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', './php/get_image.php?url=' + encodeURIComponent(url), true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
if (response.image_url) {
|
||||
imageCache[url] = response.image_url;
|
||||
if (currentHoverTarget === target) {
|
||||
showImageTooltip(event, response.image_url);
|
||||
}
|
||||
} else if (response.error) {
|
||||
console.error(response.error);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
contentDiv.addEventListener('mouseout', function (event) {
|
||||
var target = event.target;
|
||||
|
||||
if (target.tagName.toLowerCase() === 'a' && target.hasAttribute('data-url')) {
|
||||
hideImageTooltip();
|
||||
if (currentHoverTarget === target) {
|
||||
currentHoverTarget = null; // Clear the current hover target
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
contentDiv.addEventListener('mousemove', function (event) {
|
||||
var tooltip = document.getElementById('image-tooltip');
|
||||
if (tooltip && tooltip.style.display === 'block') {
|
||||
tooltip.style.left = (event.pageX + 15) + 'px';
|
||||
tooltip.style.top = (event.pageY + 15) + 'px';
|
||||
}
|
||||
});
|
||||
});
|
68
php/add_record.php
Normal file
68
php/add_record.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
include_once 'db_connect.php';
|
||||
include_once 'check_allowed_ip.php';
|
||||
|
||||
$clientIP = $_SERVER['REMOTE_ADDR'];
|
||||
|
||||
if (!isAllowedIP($clientIP, $allowedSubnets)) {
|
||||
http_response_code(403); // Устанавливаем код ответа 403
|
||||
header('Content-Type: application/json'); // Указываем, что возвращаем JSON
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Access denied: Your IP is not authorized to add records.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$name = $_POST['name'];
|
||||
$year = $_POST['year'];
|
||||
$season = $_POST['season'];
|
||||
$type = $_POST['type'];
|
||||
$comment = $_POST['comment'];
|
||||
$is_completed = isset($_POST['is_completed']) ? 1 : 0;
|
||||
$currently_watching = isset($_POST['currently_watching']) ? 1 : 0;
|
||||
$date_completed = $_POST['date_completed'];
|
||||
$url = $_POST['url'];
|
||||
|
||||
// Handle empty date_completed
|
||||
if (empty($date_completed)) {
|
||||
$date_completed = NULL;
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("INSERT INTO anime_list (name, year, season, type, comment, is_completed, date_completed, url, currently_watching) VALUES (:name, :year, :season, :type, :comment, :is_completed, :date_completed, :url, :currently_watching)");
|
||||
$stmt->bindParam(':name', $name);
|
||||
$stmt->bindParam(':year', $year, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':season', $season);
|
||||
$stmt->bindParam(':type', $type);
|
||||
$stmt->bindParam(':comment', $comment);
|
||||
$stmt->bindParam(':is_completed', $is_completed, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':currently_watching', $currently_watching, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':url', $url);
|
||||
|
||||
// Use bindValue with PDO::PARAM_NULL if date_completed is NULL
|
||||
if ($date_completed === NULL) {
|
||||
$stmt->bindValue(':date_completed', NULL, PDO::PARAM_NULL);
|
||||
} else {
|
||||
$stmt->bindParam(':date_completed', $date_completed);
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
// Log the action
|
||||
// $action_time = new DateTime('now', new DateTimeZone('GMT+5'));
|
||||
// $action_time_formatted = $action_time->format('Y-m-d H:i:s');
|
||||
// $ip_address = $_SERVER['REMOTE_ADDR'];
|
||||
// $anime_name = $name;
|
||||
// $anime_year = $year;
|
||||
// $action_type = 'adding';
|
||||
|
||||
// $log_stmt = $conn->prepare("INSERT INTO action_logs (action_time, ip_address, anime_name, action_type, year) VALUES (:action_time, :ip_address, :anime_name, :action_type, :anime_year)");
|
||||
// $log_stmt->bindParam(':action_time', $action_time_formatted);
|
||||
// $log_stmt->bindParam(':ip_address', $ip_address);
|
||||
// $log_stmt->bindParam(':anime_name', $anime_name);
|
||||
// $log_stmt->bindParam(':action_type', $action_type);
|
||||
// $log_stmt->bindParam(':anime_year', $anime_year);
|
||||
// $log_stmt->execute();
|
||||
|
||||
?>
|
||||
|
26
php/check_allowed_ip.php
Normal file
26
php/check_allowed_ip.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
$allowedSubnets = [
|
||||
'192.168.1.0/24',
|
||||
'10.0.0.0/8',
|
||||
'172.16.0.0/12',
|
||||
'77.91.72.48/32',
|
||||
'93.140.0.0/12',
|
||||
'94.140.0.0/12'
|
||||
];
|
||||
|
||||
function isAllowedIP($ip, $subnets) {
|
||||
foreach ($subnets as $subnet) {
|
||||
list($subnetBase, $bits) = explode('/', $subnet);
|
||||
$ipLong = ip2long($ip);
|
||||
$subnetLong = ip2long($subnetBase);
|
||||
$mask = -1 << (32 - $bits);
|
||||
$subnetLong &= $mask;
|
||||
|
||||
if (($ipLong & $mask) === $subnetLong) {
|
||||
return true; // Совпадение найдено
|
||||
}
|
||||
}
|
||||
return false; // Нет совпадений
|
||||
}
|
||||
|
26
php/check_completed_year.php
Normal file
26
php/check_completed_year.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
include_once 'db_connect.php';
|
||||
|
||||
$year = $_GET['year'];
|
||||
|
||||
$yearMap = [
|
||||
'pre-2009' => -1,
|
||||
're-watch' => -2,
|
||||
'manga' => -3
|
||||
];
|
||||
|
||||
$yearValue = $yearMap[$year] ?? (int)$year;
|
||||
|
||||
// Count total records and completed records for that year
|
||||
$stmt = $conn->prepare("SELECT COUNT(*) AS total_records, SUM(is_completed) AS total_completed FROM anime_list WHERE year = :year");
|
||||
$stmt->bindParam(':year', $yearValue, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$all_completed = false;
|
||||
if ($row && $row['total_records'] > 0 && $row['total_completed'] == $row['total_records']) {
|
||||
$all_completed = true;
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['all_completed' => $all_completed]);
|
19
php/db_connect.example.php
Normal file
19
php/db_connect.example.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 0); // Set to 1 for debugging purposes
|
||||
$servername = "localhost";
|
||||
$username = "username";
|
||||
$password = "definetelysecurepwd123";
|
||||
$dbname = "plan_to_watch";
|
||||
|
||||
try {
|
||||
$conn = new PDO("mysql:host=$servername;dbname=$dbname;charset=utf8mb4", $username, $password);
|
||||
// Set PDO error mode to exception
|
||||
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch(PDOException $e) {
|
||||
echo "Connection failed: " . $e->getMessage();
|
||||
exit();
|
||||
}
|
||||
?>
|
52
php/delete_record.php
Normal file
52
php/delete_record.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
include_once 'db_connect.php';
|
||||
include_once 'check_allowed_ip.php';
|
||||
|
||||
$clientIP = $_SERVER['REMOTE_ADDR'];
|
||||
|
||||
if (!isAllowedIP($clientIP, $allowedSubnets)) {
|
||||
http_response_code(403); // Set response code to 403
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Access denied: Your IP is not authorized to delete records.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_GET['id'];
|
||||
|
||||
// Fetch the anime name and year before deleting
|
||||
$stmt = $conn->prepare("SELECT name, `year` FROM anime_list WHERE id = :id");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$anime = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$anime) {
|
||||
echo 'No anime found with id ' . htmlspecialchars($id);
|
||||
exit;
|
||||
}
|
||||
|
||||
$anime_name = $anime['name'];
|
||||
$anime_year = $anime['year'];
|
||||
|
||||
// Delete the record
|
||||
$stmt = $conn->prepare("DELETE FROM anime_list WHERE id = :id");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
// Log the action
|
||||
// $action_time = new DateTime('now', new DateTimeZone('GMT+5'));
|
||||
// $action_time_formatted = $action_time->format('Y-m-d H:i:s');
|
||||
// $ip_address = $_SERVER['REMOTE_ADDR'];
|
||||
// $action_type = 'deleting';
|
||||
|
||||
// $log_stmt = $conn->prepare("INSERT INTO action_logs (action_time, ip_address, anime_name, action_type, `year`) VALUES (:action_time, :ip_address, :anime_name, :action_type, :anime_year)");
|
||||
// $log_stmt->bindParam(':action_time', $action_time_formatted);
|
||||
// $log_stmt->bindParam(':ip_address', $ip_address);
|
||||
// $log_stmt->bindParam(':anime_name', $anime_name);
|
||||
// $log_stmt->bindParam(':action_type', $action_type);
|
||||
// $log_stmt->bindParam(':anime_year', $anime_year, PDO::PARAM_INT);
|
||||
// $log_stmt->execute();
|
||||
?>
|
||||
|
70
php/edit_record.php
Normal file
70
php/edit_record.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
include_once 'db_connect.php';
|
||||
include_once 'check_allowed_ip.php';
|
||||
|
||||
$clientIP = $_SERVER['REMOTE_ADDR'];
|
||||
|
||||
if (!isAllowedIP($clientIP, $allowedSubnets)) {
|
||||
http_response_code(403); // Устанавливаем код ответа 403
|
||||
header('Content-Type: application/json'); // Указываем, что возвращаем JSON
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Access denied: Your IP is not authorized to modify records.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_POST['id'];
|
||||
$name = $_POST['name'];
|
||||
$year = $_POST['year'];
|
||||
$season = $_POST['season'];
|
||||
$type = $_POST['type'];
|
||||
$comment = $_POST['comment'];
|
||||
$is_completed = isset($_POST['is_completed']) ? 1 : 0;
|
||||
$currently_watching = isset($_POST['currently_watching']) ? 1 : 0;
|
||||
$date_completed = $_POST['date_completed'];
|
||||
$url = $_POST['url'];
|
||||
|
||||
// Handle empty date_completed
|
||||
if (empty($date_completed)) {
|
||||
$date_completed = NULL;
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("UPDATE anime_list SET name = :name, year = :year, season = :season, type = :type, comment = :comment, is_completed = :is_completed, date_completed = :date_completed, url = :url, currently_watching = :currently_watching WHERE id = :id");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':name', $name);
|
||||
$stmt->bindParam(':year', $year, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':season', $season);
|
||||
$stmt->bindParam(':type', $type);
|
||||
$stmt->bindParam(':comment', $comment);
|
||||
$stmt->bindParam(':is_completed', $is_completed, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':currently_watching', $currently_watching, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':url', $url);
|
||||
|
||||
// Use bindValue with PDO::PARAM_NULL if date_completed is NULL
|
||||
if ($date_completed === NULL) {
|
||||
$stmt->bindValue(':date_completed', NULL, PDO::PARAM_NULL);
|
||||
} else {
|
||||
$stmt->bindParam(':date_completed', $date_completed);
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
// // Log the action
|
||||
// $action_time = new DateTime('now', new DateTimeZone('GMT+5'));
|
||||
// $action_time_formatted = $action_time->format('Y-m-d H:i:s');
|
||||
// $ip_address = $_SERVER['REMOTE_ADDR'];
|
||||
// $anime_name = $name;
|
||||
// $anime_year = $year;
|
||||
// $action_type = 'editing';
|
||||
|
||||
// $log_stmt = $conn->prepare("INSERT INTO action_logs (action_time, ip_address, anime_name, action_type, year) VALUES (:action_time, :ip_address, :anime_name, :action_type, :anime_year)");
|
||||
// $log_stmt->bindParam(':action_time', $action_time_formatted);
|
||||
// $log_stmt->bindParam(':ip_address', $ip_address);
|
||||
// $log_stmt->bindParam(':anime_name', $anime_name);
|
||||
// $log_stmt->bindParam(':anime_year', $anime_year);
|
||||
// $log_stmt->bindParam(':action_type', $action_type);
|
||||
// $log_stmt->execute();
|
||||
|
||||
?>
|
||||
|
90
php/fetch_data.php
Normal file
90
php/fetch_data.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
include_once 'db_connect.php';
|
||||
|
||||
$year = $_GET['year'] ?? 'pre-2009';
|
||||
|
||||
// Map special tabs to year values
|
||||
$yearMap = [
|
||||
'pre-2009' => -1,
|
||||
're-watch' => -2,
|
||||
'manga' => -3
|
||||
];
|
||||
|
||||
$yearValue = $yearMap[$year] ?? (int)$year;
|
||||
|
||||
// Seasons array to organize records
|
||||
$seasons = ['winter', 'spring', 'summer', 'fall', 'omake'];
|
||||
|
||||
// Fetch records from DB based on mode
|
||||
if ($yearValue == -1 || $yearValue == -2 || $yearValue == -3) {
|
||||
// For pre-2009, re-watch, or manga, we order by name (or differently if you prefer)
|
||||
$stmt = $conn->prepare("SELECT * FROM anime_list WHERE year = :year ORDER BY name ASC");
|
||||
} else {
|
||||
// Normal year mode
|
||||
$stmt = $conn->prepare("SELECT * FROM anime_list WHERE year = :year ORDER BY
|
||||
CASE
|
||||
WHEN season = 'winter' THEN 1
|
||||
WHEN season = 'spring' THEN 2
|
||||
WHEN season = 'summer' THEN 3
|
||||
WHEN season = 'fall' THEN 4
|
||||
WHEN season = 'omake' THEN 5
|
||||
ELSE 6
|
||||
END, name ASC");
|
||||
}
|
||||
$stmt->bindParam(':year', $yearValue, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$records = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($yearValue == -1 || $yearValue == -2) {
|
||||
$mode = 'non-season-table';
|
||||
} else if ($yearValue == -3) {
|
||||
$mode = 'manga';
|
||||
} else {
|
||||
$mode = 'normal';
|
||||
}
|
||||
|
||||
// If normal mode, group records by season
|
||||
$seasonRecords = [];
|
||||
if ($mode === 'normal') {
|
||||
// Initialize season arrays
|
||||
foreach ($seasons as $s) {
|
||||
$seasonRecords[$s] = [];
|
||||
}
|
||||
|
||||
// Place records into their respective season
|
||||
foreach ($records as $record) {
|
||||
$recordSeason = strtolower($record['season']);
|
||||
if (in_array($recordSeason, $seasons)) {
|
||||
$seasonRecords[$recordSeason][] = $record;
|
||||
} else {
|
||||
// If season is unknown, treat as 'omake'
|
||||
$seasonRecords['omake'][] = $record;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$yearTotal = count($records);
|
||||
$yearCompleted = 0;
|
||||
foreach ($records as $r) {
|
||||
if ($r['is_completed']) {
|
||||
$yearCompleted++;
|
||||
}
|
||||
}
|
||||
$yearPercent = $yearTotal > 0 ? round(($yearCompleted / $yearTotal) * 100) : 0;
|
||||
|
||||
// Prepare data for the view
|
||||
$viewData = [
|
||||
'year' => $year,
|
||||
'yearValue' => $yearValue,
|
||||
'mode' => $mode, // either 'non-season-table' or 'normal'
|
||||
'records' => $records,
|
||||
'seasonRecords' => $seasonRecords ?? [],
|
||||
'seasons' => $seasons,
|
||||
'showSuggestButton' => ($mode === 'normal'),
|
||||
'yearTotal' => $yearTotal,
|
||||
'yearCompleted' => $yearCompleted,
|
||||
'yearPercent' => $yearPercent
|
||||
];
|
||||
|
||||
// Include the view
|
||||
include '../tmpl/fetch_data_view.php';
|
12
php/fetch_record.php
Normal file
12
php/fetch_record.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
include_once 'db_connect.php';
|
||||
|
||||
$id = $_GET['id'];
|
||||
|
||||
$stmt = $conn->prepare("SELECT * FROM anime_list WHERE id = :id");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$record = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode($record);
|
||||
?>
|
136
php/get_image.php
Normal file
136
php/get_image.php
Normal file
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
// Включение отображения ошибок для отладки (удалите или закомментируйте в продакшене)
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($_GET['url'])) {
|
||||
echo json_encode(['error' => 'No URL provided']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$url = $_GET['url'];
|
||||
|
||||
if (strpos($url, 'myanimelist.net') === false) {
|
||||
echo json_encode(['error' => 'Invalid URL']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Используем абсолютные пути для директорий кэша
|
||||
$cache_dir = __DIR__ . '/cache/images/';
|
||||
$cache_meta_dir = __DIR__ . '/cache/meta/';
|
||||
|
||||
// Проверяем и создаём директории, если они не существуют
|
||||
if (!is_dir($cache_dir)) {
|
||||
if (!mkdir($cache_dir, 0755, true)) {
|
||||
echo json_encode(['error' => 'Failed to create images cache directory']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_dir($cache_meta_dir)) {
|
||||
if (!mkdir($cache_meta_dir, 0755, true)) {
|
||||
echo json_encode(['error' => 'Failed to create meta cache directory']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$hash = md5($url);
|
||||
$cache_meta_file = $cache_meta_dir . $hash . '.json';
|
||||
$cache_image_file = $cache_dir . $hash . '.jpg'; // Предполагаем, что изображения в формате JPG
|
||||
|
||||
// Проверяем, существует ли кэшированное изображение
|
||||
if (file_exists($cache_image_file)) {
|
||||
// Возвращаем путь к локальному изображению
|
||||
$image_url_local = '/plan-to-watch/php/cache/images/' . $hash . '.jpg';
|
||||
echo json_encode(['image_url' => $image_url_local]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Если кэшированного изображения нет, получаем URL изображения
|
||||
// Используем cURL для получения страницы
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (X11; Linux x86_64; rv:132.0) Gecko/20100101 Firefox/132.0');
|
||||
$html = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($html === false) {
|
||||
echo json_encode(['error' => 'Could not fetch the page']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
libxml_use_internal_errors(true);
|
||||
$doc->loadHTML($html);
|
||||
libxml_clear_errors();
|
||||
|
||||
$xpath = new DOMXPath($doc);
|
||||
|
||||
// Ищем мета-тег og:image
|
||||
$image_nodes = $xpath->query("//meta[@property='og:image']");
|
||||
if ($image_nodes->length > 0) {
|
||||
$image_url = $image_nodes->item(0)->getAttribute('content');
|
||||
|
||||
// Проверяем, получили ли мы URL изображения
|
||||
if (!empty($image_url)) {
|
||||
// Скачиваем изображение
|
||||
$image_data = file_get_contents($image_url);
|
||||
if ($image_data === false) {
|
||||
echo json_encode(['error' => 'Could not download the image']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Определяем тип изображения
|
||||
$image_info = getimagesizefromstring($image_data);
|
||||
if ($image_info === false) {
|
||||
echo json_encode(['error' => 'Invalid image data']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Определяем расширение файла
|
||||
$mime = $image_info['mime'];
|
||||
switch ($mime) {
|
||||
case 'image/jpeg':
|
||||
$extension = '.jpg';
|
||||
break;
|
||||
case 'image/png':
|
||||
$extension = '.png';
|
||||
break;
|
||||
case 'image/gif':
|
||||
$extension = '.gif';
|
||||
break;
|
||||
default:
|
||||
echo json_encode(['error' => 'Unsupported image type']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Обновляем путь к кэшированному изображению с правильным расширением
|
||||
$cache_image_file = $cache_dir . $hash . $extension;
|
||||
$image_url_local = '/plan-to-watch/php/cache/images/' . $hash . $extension;
|
||||
|
||||
// Сохраняем изображение на сервере
|
||||
if (file_put_contents($cache_image_file, $image_data) === false) {
|
||||
echo json_encode(['error' => 'Failed to save the image']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Сохраняем метаданные (опционально)
|
||||
$meta_data = json_encode(['image_url' => $image_url_local]);
|
||||
if (file_put_contents($cache_meta_file, $meta_data) === false) {
|
||||
echo json_encode(['error' => 'Failed to save meta data']);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['image_url' => $image_url_local]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(['error' => 'Image not found']);
|
||||
exit;
|
||||
?>
|
||||
|
55
php/get_logs.php
Normal file
55
php/get_logs.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
include_once 'db_connect.php';
|
||||
include_once 'check_allowed_ip.php';
|
||||
|
||||
$clientIP = $_SERVER['REMOTE_ADDR'];
|
||||
|
||||
if (!isAllowedIP($clientIP, $allowedSubnets)) {
|
||||
http_response_code(403);
|
||||
die("Access denied for {$clientIP}");
|
||||
}
|
||||
|
||||
// Generate CSRF token if not set
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
$csrf_token = $_SESSION['csrf_token'];
|
||||
|
||||
// Handle clear logs action
|
||||
if (isset($_GET['action']) && $_GET['action'] === 'clear_logs') {
|
||||
// Verify CSRF token
|
||||
if (!isset($_GET['token']) || $_GET['token'] !== $_SESSION['csrf_token']) {
|
||||
// Invalid CSRF token
|
||||
die('Invalid CSRF token');
|
||||
}
|
||||
|
||||
// Clear the logs
|
||||
$stmt = $conn->prepare("TRUNCATE TABLE action_logs");
|
||||
$stmt->execute();
|
||||
|
||||
// Regenerate CSRF token
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
|
||||
// Redirect back to panel.php
|
||||
header("Location: panel.php");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Handle pagination parameters passed from logs.php
|
||||
$per_page = isset($_GET['per_page']) ? (int)$_GET['per_page'] : 10;
|
||||
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||
if ($page < 1) $page = 1;
|
||||
|
||||
// Count total logs
|
||||
$count_stmt = $conn->query("SELECT COUNT(*) AS total FROM action_logs");
|
||||
$total_logs = (int)$count_stmt->fetchColumn();
|
||||
|
||||
// Calculate offset
|
||||
$offset = ($page - 1) * $per_page;
|
||||
|
||||
// Fetch logs with LIMIT and OFFSET
|
||||
$stmt = $conn->prepare("SELECT * FROM action_logs ORDER BY action_time DESC LIMIT :limit OFFSET :offset");
|
||||
$stmt->bindValue(':limit', $per_page, PDO::PARAM_INT);
|
||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$logs = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
28
php/get_options.php
Normal file
28
php/get_options.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
include_once 'db_connect.php'; // ensure you have a PDO $conn
|
||||
|
||||
// Check if form is submitted
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_options'])) {
|
||||
// Get checkbox value, 'on' if checked, otherwise null
|
||||
$autoAdd = isset($_POST['auto_add_completed_date']) ? '1' : '0';
|
||||
|
||||
// Update the option in the database
|
||||
$stmt = $conn->prepare("INSERT INTO options (option_name, option_value) VALUES ('auto_add_completed_date', :val)
|
||||
ON DUPLICATE KEY UPDATE option_value = :val");
|
||||
$stmt->bindParam(':val', $autoAdd, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
|
||||
// Redirect to panel.php to avoid form resubmission
|
||||
header("Location: panel.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Fetch the current option value
|
||||
$stmt = $conn->prepare("SELECT option_value FROM options WHERE option_name = 'auto_add_completed_date'");
|
||||
$stmt->execute();
|
||||
$autoAddCompletedDate = $stmt->fetchColumn();
|
||||
|
||||
// If not found in DB, default to '0'
|
||||
if ($autoAddCompletedDate === false) {
|
||||
$autoAddCompletedDate = '0';
|
||||
}
|
66
php/get_stats.php
Normal file
66
php/get_stats.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
function getDirectorySize($dir) {
|
||||
$size = 0;
|
||||
|
||||
if (is_dir($dir)) {
|
||||
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)) as $file) {
|
||||
$size += $file->getSize();
|
||||
}
|
||||
}
|
||||
return $size;
|
||||
}
|
||||
|
||||
// Функция для подсчета количества файлов в директории
|
||||
function getFileCount($dir) {
|
||||
$count = 0;
|
||||
|
||||
if (is_dir($dir)) {
|
||||
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS)) as $file) {
|
||||
if ($file->isFile()) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
// Определяем абсолютные пути к директориям кэша для расчета статистики
|
||||
$cache_images_dir = __DIR__ . '/cache/images/';
|
||||
$cache_meta_dir = __DIR__ . '/cache/meta/';
|
||||
|
||||
// Расчет размера кэша (только изображения)
|
||||
$cache_size_bytes = getDirectorySize($cache_images_dir);
|
||||
$cache_size_mb = round($cache_size_bytes / (1024 * 1024), 2);
|
||||
|
||||
// Подсчет количества изображений в кэше
|
||||
$image_count = getFileCount($cache_images_dir);
|
||||
|
||||
// Fetch statistics
|
||||
$stats = [];
|
||||
|
||||
// Total number of entries
|
||||
$query = "SELECT COUNT(*) AS total_entries FROM anime_list";
|
||||
$stats['total_entries'] = $conn->query($query)->fetch(PDO::FETCH_ASSOC)['total_entries'];
|
||||
|
||||
// Total number of completed anime
|
||||
$query = "SELECT COUNT(*) AS completed_anime FROM anime_list WHERE is_completed = 1";
|
||||
$stats['completed_anime'] = $conn->query($query)->fetch(PDO::FETCH_ASSOC)['completed_anime'];
|
||||
|
||||
// Count by type
|
||||
$query = "SELECT type, COUNT(*) AS count FROM anime_list GROUP BY type";
|
||||
$stats['by_type'] = $conn->query($query)->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Most recent completion date
|
||||
$query = "SELECT MAX(date_completed) AS most_recent_completion FROM anime_list WHERE date_completed IS NOT NULL";
|
||||
$stats['most_recent_completion'] = $conn->query($query)->fetch(PDO::FETCH_ASSOC)['most_recent_completion'];
|
||||
|
||||
// DB size
|
||||
$query = "SELECT SUM(data_length + index_length) AS size FROM information_schema.TABLES WHERE table_schema = :dbname";
|
||||
$stmt = $conn->prepare($query);
|
||||
$stmt->execute(['dbname' => $dbname]);
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$size_in_bytes = $row['size'];
|
||||
$size_in_kilobytes = $size_in_bytes / 1024;
|
||||
|
||||
?>
|
11
php/options_helper.php
Normal file
11
php/options_helper.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
include_once 'db_connect.php';
|
||||
|
||||
function getOptionValue($conn, $optionName, $default = '0') {
|
||||
$stmt = $conn->prepare("SELECT option_value FROM options WHERE option_name = :option_name");
|
||||
$stmt->bindParam(':option_name', $optionName, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
$value = $stmt->fetchColumn();
|
||||
return $value !== false ? $value : $default;
|
||||
}
|
||||
|
49
php/panel.php
Normal file
49
php/panel.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
include_once 'db_connect.php'; // Ensure session and DB are available
|
||||
include_once 'check_allowed_ip.php';
|
||||
include 'get_options.php';
|
||||
|
||||
$clientIP = $_SERVER['REMOTE_ADDR'];
|
||||
|
||||
if (!isAllowedIP($clientIP, $allowedSubnets)) {
|
||||
http_response_code(403);
|
||||
die("Access denied for {$clientIP}");
|
||||
}
|
||||
|
||||
// Default per_page and allowed values
|
||||
$allowed_per_page = [5, 10, 50, 100];
|
||||
$default_per_page = 10;
|
||||
|
||||
// Get the chosen per_page from GET, default if not valid
|
||||
$per_page = isset($_GET['per_page']) && in_array((int)$_GET['per_page'], $allowed_per_page)
|
||||
? (int)$_GET['per_page']
|
||||
: $default_per_page;
|
||||
|
||||
// Get the current page from GET, default to 1 if not valid
|
||||
$page = isset($_GET['page']) && (int)$_GET['page'] > 0 ? (int)$_GET['page'] : 1;
|
||||
|
||||
// Pass these parameters to get_logs.php for the query
|
||||
$_GET['per_page'] = $per_page;
|
||||
$_GET['page'] = $page;
|
||||
|
||||
include 'get_logs.php';
|
||||
include 'get_stats.php';
|
||||
|
||||
// Prepare data for the view
|
||||
$viewData = [
|
||||
'per_page' => $per_page,
|
||||
'page' => $page,
|
||||
'allowed_per_page' => $allowed_per_page,
|
||||
'logs' => $logs,
|
||||
'total_logs' => $total_logs,
|
||||
'csrf_token' => $csrf_token,
|
||||
'stats' => $stats,
|
||||
'size_in_kilobytes' => $size_in_kilobytes,
|
||||
'cache_size_mb' => $cache_size_mb,
|
||||
'image_count' => $image_count,
|
||||
'autoAddCompletedDate' => $autoAddCompletedDate
|
||||
];
|
||||
|
||||
|
||||
include '../tmpl/panel_view.php';
|
67
php/set_complete.php
Normal file
67
php/set_complete.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
include_once 'db_connect.php';
|
||||
include_once 'check_allowed_ip.php';
|
||||
include 'options_helper.php';
|
||||
|
||||
$clientIP = $_SERVER['REMOTE_ADDR'];
|
||||
|
||||
if (!isAllowedIP($clientIP, $allowedSubnets)) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Access denied: Your IP is not authorized to modify records.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_POST['id'];
|
||||
|
||||
// Fetch the anime name and year for logging
|
||||
$stmt = $conn->prepare("SELECT name, year FROM anime_list WHERE id = :id");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$record = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$record) {
|
||||
http_response_code(404);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Record not found.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$anime_name = $record['name'];
|
||||
$anime_year = $record['year'];
|
||||
|
||||
// Update the record to set is_completed to 1
|
||||
|
||||
$auto_add_complelete_date = getOptionValue($conn, 'auto_add_completed_date', '0');
|
||||
if ($auto_add_complelete_date == 1) {
|
||||
$current_date = date('Y-m-d');
|
||||
$stmt = $conn->prepare("UPDATE anime_list SET is_completed = 1, date_completed = :current_date WHERE id = :id");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':current_date', $current_date);
|
||||
} else {
|
||||
$stmt = $conn->prepare("UPDATE anime_list SET is_completed = 1 WHERE id = :id");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
}
|
||||
$stmt->execute();
|
||||
|
||||
// Log the action
|
||||
$action_time = new DateTime('now', new DateTimeZone('GMT+5'));
|
||||
$action_time_formatted = $action_time->format('Y-m-d H:i:s');
|
||||
$ip_address = $_SERVER['REMOTE_ADDR'];
|
||||
$action_type = 'set_complete';
|
||||
|
||||
$log_stmt = $conn->prepare("INSERT INTO action_logs (action_time, ip_address, anime_name, action_type, year) VALUES (:action_time, :ip_address, :anime_name, :action_type, :anime_year)");
|
||||
$log_stmt->bindParam(':action_time', $action_time_formatted);
|
||||
$log_stmt->bindParam(':ip_address', $ip_address);
|
||||
$log_stmt->bindParam(':anime_name', $anime_name);
|
||||
$log_stmt->bindParam(':anime_year', $anime_year);
|
||||
$log_stmt->bindParam(':action_type', $action_type);
|
||||
$log_stmt->execute();
|
||||
|
||||
echo json_encode(['status' => 'success']);
|
||||
?>
|
60
php/set_currently_watching.php
Normal file
60
php/set_currently_watching.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
include_once 'db_connect.php';
|
||||
include_once 'check_allowed_ip.php';
|
||||
|
||||
$clientIP = $_SERVER['REMOTE_ADDR'];
|
||||
|
||||
if (!isAllowedIP($clientIP, $allowedSubnets)) {
|
||||
http_response_code(403);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Access denied: Your IP is not authorized to modify records.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = $_POST['id'];
|
||||
|
||||
// Fetch the current state of currently_watching
|
||||
$stmt = $conn->prepare("SELECT name, year, currently_watching FROM anime_list WHERE id = :id");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$record = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$record) {
|
||||
http_response_code(404);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'message' => 'Record not found.'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$anime_name = $record['name'];
|
||||
$anime_year = $record['year'];
|
||||
$currentState = (int)$record['currently_watching'];
|
||||
|
||||
// Toggle currently_watching
|
||||
$newState = $currentState === 1 ? 0 : 1;
|
||||
|
||||
$stmt = $conn->prepare("UPDATE anime_list SET currently_watching = :newState WHERE id = :id");
|
||||
$stmt->bindParam(':newState', $newState, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
// Log the action
|
||||
// $action_time = new DateTime('now', new DateTimeZone('GMT+5'));
|
||||
// $action_time_formatted = $action_time->format('Y-m-d H:i:s');
|
||||
// $ip_address = $_SERVER['REMOTE_ADDR'];
|
||||
// $action_type = $newState === 1 ? 'set_currently_watching' : 'unset_currently_watching';
|
||||
|
||||
// $log_stmt = $conn->prepare("INSERT INTO action_logs (action_time, ip_address, anime_name, action_type, year) VALUES (:action_time, :ip_address, :anime_name, :action_type, :anime_year)");
|
||||
// $log_stmt->bindParam(':action_time', $action_time_formatted);
|
||||
// $log_stmt->bindParam(':ip_address', $ip_address);
|
||||
// $log_stmt->bindParam(':anime_name', $anime_name);
|
||||
// $log_stmt->bindParam(':anime_year', $anime_year);
|
||||
// $log_stmt->bindParam(':action_type', $action_type);
|
||||
// $log_stmt->execute();
|
||||
|
||||
echo json_encode(['status' => 'success']);
|
84
schema.sql
Normal file
84
schema.sql
Normal file
@ -0,0 +1,84 @@
|
||||
-- MySQL dump 10.19 Distrib 10.3.39-MariaDB, for debian-linux-gnu (x86_64)
|
||||
--
|
||||
-- Host: localhost Database: plan_to_watch
|
||||
-- ------------------------------------------------------
|
||||
-- Server version 10.3.39-MariaDB-0+deb10u2
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
|
||||
--
|
||||
-- Table structure for table `action_logs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `action_logs`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `action_logs` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`action_time` datetime DEFAULT NULL,
|
||||
`ip_address` varchar(45) DEFAULT NULL,
|
||||
`anime_name` varchar(255) DEFAULT NULL,
|
||||
`action_type` varchar(50) DEFAULT NULL,
|
||||
`year` int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `anime_list`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `anime_list`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `anime_list` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`year` int(11) DEFAULT NULL,
|
||||
`season` varchar(50) DEFAULT NULL,
|
||||
`type` varchar(50) DEFAULT NULL,
|
||||
`comment` text DEFAULT NULL,
|
||||
`is_completed` tinyint(1) DEFAULT 0,
|
||||
`date_completed` date DEFAULT NULL,
|
||||
`url` varchar(255) DEFAULT NULL,
|
||||
`currently_watching` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`table_type` varchar(20) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=985 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
--
|
||||
-- Table structure for table `options`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `options`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `options` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`option_name` varchar(255) NOT NULL,
|
||||
`option_value` varchar(255) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `option_name` (`option_name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
-- Dump completed on 2025-01-06 22:57:21
|
391
tmpl/fetch_data_view.php
Normal file
391
tmpl/fetch_data_view.php
Normal file
@ -0,0 +1,391 @@
|
||||
<?php
|
||||
extract($viewData);
|
||||
?>
|
||||
|
||||
<h2><?php echo htmlspecialchars($year); ?></h2>
|
||||
|
||||
<!-- Display year stats -->
|
||||
<p>
|
||||
Total entries in <?php echo $year; ?>: <strong><?php echo $yearTotal; ?></strong>, Completed: <strong><?php echo $yearCompleted; ?> (<?php echo $yearPercent; ?>%)</strong>
|
||||
</p>
|
||||
|
||||
<?php
|
||||
if ($showSuggestButton) {
|
||||
echo '<button id="suggest-button">Suggest</button>';
|
||||
}
|
||||
|
||||
if ($mode === 'non-season-table') {
|
||||
if (!empty($records)) {
|
||||
renderPre2009Table($records, $yearTotal, $yearCompleted, $yearPercent);
|
||||
} else {
|
||||
echo '<p>No records found.</p>';
|
||||
}
|
||||
renderAddRecordForm($yearValue, $seasons);
|
||||
|
||||
} else if ($mode === 'manga') {
|
||||
if (!empty($records)) {
|
||||
renderMangaTable($records, $yearTotal, $yearCompleted, $yearPercent);
|
||||
}
|
||||
else {
|
||||
echo '<p>No records found.</p>';
|
||||
}
|
||||
renderAddMangaForm($yearValue, $seasons);
|
||||
}
|
||||
else {
|
||||
renderSeasonTables($seasons, $seasonRecords);
|
||||
renderAddRecordForm($yearValue, $seasons);
|
||||
}
|
||||
|
||||
// Renders table for pre-2009 records
|
||||
function renderPre2009Table($records, $yearTotal, $yearCompleted, $yearPercent) {
|
||||
// Since pre-2009 mode doesn't have seasons, we've already shown year stats above.
|
||||
?>
|
||||
<table class="record-table">
|
||||
<colgroup>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Comment</th>
|
||||
<th>Date Completed</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $rowNumber = 1; ?>
|
||||
<?php foreach ($records as $record): ?>
|
||||
<?php
|
||||
$rowClasses = [];
|
||||
if ($record['is_completed']) {
|
||||
$rowClasses[] = 'completed';
|
||||
}
|
||||
if (!empty($record['currently_watching']) && $record['currently_watching']) {
|
||||
$rowClasses[] = 'watching';
|
||||
}
|
||||
$classAttr = !empty($rowClasses) ? ' class="'.implode(' ', $rowClasses).'"' : '';
|
||||
?>
|
||||
<tr <?php echo $classAttr; ?> data-id="<?php echo $record['id']; ?>" data-name="<?php echo htmlspecialchars($record['name'], ENT_QUOTES); ?>">
|
||||
<td><?php echo $rowNumber++; ?></td>
|
||||
<td>
|
||||
<?php if (!empty($record['url'])): ?>
|
||||
<a href="<?php echo htmlspecialchars($record['url']); ?>" target="_blank" data-url="<?php echo htmlspecialchars($record['url'], ENT_QUOTES); ?>">
|
||||
<?php echo htmlspecialchars($record['name']); ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?php echo htmlspecialchars($record['name']); ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo htmlspecialchars($record['type']); ?></td>
|
||||
<td><?php echo htmlspecialchars($record['comment']); ?></td>
|
||||
<td><?php echo htmlspecialchars($record['date_completed']); ?></td>
|
||||
<td>
|
||||
<?php if (!$record['is_completed']): ?>
|
||||
<button class="set-complete-button" data-id="<?php echo $record['id']; ?>">+</button>
|
||||
<?php endif; ?>
|
||||
<button class="set-currently-watching-button" data-id="<?php echo $record['id']; ?>">W</button>
|
||||
<button class="edit-button" data-id="<?php echo $record['id']; ?>">Edit</button>
|
||||
<button class="delete-button" data-id="<?php echo $record['id']; ?>">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Renders table for manga
|
||||
function renderMangaTable($records, $yearTotal, $yearCompleted, $yearPercent) {
|
||||
// Since pre-2009 mode doesn't have seasons, we've already shown year stats above.
|
||||
?>
|
||||
<table class="record-table" id="manga">
|
||||
<colgroup>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
<col>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Name</th>
|
||||
<th>Comment</th>
|
||||
<th>Date Completed</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $rowNumber = 1; ?>
|
||||
<?php foreach ($records as $record): ?>
|
||||
<?php
|
||||
$rowClasses = [];
|
||||
if ($record['is_completed']) {
|
||||
$rowClasses[] = 'completed';
|
||||
}
|
||||
if (!empty($record['currently_watching']) && $record['currently_watching']) {
|
||||
$rowClasses[] = 'watching';
|
||||
}
|
||||
$classAttr = !empty($rowClasses) ? ' class="'.implode(' ', $rowClasses).'"' : '';
|
||||
?>
|
||||
<tr <?php echo $classAttr; ?> data-id="<?php echo $record['id']; ?>" data-name="<?php echo htmlspecialchars($record['name'], ENT_QUOTES); ?>">
|
||||
<td><?php echo $rowNumber++; ?></td>
|
||||
<td>
|
||||
<?php if (!empty($record['url'])): ?>
|
||||
<a href="<?php echo htmlspecialchars($record['url']); ?>" target="_blank" data-url="<?php echo htmlspecialchars($record['url'], ENT_QUOTES); ?>">
|
||||
<?php echo htmlspecialchars($record['name']); ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?php echo htmlspecialchars($record['name']); ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo htmlspecialchars($record['comment']); ?></td>
|
||||
<td><?php echo htmlspecialchars($record['date_completed']); ?></td>
|
||||
<td>
|
||||
<?php if (!$record['is_completed']): ?>
|
||||
<button class="set-complete-button" data-id="<?php echo $record['id']; ?>">+</button>
|
||||
<?php endif; ?>
|
||||
<button class="set-currently-watching-button" data-id="<?php echo $record['id']; ?>">CR</button>
|
||||
<button class="edit-button" data-id="<?php echo $record['id']; ?>">Edit</button>
|
||||
<button class="delete-button" data-id="<?php echo $record['id']; ?>">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Renders season tables for normal mode
|
||||
function renderSeasonTables($seasons, $seasonRecords) {
|
||||
foreach ($seasons as $season) {
|
||||
// Compute season stats:
|
||||
$seasonRecordsThis = $seasonRecords[$season] ?? [];
|
||||
$seasonTotal = count($seasonRecordsThis);
|
||||
$seasonCompleted = 0;
|
||||
foreach ($seasonRecordsThis as $r) {
|
||||
if ($r['is_completed']) {
|
||||
$seasonCompleted++;
|
||||
}
|
||||
}
|
||||
$seasonPercent = $seasonTotal > 0 ? round(($seasonCompleted / $seasonTotal) * 100) : 0;
|
||||
|
||||
echo '<h3>' . ucfirst($season) . ' ' . get_season_emoji($season) . '</h3>';
|
||||
// Display season stats:
|
||||
echo "<p>Completed: <strong>$seasonCompleted ($seasonPercent%)</strong></p>";
|
||||
|
||||
if (!empty($seasonRecordsThis)) {
|
||||
?>
|
||||
<table class="record-table">
|
||||
<colgroup>
|
||||
<col><col><col><col><col><col>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Comment</th>
|
||||
<th>Date Completed</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $rowNumber = 1; ?>
|
||||
<?php foreach ($seasonRecordsThis as $record): ?>
|
||||
<?php
|
||||
$rowClasses = [];
|
||||
if ($record['is_completed']) {
|
||||
$rowClasses[] = 'completed';
|
||||
}
|
||||
if (!empty($record['currently_watching']) && $record['currently_watching']) {
|
||||
$rowClasses[] = 'watching';
|
||||
}
|
||||
$classAttr = !empty($rowClasses) ? ' class="' . implode(' ', $rowClasses) . '"' : '';
|
||||
?>
|
||||
<tr<?php echo $classAttr; ?> data-id="<?php echo $record['id']; ?>" data-name="<?php echo htmlspecialchars($record['name'], ENT_QUOTES); ?>">
|
||||
<td><?php echo $rowNumber++; ?></td>
|
||||
<td>
|
||||
<?php if (!empty($record['url'])): ?>
|
||||
<a href="<?php echo htmlspecialchars($record['url']); ?>" target="_blank" data-url="<?php echo htmlspecialchars($record['url'], ENT_QUOTES); ?>">
|
||||
<?php echo htmlspecialchars($record['name']); ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?php echo htmlspecialchars($record['name']); ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td><?php echo htmlspecialchars($record['type']); ?></td>
|
||||
<td><?php echo htmlspecialchars($record['comment']); ?></td>
|
||||
<td><?php echo htmlspecialchars($record['date_completed']); ?></td>
|
||||
<td>
|
||||
<?php if (!$record['is_completed']): ?>
|
||||
<button class="set-complete-button" data-id="<?php echo $record['id']; ?>">+</button>
|
||||
<?php endif; ?>
|
||||
<button class="set-currently-watching-button" data-id="<?php echo $record['id']; ?>">CW</button>
|
||||
<button class="edit-button" data-id="<?php echo $record['id']; ?>">Edit</button>
|
||||
<button class="delete-button" data-id="<?php echo $record['id']; ?>">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
} else {
|
||||
echo '<p>No records for this season.</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderAddMangaForm($yearValue, $seasons) {
|
||||
?>
|
||||
<form id="add-form" class="form-container-horizontal">
|
||||
<h3>Add New Record</h3>
|
||||
<input type="hidden" name="year" value="<?php echo htmlspecialchars($yearValue); ?>">
|
||||
<div class="form-row">
|
||||
<?php if ($yearValue == -1 || $yearValue == -2 || $yearValue == -3): ?>
|
||||
<input type="hidden" name="season" value="N/A">
|
||||
<div class="form-group">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" name="name" id="name" class="form-control" required>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="form-group">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" name="name" id="name" class="form-control" required>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div> <!-- End of first form row -->
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="comment">Comment:</label>
|
||||
<input type="text" name="comment" id="comment" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="date_completed">Date Completed:</label>
|
||||
<input type="date" name="date_completed" id="date_completed" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="url">URL:</label>
|
||||
<input type="url" name="url" id="url" class="form-control">
|
||||
</div>
|
||||
</div> <!-- End of second form row -->
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group form-check">
|
||||
<input type="checkbox" name="is_completed" id="is_completed" class="form-check-input">
|
||||
<label for="is_completed" class="form-check-label">Is Completed</label>
|
||||
</div>
|
||||
<div class="form-group form-check">
|
||||
<input type="checkbox" name="currently_watching" id="currently_watching" class="form-check-input">
|
||||
<label for="currently_watching" class="form-check-label">Currently reading</label>
|
||||
</div>
|
||||
</div> <!-- End of third form row -->
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">Add Record</button>
|
||||
</div>
|
||||
</div> <!-- End of fourth form row -->
|
||||
</form>
|
||||
<div><a href="./php/panel.php">Control panel</a> | <a href="/docs/animu-tree.txt">Downloaded anime</a></div>
|
||||
<?php
|
||||
}
|
||||
|
||||
function renderAddRecordForm($yearValue, $seasons) {
|
||||
?>
|
||||
<form id="add-form" class="form-container-horizontal">
|
||||
<h3>Add New Record</h3>
|
||||
<input type="hidden" name="year" value="<?php echo htmlspecialchars($yearValue); ?>">
|
||||
<div class="form-row">
|
||||
<?php if ($yearValue == -1 || $yearValue == -2 || $yearValue == -3): ?>
|
||||
<input type="hidden" name="season" value="N/A">
|
||||
<div class="form-group">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" name="name" id="name" class="form-control" required>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="form-group">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" name="name" id="name" class="form-control" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="season">Season:</label>
|
||||
<select name="season" id="season" class="form-control">
|
||||
<?php foreach ($seasons as $season): ?>
|
||||
<option value="<?php echo $season; ?>"><?php echo ucfirst($season); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="form-group">
|
||||
<label for="type">Type:</label>
|
||||
<select name="type" id="type" class="form-control">
|
||||
<option value="tv">TV</option>
|
||||
<option value="special">Special</option>
|
||||
<option value="short">Short</option>
|
||||
<option value="ova">OVA</option>
|
||||
<option value="movie">Movie</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
</div> <!-- End of first form row -->
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="comment">Comment:</label>
|
||||
<input type="text" name="comment" id="comment" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="date_completed">Date Completed:</label>
|
||||
<input type="date" name="date_completed" id="date_completed" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="url">URL:</label>
|
||||
<input type="url" name="url" id="url" class="form-control">
|
||||
</div>
|
||||
</div> <!-- End of second form row -->
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group form-check">
|
||||
<input type="checkbox" name="is_completed" id="is_completed" class="form-check-input">
|
||||
<label for="is_completed" class="form-check-label">Is Completed</label>
|
||||
</div>
|
||||
<div class="form-group form-check">
|
||||
<input type="checkbox" name="currently_watching" id="currently_watching" class="form-check-input">
|
||||
<label for="currently_watching" class="form-check-label">Currently Watching</label>
|
||||
</div>
|
||||
</div> <!-- End of third form row -->
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">Add Record</button>
|
||||
</div>
|
||||
</div> <!-- End of fourth form row -->
|
||||
</form>
|
||||
<div><a href="./php/panel.php">Control panel</a> | <a href="/docs/animu-tree.txt">Downloaded anime</a></div>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
||||
function get_season_emoji($season) {
|
||||
switch($season) {
|
||||
case 'winter':
|
||||
return '❄️';
|
||||
case 'spring':
|
||||
return '🌱';
|
||||
case 'summer':
|
||||
return '☀️';
|
||||
case 'fall':
|
||||
return '🍂';
|
||||
default:
|
||||
return '🎇';
|
||||
}
|
||||
}
|
187
tmpl/panel_view.php
Normal file
187
tmpl/panel_view.php
Normal file
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
// panel_view.php
|
||||
// Extract all the variables we need from $viewData
|
||||
extract($viewData);
|
||||
|
||||
function get_anime_year($raw_year) {
|
||||
switch($raw_year) {
|
||||
case -1:
|
||||
return 'pre-2009';
|
||||
case -2:
|
||||
return 're-watch';
|
||||
case -3:
|
||||
return 'manga';
|
||||
default:
|
||||
return $raw_year;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate pagination details
|
||||
$total_pages = ($total_logs > 0) ? ceil($total_logs / $per_page) : 1;
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Control Panel</title>
|
||||
<style>
|
||||
/* Existing styles */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
th {
|
||||
background-color: #ddd;
|
||||
text-align: left;
|
||||
}
|
||||
/* Style for the links */
|
||||
.action-links {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.action-links a {
|
||||
margin-right: 15px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
.clear-logs-link {
|
||||
color: red;
|
||||
}
|
||||
/* Pagination styles */
|
||||
.pagination {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.pagination a {
|
||||
margin: 0 5px;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
.pagination span.current-page {
|
||||
font-weight: bold;
|
||||
margin: 0 5px;
|
||||
}
|
||||
/* Per-page form */
|
||||
.per-page-form {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function confirmClearLogs() {
|
||||
return confirm('Are you sure you want to clear all logs?');
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Action Logs</h1>
|
||||
|
||||
<div class="action-links">
|
||||
<!-- Link to refresh logs -->
|
||||
<a href="panel.php?per_page=<?php echo $per_page; ?>" class="refresh-logs-link">Refresh</a>
|
||||
|
||||
<!-- Link to clear logs -->
|
||||
<a href="panel.php?action=clear_logs&token=<?php echo $csrf_token; ?>&per_page=<?php echo $per_page; ?>" class="clear-logs-link" onclick="return confirmClearLogs();">Clear Logs</a>
|
||||
</div>
|
||||
|
||||
<!-- Per-page selection form -->
|
||||
<form class="per-page-form" method="get" action="panel.php">
|
||||
<label for="per_page">Show per page:</label>
|
||||
<select name="per_page" id="per_page">
|
||||
<?php foreach ($allowed_per_page as $option): ?>
|
||||
<option value="<?php echo $option; ?>" <?php if ($option == $per_page) echo 'selected'; ?>><?php echo $option; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<button type="submit">Apply</button>
|
||||
</form>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Date and Time (GMT+5)</th>
|
||||
<th>IP Address</th>
|
||||
<th>Anime Name</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
<?php if (count($logs) > 0): ?>
|
||||
<?php foreach ($logs as $log): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($log['action_time']); ?></td>
|
||||
<td><?php echo htmlspecialchars($log['ip_address']); ?></td>
|
||||
<td>
|
||||
<?php
|
||||
echo htmlspecialchars($log['anime_name']);
|
||||
echo ' (' . get_anime_year(htmlspecialchars($log['year'])) . ')';
|
||||
?>
|
||||
</td>
|
||||
<td><?php echo htmlspecialchars($log['action_type']); ?></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<tr>
|
||||
<td colspan="4">No logs available.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</table>
|
||||
<hr>
|
||||
|
||||
<!-- Pagination Links -->
|
||||
<?php if ($total_logs > 0 && $total_pages > 1): ?>
|
||||
<div class="pagination">
|
||||
<?php if ($page > 1): ?>
|
||||
<a href="panel.php?page=<?php echo $page - 1; ?>&per_page=<?php echo $per_page; ?>">« Prev</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
|
||||
<?php if ($i == $page): ?>
|
||||
<span class="current-page"><?php echo $i; ?></span>
|
||||
<?php else: ?>
|
||||
<a href="panel.php?page=<?php echo $i; ?>&per_page=<?php echo $per_page; ?>"><?php echo $i; ?></a>
|
||||
<?php endif; ?>
|
||||
<?php endfor; ?>
|
||||
|
||||
<?php if ($page < $total_pages): ?>
|
||||
<a href="panel.php?page=<?php echo $page + 1; ?>&per_page=<?php echo $per_page; ?>">Next »</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h1>Stats</h1>
|
||||
<p><strong>Total Entries:</strong> <?php echo $stats['total_entries']; ?></p>
|
||||
<p><strong>Completed:</strong> <?php echo $stats['completed_anime']; ?> (<?php echo ceil(($stats['completed_anime'] / $stats['total_entries']) * 100); ?>%)</p>
|
||||
<p><strong>Database size: </strong><?php echo number_format($size_in_kilobytes, 2); ?> Kb.</p>
|
||||
<p><strong>Image cache size: </strong><?php echo $cache_size_mb; ?>Mb.</p>
|
||||
<p><strong>Number of cached images: </strong><?php echo $image_count; ?></p>
|
||||
|
||||
<h3>Count by Type</h3>
|
||||
<?php if (!empty($stats['by_type'])): ?>
|
||||
<ul>
|
||||
<?php foreach ($stats['by_type'] as $type): ?>
|
||||
<li><?php echo htmlspecialchars($type['type'] ?: 'Unknown'); ?>: <?php echo $type['count']; ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
|
||||
<p><strong>Most Recent Completion Date:</strong>
|
||||
<?php if ($stats['most_recent_completion']):
|
||||
$date = new DateTime($stats['most_recent_completion']);
|
||||
echo $date->format('F j, Y');
|
||||
else:
|
||||
echo "No date found.";
|
||||
endif; ?>
|
||||
</p>
|
||||
|
||||
<h1>Options</h1>
|
||||
<form method="post" action="panel.php">
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox" name="auto_add_completed_date" <?php if ($autoAddCompletedDate === '1') echo 'checked'; ?>>
|
||||
Automatically add current date when anime is completed
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" name="update_options">Save</button>
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user