1st restructure
This commit is contained in:
26
app/static/color_mode.js
Normal file
26
app/static/color_mode.js
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const themeToggle = document.getElementById('bd-theme');
|
||||
|
||||
// Check if a theme preference is saved in localStorage
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
|
||||
if (savedTheme === 'dark') {
|
||||
themeToggle.checked = true;
|
||||
document.documentElement.setAttribute('data-bs-theme', 'dark');
|
||||
} else {
|
||||
themeToggle.checked = false;
|
||||
document.documentElement.setAttribute('data-bs-theme', 'light');
|
||||
}
|
||||
|
||||
// Add event listener to toggle theme on checkbox change
|
||||
themeToggle.addEventListener('change', () => {
|
||||
if (themeToggle.checked) {
|
||||
document.documentElement.setAttribute('data-bs-theme', 'dark');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-bs-theme', 'light');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
});
|
||||
104
app/static/download_results.js
Normal file
104
app/static/download_results.js
Normal file
@@ -0,0 +1,104 @@
|
||||
async function deleteFiles(filePaths) {
|
||||
try {
|
||||
const response = await fetch('/delete_files', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ file_paths: filePaths })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert('Files deleted successfully');
|
||||
location.reload();
|
||||
} else {
|
||||
alert(`Error deleting files: ${JSON.stringify(data.errors)}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('An error occurred while deleting files.');
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectedFiles() {
|
||||
return Array.from(document.querySelectorAll('input[name="fileCheckbox"]:checked'))
|
||||
.map(checkbox => checkbox.value);
|
||||
}
|
||||
|
||||
function deleteSelectedFiles() {
|
||||
const selectedFiles = getSelectedFiles();
|
||||
if (selectedFiles.length > 0) {
|
||||
deleteFiles(selectedFiles);
|
||||
} else {
|
||||
alert('No files selected');
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadSelectedFiles() {
|
||||
const selectedFiles = getSelectedFiles();
|
||||
if (selectedFiles.length === 0) {
|
||||
alert('No files selected');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/download_files', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ file_paths: selectedFiles })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || 'Failed to download files.');
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
if (blob.type !== 'application/zip') {
|
||||
throw new Error('Received invalid ZIP file.');
|
||||
}
|
||||
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'files.zip';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error('Download error:', error);
|
||||
alert(`Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function sortTable(columnIndex, tableId) {
|
||||
const table = document.getElementById(tableId);
|
||||
const tbody = table.querySelector('tbody');
|
||||
const rows = Array.from(tbody.rows);
|
||||
const isAscending = table.dataset.sortAsc === 'true';
|
||||
|
||||
rows.sort((rowA, rowB) => {
|
||||
const cellA = rowA.cells[columnIndex].innerText.trim().toLowerCase();
|
||||
const cellB = rowB.cells[columnIndex].innerText.trim().toLowerCase();
|
||||
return cellA.localeCompare(cellB) * (isAscending ? 1 : -1);
|
||||
});
|
||||
|
||||
// Toggle sorting order for next click
|
||||
table.dataset.sortAsc = !isAscending;
|
||||
|
||||
// Reinsert sorted rows
|
||||
rows.forEach(row => tbody.appendChild(row));
|
||||
}
|
||||
|
||||
function checkAllCheckboxes(tableId, checkAllCheckboxId) {
|
||||
const table = document.getElementById(tableId);
|
||||
const checkboxes = table.querySelectorAll('input[name="fileCheckbox"]');
|
||||
const checkAllCheckbox = document.getElementById(checkAllCheckboxId);
|
||||
|
||||
checkboxes.forEach(checkbox => checkbox.checked = checkAllCheckbox.checked);
|
||||
}
|
||||
144
app/static/index.js
Normal file
144
app/static/index.js
Normal file
@@ -0,0 +1,144 @@
|
||||
class LogScraperApp {
|
||||
constructor() {
|
||||
this.form = document.getElementById('scrapingForm');
|
||||
this.stopButton = document.getElementById('stopButton');
|
||||
this.logsElement = document.getElementById('logs');
|
||||
this.prevPageButton = document.getElementById('prevPage');
|
||||
this.nextPageButton = document.getElementById('nextPage');
|
||||
this.pageInfo = document.getElementById('pageInfo');
|
||||
this.startButton = document.getElementById('startButton');
|
||||
|
||||
this.currentPage = 0;
|
||||
this.linesPerPage = null;
|
||||
this.autoRefreshInterval = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.fetchConfig();
|
||||
await this.checkScrapingStatus();
|
||||
this.addEventListeners();
|
||||
}
|
||||
|
||||
async fetchConfig() {
|
||||
try {
|
||||
const response = await fetch('/config/lines_per_page');
|
||||
const data = await response.json();
|
||||
this.linesPerPage = data.lines_per_page;
|
||||
this.fetchLogs(this.currentPage);
|
||||
} catch (error) {
|
||||
console.error('Error fetching config:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchLogs(page) {
|
||||
try {
|
||||
const response = await fetch(`/logfile?page=${page}&lines_per_page=${this.linesPerPage}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
this.logsElement.textContent = data.error;
|
||||
} else {
|
||||
this.logsElement.innerHTML = data.log.map((line, index) => {
|
||||
const lineNumber = data.start_line - index;
|
||||
return `<span class="line-number">${lineNumber}</span> ${line}`;
|
||||
}).join('');
|
||||
|
||||
this.updatePagination(data.total_lines);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching logs:', error);
|
||||
}
|
||||
}
|
||||
|
||||
updatePagination(totalLines) {
|
||||
this.prevPageButton.disabled = this.currentPage === 0;
|
||||
this.nextPageButton.disabled = (this.currentPage + 1) * this.linesPerPage >= totalLines;
|
||||
this.pageInfo.textContent = `Page ${this.currentPage + 1} of ${Math.ceil(totalLines / this.linesPerPage)}`;
|
||||
}
|
||||
|
||||
startAutoRefresh() {
|
||||
this.autoRefreshInterval = setInterval(() => this.fetchLogs(this.currentPage), 5000);
|
||||
}
|
||||
|
||||
stopAutoRefresh() {
|
||||
clearInterval(this.autoRefreshInterval);
|
||||
}
|
||||
|
||||
async checkScrapingStatus() {
|
||||
try {
|
||||
const response = await fetch('/scraping_status');
|
||||
const data = await response.json();
|
||||
if (data.scraping_active) {
|
||||
this.startButton.disabled = true;
|
||||
this.stopButton.disabled = false;
|
||||
this.startAutoRefresh();
|
||||
} else {
|
||||
this.startButton.disabled = false;
|
||||
this.stopButton.disabled = true;
|
||||
}
|
||||
this.fetchLogs(this.currentPage);
|
||||
} catch (error) {
|
||||
console.error('Error checking scraping status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async startScraping(event) {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(this.form);
|
||||
try {
|
||||
const response = await fetch('/start_scraping', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
console.log(data);
|
||||
if (data.status === "Scraping started") {
|
||||
this.startButton.disabled = true;
|
||||
this.stopButton.disabled = false;
|
||||
this.startAutoRefresh();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error starting scraping:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async stopScraping() {
|
||||
try {
|
||||
const response = await fetch('/stop_scraping', { method: 'POST' });
|
||||
const data = await response.json();
|
||||
|
||||
console.log(data);
|
||||
if (data.status === "Scraping stopped") {
|
||||
this.startButton.disabled = false;
|
||||
this.stopButton.disabled = true;
|
||||
this.stopAutoRefresh();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error stopping scraping:', error);
|
||||
}
|
||||
}
|
||||
|
||||
addEventListeners() {
|
||||
this.prevPageButton.addEventListener('click', () => {
|
||||
if (this.currentPage > 0) {
|
||||
this.currentPage--;
|
||||
this.fetchLogs(this.currentPage);
|
||||
}
|
||||
});
|
||||
|
||||
this.nextPageButton.addEventListener('click', () => {
|
||||
this.currentPage++;
|
||||
this.fetchLogs(this.currentPage);
|
||||
});
|
||||
|
||||
this.form.addEventListener('submit', (event) => this.startScraping(event));
|
||||
|
||||
this.stopButton.addEventListener('click', () => this.stopScraping());
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the application when DOM is fully loaded
|
||||
document.addEventListener('DOMContentLoaded', () => new LogScraperApp());
|
||||
217
app/static/style.css
Normal file
217
app/static/style.css
Normal file
@@ -0,0 +1,217 @@
|
||||
/* LIGHT MODE (default) */
|
||||
:root {
|
||||
--bs-body-bg: #f8f9fa; /* Light background */
|
||||
--bs-body-color: #212529; /* Dark text */
|
||||
|
||||
--bs-primary: #007bff;
|
||||
--bs-primary-bg-subtle: #cce5ff;
|
||||
--bs-primary-border-subtle: #80bdff;
|
||||
--bs-primary-text-emphasis: #004085;
|
||||
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-secondary-border-subtle: #c8cbcf;
|
||||
--bs-secondary-text-emphasis: #383d41;
|
||||
|
||||
--bs-success: #198754;
|
||||
--bs-success-bg-subtle: #d4edda;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-success-text-emphasis: #155724;
|
||||
|
||||
--bs-danger: #dc3545;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-danger-text-emphasis: #721c24;
|
||||
|
||||
--bs-warning: #ffc107;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-warning-border-subtle: #ffeeba;
|
||||
--bs-warning-text-emphasis: #856404;
|
||||
|
||||
--bs-info: #17a2b8;
|
||||
--bs-info-bg-subtle: #d1ecf1;
|
||||
--bs-info-border-subtle: #bee5eb;
|
||||
--bs-info-text-emphasis: #0c5460;
|
||||
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-light-bg-subtle: #ffffff;
|
||||
--bs-light-border-subtle: #d6d8db;
|
||||
--bs-light-text-emphasis: #6c757d;
|
||||
|
||||
--bs-dark: #343a40;
|
||||
--bs-dark-bg-subtle: #212529;
|
||||
--bs-dark-border-subtle: #1d2124;
|
||||
--bs-dark-text-emphasis: #ffffff;
|
||||
|
||||
--bs-border-color: #dee2e6; /* Default border color */
|
||||
}
|
||||
|
||||
/* DARK MODE */
|
||||
[data-bs-theme="dark"] {
|
||||
--bs-body-bg: #121212;
|
||||
--bs-body-color: #e9ecef;
|
||||
|
||||
--bs-primary: #1e90ff;
|
||||
--bs-primary-bg-subtle: #1c2b36;
|
||||
--bs-primary-border-subtle: #374b58;
|
||||
--bs-primary-text-emphasis: #a0c4ff;
|
||||
|
||||
--bs-secondary: #adb5bd;
|
||||
--bs-secondary-bg-subtle: #2d3238;
|
||||
--bs-secondary-border-subtle: #3e444a;
|
||||
--bs-secondary-text-emphasis: #ced4da;
|
||||
|
||||
--bs-success: #00c851;
|
||||
--bs-success-bg-subtle: #1b3425;
|
||||
--bs-success-border-subtle: #3b6147;
|
||||
--bs-success-text-emphasis: #b9f6ca;
|
||||
|
||||
--bs-danger: #ff4444;
|
||||
--bs-danger-bg-subtle: #381717;
|
||||
--bs-danger-border-subtle: #633030;
|
||||
--bs-danger-text-emphasis: #ffcccb;
|
||||
|
||||
--bs-warning: #ffbb33;
|
||||
--bs-warning-bg-subtle: #3a2b19;
|
||||
--bs-warning-border-subtle: #67512e;
|
||||
--bs-warning-text-emphasis: #ffd700;
|
||||
|
||||
--bs-info: #33b5e5;
|
||||
--bs-info-bg-subtle: #182e38;
|
||||
--bs-info-border-subtle: #305564;
|
||||
--bs-info-text-emphasis: #66d1ff;
|
||||
|
||||
--bs-light: #343a40;
|
||||
--bs-light-bg-subtle: #2c3137;
|
||||
--bs-light-border-subtle: #464b50;
|
||||
--bs-light-text-emphasis: #e9ecef;
|
||||
|
||||
--bs-dark: #ffffff;
|
||||
--bs-dark-bg-subtle: #f8f9fa;
|
||||
--bs-dark-border-subtle: #e9ecef;
|
||||
--bs-dark-text-emphasis: #121212;
|
||||
|
||||
--bs-border-color: #495057;
|
||||
}
|
||||
|
||||
|
||||
[data-bs-theme="dark"] .shadow {
|
||||
box-shadow: var(--bs-box-shadow) !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .shadow-sm {
|
||||
box-shadow: var(--bs-box-shadow-sm) !important;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .shadow-lg {
|
||||
box-shadow: var(--bs-box-shadow-lg) !important;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bs-primary: var(--primary);
|
||||
--bs-secondary: var(--secondary);
|
||||
--bs-body-bg: var(--background);
|
||||
--bs-body-color: var(--text-color);
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] {
|
||||
--bs-primary: var(--primary);
|
||||
--bs-secondary: var(--secondary);
|
||||
--bs-body-bg: var(--background);
|
||||
--bs-body-color: var(--text-color);
|
||||
}
|
||||
|
||||
|
||||
/* Dark Mode Toggle Button */
|
||||
/* Hide the default checkbox */
|
||||
#color-mode-toggle input[type=checkbox] {
|
||||
height: 0;
|
||||
width: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* Style the switch */
|
||||
#color-mode-toggle label {
|
||||
cursor: pointer;
|
||||
width: 70px;
|
||||
height: 30px;
|
||||
background: grey;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-radius: 30px;
|
||||
position: relative;
|
||||
padding: 5px 15px;
|
||||
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* The moving toggle circle */
|
||||
#color-mode-toggle label:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
/* Sun and Moon Icons */
|
||||
.icon {
|
||||
font-size: 15px;
|
||||
position: absolute;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
/* Position Sun on the left */
|
||||
.sun {
|
||||
left: 10px;
|
||||
/* color: var(--bs-dark) */
|
||||
color: var(--sun-color);
|
||||
}
|
||||
|
||||
/* Position Moon on the right */
|
||||
.moon {
|
||||
right: 10px;
|
||||
/* color: var(--bs-light); */
|
||||
color: var(--sun-color);
|
||||
}
|
||||
|
||||
/* Move the toggle circle when checked */
|
||||
#color-mode-toggle input:checked + label {
|
||||
background: var(--bs-light);
|
||||
}
|
||||
|
||||
#color-mode-toggle input:checked + label:after {
|
||||
left: calc(100% - 25px);
|
||||
background: var(--bs-dark);
|
||||
}
|
||||
|
||||
/* Hide moon when in dark mode */
|
||||
#color-mode-toggle input:checked + label .sun {
|
||||
opacity: 100;
|
||||
}
|
||||
|
||||
#color-mode-toggle input:checked + label .moon {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Hide sun when in light mode */
|
||||
#color-mode-toggle input:not(:checked) + label .moon {
|
||||
opacity: 100;
|
||||
}
|
||||
|
||||
#color-mode-toggle input:not(:checked) + label .sun {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.line-number {
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
text-align: right;
|
||||
margin-right: 10px;
|
||||
color: #888;
|
||||
}
|
||||
Reference in New Issue
Block a user