368 lines
15 KiB
Django/Jinja
368 lines
15 KiB
Django/Jinja
{% extends "base.html.jinja" %}
|
|
|
|
{% block title %}Control Panel{% endblock title %}
|
|
|
|
{% block styles %}
|
|
{{ super() }}
|
|
<style>
|
|
.status-indicator {
|
|
width: 15px;
|
|
height: 15px;
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
margin-right: 5px;
|
|
}
|
|
|
|
.status-active {
|
|
background-color: #28a745;
|
|
}
|
|
|
|
.status-paused {
|
|
background-color: #ffc107;
|
|
}
|
|
|
|
.status-inactive {
|
|
background-color: #dc3545;
|
|
}
|
|
|
|
.stats-chart {
|
|
height: 400px;
|
|
}
|
|
|
|
.chart-wrapper {
|
|
position: relative;
|
|
height: 400px !important;
|
|
width: 100%;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.chart-wrapper canvas {
|
|
height: 400px !important;
|
|
}
|
|
|
|
.scraper-chart-wrapper {
|
|
position: relative;
|
|
height: 150px !important;
|
|
width: 100%;
|
|
border-top: 1px solid #e0e0e0;
|
|
padding-top: 15px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.scraper-chart-wrapper canvas {
|
|
display: block;
|
|
width: 100%;
|
|
}
|
|
|
|
.search-results-container {
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* Paper status badges */
|
|
.badge-new {
|
|
background-color: #17a2b8;
|
|
}
|
|
|
|
.badge-pending {
|
|
background-color: #ffc107;
|
|
}
|
|
|
|
.badge-done {
|
|
background-color: #28a745;
|
|
}
|
|
|
|
.badge-failed {
|
|
background-color: #dc3545;
|
|
}
|
|
|
|
.activity-controls {
|
|
width: auto;
|
|
display: inline-block;
|
|
}
|
|
</style>
|
|
{% endblock styles %}
|
|
|
|
{% block content %}
|
|
<div class="container mt-4">
|
|
<h1>Control Panel</h1>
|
|
|
|
<!-- Include standardized flash messages -->
|
|
{% include "partials/flash_messages.html.jinja" %}
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5>Scraper Status</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div id="statusIndicator" class="status-indicator status-inactive"></div>
|
|
<span id="statusText">Inactive</span>
|
|
</div>
|
|
|
|
<div class="btn-group" role="group">
|
|
<button id="startButton" class="btn btn-success">Start</button>
|
|
<button id="pauseButton" class="btn btn-warning" disabled>Pause</button>
|
|
<button id="stopButton" class="btn btn-danger" disabled>Stop</button>
|
|
<button id="resetButton" class="btn btn-secondary" disabled>Reset</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5>Scraper Configuration</h5>
|
|
<button type="button" class="btn btn-outline-info btn-sm" onclick="showScraperOverview()"
|
|
title="View scraper modules overview">
|
|
<i class="fas fa-info-circle"></i> How Scrapers Work
|
|
</button>
|
|
</div>
|
|
<div class="card-body">
|
|
<form id="volumeForm">
|
|
<div class="form-group mb-3">
|
|
<label for="volumeInput">Papers per day:</label>
|
|
<input type="number" class="form-control" id="volumeInput"
|
|
value="{{ volume_config if volume_config else 100 }}" min="1" max="{{ max_volume }}">
|
|
<div class="form-text">Enter a value between 1 and {{ max_volume }}</div>
|
|
</div>
|
|
|
|
<div class="form-group mb-3">
|
|
<label for="mainScraperSelect">Scraper Module:</label>
|
|
<select class="form-control" id="mainScraperSelect">
|
|
{% for module in available_scraper_modules %}
|
|
<option value="{{ module }}" {% if module==current_scraper_module %}selected{% endif %}>
|
|
{{ module }}
|
|
{% if scraper_details[module] %}
|
|
- {{ scraper_details[module].description[:50] }}{% if
|
|
scraper_details[module].description|length > 50 %}...{% endif %}
|
|
{% endif %}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
<div class="form-text">
|
|
Select which scraper module to use for automated processing. Current: <strong>{{
|
|
current_scraper_module }}</strong>
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-save"></i> Update Configuration
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- New row for single paper processing -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5>Process Single Paper</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<form id="searchPaperForm" class="mb-3">
|
|
<label for="paperSearchInput">Search paper:</label>
|
|
<div class="input-group">
|
|
<input type="text" id="paperSearchInput" class="form-control"
|
|
placeholder="By title, DOI, or ID...">
|
|
<button class="btn btn-outline-secondary" type="submit">Search</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="scraperSelect">Scraper Module:</label>
|
|
<select class="form-control" id="scraperSelect">
|
|
<option value="">Use default system scraper</option>
|
|
<!-- Available scrapers will be populated here -->
|
|
</select>
|
|
<div class="form-text">
|
|
Select which scraper to use for processing the paper
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="searchResults" class="mt-3 search-results-container d-none">
|
|
<table class="table table-hover table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Title</th>
|
|
<th>DOI</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="paperSearchResults">
|
|
<!-- Search results will be populated here -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5>Scraping Activity</h5>
|
|
<div>
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="notificationsToggle" checked>
|
|
<label class="form-check-label" for="notificationsToggle">Show Notifications</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="btn-group mb-3">
|
|
<button class="btn btn-outline-secondary time-range-btn" data-hours="6">Last 6
|
|
hours</button>
|
|
<button class="btn btn-outline-secondary time-range-btn active" data-hours="24">Last 24
|
|
hours</button>
|
|
<button class="btn btn-outline-secondary time-range-btn" data-hours="72">Last 3
|
|
days</button>
|
|
</div>
|
|
|
|
<!-- Scraper State Legend - will be populated by JavaScript -->
|
|
<div id="scraper-state-legend" class="scraper-state-legend mb-3 d-none">
|
|
<small class="text-muted">Scraper Status Timeline:</small><br>
|
|
<span class="badge bg-secondary">Loading state information...</span>
|
|
</div>
|
|
|
|
<div class="chart-wrapper">
|
|
<canvas id="activityChart"></canvas>
|
|
</div>
|
|
|
|
<!-- Scraper Activity Timeline Chart -->
|
|
<div class="scraper-chart-wrapper">
|
|
<h6 class="text-muted mb-2">
|
|
<i class="fas fa-power-off"></i> Scraper Activity Timeline
|
|
</h6>
|
|
<canvas id="scraperActivityChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5>Recent Activity</h5>
|
|
<div class="d-flex align-items-center gap-3">
|
|
<div class="form-group mb-0">
|
|
<label for="activityPageSize" class="form-label mb-0 me-2">Show:</label>
|
|
<select id="activityPageSize" class="form-select form-select-sm activity-controls">
|
|
<option value="10">10</option>
|
|
<option value="20" selected>20</option>
|
|
<option value="50">50</option>
|
|
<option value="100">100</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group mb-0">
|
|
<label for="activityStatusFilter" class="form-label mb-0 me-2">Status:</label>
|
|
<select id="activityStatusFilter" class="form-select form-select-sm activity-controls">
|
|
<option value="">All</option>
|
|
<option value="success">Success</option>
|
|
<option value="error">Error</option>
|
|
<option value="pending">Pending</option>
|
|
<option value="info">Info</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Time</th>
|
|
<th>Action</th>
|
|
<th>Status</th>
|
|
<th>Description</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="activityLog">
|
|
<tr>
|
|
<td colspan="4" class="text-center">Loading activities...</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination Controls -->
|
|
<nav id="activityPagination" aria-label="Activity log pagination" class="mt-3 d-none">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="text-muted">
|
|
<span id="activityPaginationInfo">Showing 0 - 0 of 0 entries</span>
|
|
</div>
|
|
<ul class="pagination pagination-sm mb-0">
|
|
<li class="page-item" id="activityPrevPage">
|
|
<a class="page-link" href="#" aria-label="Previous">
|
|
<span aria-hidden="true">«</span>
|
|
</a>
|
|
</li>
|
|
<li class="page-item active" id="activityCurrentPage">
|
|
<span class="page-link">1</span>
|
|
</li>
|
|
<li class="page-item" id="activityNextPage">
|
|
<a class="page-link" href="#" aria-label="Next">
|
|
<span aria-hidden="true">»</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Include the scraper overview modal -->
|
|
{% include "partials/scraper_overview_modal.html.jinja" %}
|
|
|
|
{% endblock content %}
|
|
|
|
{% block scripts %}
|
|
{{ super() }}
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script
|
|
src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
|
|
|
|
<!-- Modular JavaScript files -->
|
|
<script src="{{ url_for('static', filename='js/chart.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/scraper-control.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/paper-processor.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/activity-monitor.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/scraper-dashboard.js') }}"></script>
|
|
<script src="{{ url_for('static', filename='js/scraper-overview.js') }}"></script>
|
|
|
|
<script id="scraper-config" type="application/json">
|
|
{
|
|
"maxVolume": {{ max_volume }},
|
|
"volumeConfig": {{ volume_config if volume_config else 100 }}
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
const rawConfig = document.getElementById('scraper-config').textContent;
|
|
const dashboardConfig = JSON.parse(rawConfig);
|
|
|
|
window.scraperDashboard = initScraperDashboard(dashboardConfig);
|
|
});
|
|
</script>
|
|
{% endblock scripts %} |