adds activity indicators in header (ugly af)
This commit is contained in:
@@ -1,64 +1,22 @@
|
|||||||
|
import { ScraperUtils } from './scraper_utils.js';
|
||||||
|
|
||||||
class Common {
|
class Common {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.activityIndicator = document.getElementById('activity_indicator');
|
this.utils = new ScraperUtils();
|
||||||
this.endTimeElement = document.getElementById('end_time');
|
this.addEventListeners();
|
||||||
this.serverTimeElement = document.getElementById('server_time');
|
this.scheduleUpdates();
|
||||||
this.init();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
scheduleUpdates() {
|
||||||
this.updateServerTime();
|
// Ensure server time updates every minute but only after initial fetch
|
||||||
setInterval(() => this.updateServerTime(), 60000); // Update every minute
|
setTimeout(() => {
|
||||||
this.checkScrapingStatus();
|
setInterval(() => this.utils.updateServerTime(), 60000);
|
||||||
|
}, 5000); // Delay first scheduled update to prevent duplicate initial request
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchEndTime() {
|
addEventListeners() {
|
||||||
try {
|
if (this.utils.stopButton) {
|
||||||
const response = await fetch('/scraping_get_end_time');
|
this.utils.stopButton.addEventListener('click', () => this.utils.checkScrapingStatus());
|
||||||
const data = await response.json();
|
|
||||||
if (data.end_time) {
|
|
||||||
const endTime = new Date(data.end_time);
|
|
||||||
const localEndTime = endTime.toLocaleString();
|
|
||||||
this.endTimeElement.textContent = `End Time: ${endTime} TCT`;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.endTimeElement.textContent = 'Error fetching end time';
|
|
||||||
console.error('Error fetching end time:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateServerTime() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/server_time');
|
|
||||||
const data = await response.json();
|
|
||||||
this.serverTimeElement.textContent = `Server Time (UTC): ${data.server_time}`;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching server time:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async checkScrapingStatus() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/scraping_status');
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.scraping_active) {
|
|
||||||
this.activityIndicator.classList.remove('text-bg-danger');
|
|
||||||
this.activityIndicator.classList.add('text-bg-success');
|
|
||||||
this.activityIndicator.textContent = 'Active';
|
|
||||||
|
|
||||||
this.endTimeElement.classList.remove('d-none');
|
|
||||||
this.endTimeElement.textContent = data.end_time;
|
|
||||||
await this.fetchEndTime();
|
|
||||||
} else {
|
|
||||||
this.activityIndicator.classList.remove('text-bg-success');
|
|
||||||
this.activityIndicator.classList.add('text-bg-danger');
|
|
||||||
this.activityIndicator.textContent = 'Inactive';
|
|
||||||
|
|
||||||
this.endTimeElement.textContent = '';
|
|
||||||
this.endTimeElement.classList.add('d-none');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error checking scraping status:', error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,10 +25,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
new Common();
|
new Common();
|
||||||
});
|
});
|
||||||
|
|
||||||
function checkAllCheckboxes(tableId, checkAllCheckboxId) {
|
window.checkAllCheckboxes = function(tableId, checkAllId) {
|
||||||
const table = document.getElementById(tableId);
|
var table = document.getElementById(tableId);
|
||||||
const checkboxes = table.querySelectorAll('input[type="checkbox"]:not(:disabled)');
|
var checkAll = document.getElementById(checkAllId);
|
||||||
const checkAllCheckbox = document.getElementById(checkAllCheckboxId);
|
var checkboxes = table.querySelectorAll('input[type="checkbox"]');
|
||||||
|
|
||||||
checkboxes.forEach(checkbox => checkbox.checked = checkAllCheckbox.checked);
|
checkboxes.forEach(function(checkbox) {
|
||||||
}
|
if (!checkbox.disabled) {
|
||||||
|
checkbox.checked = checkAll.checked;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,36 +1,21 @@
|
|||||||
|
import { ScraperUtils } from './scraper_utils.js';
|
||||||
|
|
||||||
class ScraperApp {
|
class ScraperApp {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.utils = new ScraperUtils();
|
||||||
this.form = document.getElementById('scrapingForm');
|
this.form = document.getElementById('scrapingForm');
|
||||||
this.stopButton = document.getElementById('stopButton');
|
this.stopButton = document.getElementById('stopButton');
|
||||||
this.startButton = document.getElementById('startButton');
|
this.startButton = document.getElementById('startButton');
|
||||||
this.activityIndicator = document.getElementById('activity_indicator');
|
|
||||||
this.endTimeElement = document.getElementById('end_time');
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.checkScrapingStatusIndex();
|
this.utils.checkScrapingStatus();
|
||||||
this.addEventListeners();
|
this.addEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkScrapingStatusIndex() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/scraping_status');
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.scraping_active) {
|
|
||||||
this.startButton.disabled = true;
|
|
||||||
this.stopButton.disabled = false;
|
|
||||||
} else {
|
|
||||||
this.startButton.disabled = false;
|
|
||||||
this.stopButton.disabled = true;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error checking scraping status:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async startScraping(event) {
|
async startScraping(event) {
|
||||||
event.preventDefault(); // Prevent the default form submission
|
event.preventDefault(); // Prevent default form submission
|
||||||
const formData = new FormData(this.form);
|
const formData = new FormData(this.form);
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/start_scraping', {
|
const response = await fetch('/start_scraping', {
|
||||||
@@ -39,7 +24,7 @@ class ScraperApp {
|
|||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.status === "Scraping started") {
|
if (data.status === "Scraping started") {
|
||||||
this.checkScrapingStatusIndex();
|
this.utils.checkScrapingStatus(); // Update UI
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error starting scraping:', error);
|
console.error('Error starting scraping:', error);
|
||||||
@@ -53,7 +38,7 @@ class ScraperApp {
|
|||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.status === "Scraping stopped") {
|
if (data.status === "Scraping stopped") {
|
||||||
this.checkScrapingStatusIndex();
|
this.utils.checkScrapingStatus(); // Update UI
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error stopping scraping:', error);
|
console.error('Error stopping scraping:', error);
|
||||||
@@ -66,7 +51,6 @@ class ScraperApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the application when DOM is fully loaded
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
new ScraperApp();
|
new ScraperApp();
|
||||||
});
|
});
|
||||||
180
app/static/scraper_utils.js
Normal file
180
app/static/scraper_utils.js
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
export class ScraperUtils {
|
||||||
|
constructor() {
|
||||||
|
this.activityIndicator = document.getElementById('activity_indicator');
|
||||||
|
this.endTimeElement = document.getElementById('end_time');
|
||||||
|
this.serverTimeElement = document.getElementById('server_time');
|
||||||
|
this.timeLeftElement = document.getElementById('time-left'); // New element for countdown
|
||||||
|
this.stopButton = document.getElementById('stopButton');
|
||||||
|
this.startButton = document.getElementById('startButton');
|
||||||
|
this.statusContainer = document.getElementById('status_container');
|
||||||
|
this.loadingIndicator = document.getElementById('loading_indicator');
|
||||||
|
this.statusContent = document.querySelectorAll('#status_content');
|
||||||
|
|
||||||
|
this.serverTime = null;
|
||||||
|
this.endTime = null;
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.showLoadingIndicator();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ensure each function runs only once
|
||||||
|
await Promise.all([
|
||||||
|
this.updateServerTime(),
|
||||||
|
this.checkScrapingStatus()
|
||||||
|
]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during initialization:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure end time is fetched only if scraping is active
|
||||||
|
if (this.endTime === null) {
|
||||||
|
try {
|
||||||
|
await this.fetchEndTime();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching end time:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure UI is only updated once everything is ready
|
||||||
|
if (this.serverTime && this.endTime) {
|
||||||
|
this.startClock();
|
||||||
|
this.hideLoadingIndicator();
|
||||||
|
} else {
|
||||||
|
console.warn("Delaying hiding the loading indicator due to missing data...");
|
||||||
|
const checkDataInterval = setInterval(() => {
|
||||||
|
if (this.serverTime && this.endTime) {
|
||||||
|
clearInterval(checkDataInterval);
|
||||||
|
this.startClock();
|
||||||
|
this.hideLoadingIndicator();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
showLoadingIndicator() {
|
||||||
|
this.statusContainer.classList.remove('d-none');
|
||||||
|
this.loadingIndicator.classList.remove('d-none');
|
||||||
|
this.statusContent.forEach(element => element.classList.add('d-none'));
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLoadingIndicator() {
|
||||||
|
this.loadingIndicator.classList.add('d-none');
|
||||||
|
this.statusContent.forEach(element => element.classList.remove('d-none'));
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkScrapingStatus() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/scraping_status');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.scraping_active) {
|
||||||
|
if (this.startButton) this.startButton.disabled = true;
|
||||||
|
if (this.stopButton) this.stopButton.disabled = false;
|
||||||
|
|
||||||
|
this.activityIndicator.classList.remove('text-bg-danger');
|
||||||
|
this.activityIndicator.classList.add('text-bg-success');
|
||||||
|
this.activityIndicator.textContent = 'Active';
|
||||||
|
|
||||||
|
console.log(`Scraping is active until ${data.end_time} TCT`);
|
||||||
|
|
||||||
|
// Only call fetchEndTime() if endTime is not already set
|
||||||
|
if (!this.endTime) {
|
||||||
|
await this.fetchEndTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.endTimeElement.classList.remove('d-none');
|
||||||
|
this.timeLeftElement.classList.remove('d-none');
|
||||||
|
} else {
|
||||||
|
if (this.startButton) this.startButton.disabled = false;
|
||||||
|
if (this.stopButton) this.stopButton.disabled = true;
|
||||||
|
|
||||||
|
this.activityIndicator.classList.remove('text-bg-success');
|
||||||
|
this.activityIndicator.classList.add('text-bg-danger');
|
||||||
|
this.activityIndicator.textContent = 'Inactive';
|
||||||
|
|
||||||
|
this.endTimeElement.classList.add('d-none');
|
||||||
|
this.timeLeftElement.classList.add('d-none');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking scraping status:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateServerTime() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/server_time');
|
||||||
|
const data = await response.json();
|
||||||
|
this.serverTime = new Date(data.server_time.replace(' ', 'T'));
|
||||||
|
|
||||||
|
this.serverTimeElement.textContent = `Server Time (TCT): ${this.formatDateToHHMMSS(this.serverTime)}`;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching server time:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async fetchEndTime() {
|
||||||
|
if (this.endTime) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/scraping_get_end_time');
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.end_time) {
|
||||||
|
this.endTime = new Date(data.end_time);
|
||||||
|
this.endTimeElement.textContent = `Running until ${this.formatDateToYYYYMMDDHHMMSS(this.endTime)} TCT`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.endTimeElement.textContent = 'Error fetching end time';
|
||||||
|
console.error('Error fetching end time:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startClock() {
|
||||||
|
const updateClock = () => {
|
||||||
|
if (this.serverTime) {
|
||||||
|
this.serverTime.setSeconds(this.serverTime.getSeconds() + 1);
|
||||||
|
this.serverTimeElement.textContent = `Server Time (TCT): ${this.formatDateToHHMMSS(this.serverTime)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.endTime && this.serverTime) {
|
||||||
|
const timeLeft = this.endTime - this.serverTime;
|
||||||
|
this.timeLeftElement.textContent = `Time Left: ${timeLeft > 0 ? this.formatMillisecondsToHHMMSS(timeLeft) : '00:00:00'}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Immediately update the clock
|
||||||
|
updateClock();
|
||||||
|
|
||||||
|
// Continue updating every second
|
||||||
|
setInterval(updateClock, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDateToYYYYMMDDHHMMSS(date) {
|
||||||
|
if (!(date instanceof Date) || isNaN(date)) {
|
||||||
|
console.error('Invalid date:', date);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ` +
|
||||||
|
`${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDateToHHMMSS(date) {
|
||||||
|
if (!(date instanceof Date) || isNaN(date)) {
|
||||||
|
console.error('Invalid date:', date);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatMillisecondsToHHMMSS(ms) {
|
||||||
|
const totalSeconds = Math.floor(ms / 1000);
|
||||||
|
const hours = Math.floor(totalSeconds / 3600);
|
||||||
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||||
|
const seconds = totalSeconds % 60;
|
||||||
|
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<table id="logFilesTable" class="table table-striped table-bordered table-hover">
|
<table id="logFilesTable" class="table table-striped table-bordered table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th width="2%"><input type="checkbox" id="checkAllLog" onclick="checkAllCheckboxes('logFilesTable', 'checkAllLog')"></th>
|
<th width="2%"><input type="checkbox" id="checkAllLog" class="form-check-input" onclick="checkAllCheckboxes('logFilesTable', 'checkAllLog')"></th>
|
||||||
<th onclick="sortTable(1, 'logFilesTable')">File Name</th>
|
<th onclick="sortTable(1, 'logFilesTable')">File Name</th>
|
||||||
<th onclick="sortTable(2, 'logFilesTable')">Last Modified</th>
|
<th onclick="sortTable(2, 'logFilesTable')">Last Modified</th>
|
||||||
<th onclick="sortTable(3, 'logFilesTable')">Created</th>
|
<th onclick="sortTable(3, 'logFilesTable')">Created</th>
|
||||||
|
|||||||
@@ -15,9 +15,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div id="status_container" class="container-fluid d-flex justify-content-center">
|
<div id="status_container" class="container-fluid d-flex justify-content-center">
|
||||||
<div class="d-flex m-2" id="navbarNav">
|
<div class="container-md my-1 shadow p-4 pb-0 m-1 w-50" id="status_badges">
|
||||||
<div id="activity_indicator" class="badge text-bg-danger fw-bold m-1">Inactive</div>
|
<div id="loading_indicator" class="alert alert-info">Loading...</div>
|
||||||
<div id="server_time" class="badge text-bg-secondary m-1"></div>
|
<div id="status_content">
|
||||||
<div id="end_time" class="badge text-bg-info d-none m-1"></div>
|
<div class="row justify-content-center">
|
||||||
|
<div class="col col-6 p-1">
|
||||||
|
<div id="activity_indicator" class="alert alert-danger fw-bolder">Inactive</div>
|
||||||
|
</div>
|
||||||
|
<div class="col col-6 p-1">
|
||||||
|
<div id="server_time" class="alert alert-primary">Server Time (TCT):</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col col-6 p-1">
|
||||||
|
<div id="end_time" class="alert alert-info">Running until:</div>
|
||||||
|
</div>
|
||||||
|
<div class="col p-1">
|
||||||
|
<div id="time-left" class="alert alert-info">Time Left:</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
{{ bootstrap.load_js() }}
|
{{ bootstrap.load_js() }}
|
||||||
<script src="{{url_for('static', filename='color_mode.js')}}"></script>
|
<script src="{{url_for('static', filename='color_mode.js')}}"></script>
|
||||||
<script src="{{ url_for('static', filename='common.js') }}"></script>
|
<script type="module" src="{{ url_for('static', filename='common.js') }}"></script>
|
||||||
@@ -30,5 +30,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<script src="{{url_for('static', filename='index.js')}}"></script>
|
<script type="module" src="{{url_for('static', filename='index.js')}}"></script>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
Reference in New Issue
Block a user