254 lines
9.2 KiB
Django/Jinja
254 lines
9.2 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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
</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">
|
|
<h5>Volume Configuration</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form id="volumeForm">
|
|
<div class="form-group">
|
|
<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 }}">
|
|
<button type="submit" class="btn btn-primary mt-2">
|
|
<i class="fas fa-save"></i> Update Volume
|
|
</button>
|
|
</div>
|
|
<div class="form-text">Enter a value between 1 and {{ max_volume }}</div>
|
|
</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">
|
|
<div class="input-group">
|
|
<input type="text" id="paperSearchInput" class="form-control"
|
|
placeholder="Search paper 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 id="processingStatus" class="alert alert-info mt-3 d-none"></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>
|
|
<div class="chart-wrapper">
|
|
<canvas id="activityChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5>Recent Activity</h5>
|
|
</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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock content %}
|
|
|
|
{% block scripts %}
|
|
{{ super() }}
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
|
|
<!-- Modular JavaScript files -->
|
|
<script src="{{ url_for('static', filename='js/common.js') }}"></script>
|
|
<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 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 %} |