843 lines
37 KiB
HTML
843 lines
37 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Results - SCADA vs DWG Manifest Comparison Tool{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.missing-item {
|
|
background-color: #ffebee;
|
|
}
|
|
/* Main container for the table */
|
|
.table-container {
|
|
position: relative;
|
|
min-height: 200px;
|
|
border-top: none;
|
|
overflow: visible; /* Allow the inner content to determine scrolling */
|
|
}
|
|
/* Sticky header at the top */
|
|
.sticky-table-header {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 20;
|
|
background-color: white;
|
|
border-bottom: 1px solid #dee2e6;
|
|
width: 100%;
|
|
}
|
|
/* Scrollable content area */
|
|
.scrollable-table-content {
|
|
overflow-y: auto;
|
|
max-height: 500px; /* Only this element should have scrolling */
|
|
min-height: 200px;
|
|
scrollbar-width: thin; /* For Firefox */
|
|
padding-bottom: 10px; /* Add some padding at bottom to ensure last row is visible */
|
|
}
|
|
/* For Webkit browsers like Chrome/Safari */
|
|
.scrollable-table-content::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
/* Table filter inside sticky header */
|
|
.table-filter-container {
|
|
padding: 10px;
|
|
background-color: white;
|
|
border-bottom: 1px solid #dee2e6;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
}
|
|
/* Fixed table layout - prevents column width changes */
|
|
.table-fixed {
|
|
table-layout: fixed;
|
|
width: 100%;
|
|
}
|
|
.table-fixed td, .table-fixed th {
|
|
word-wrap: break-word;
|
|
padding: 8px;
|
|
}
|
|
/* Make table header sticky */
|
|
.scrollable-table-content thead {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
background-color: white;
|
|
width: 100%; /* Full width */
|
|
}
|
|
.scrollable-table-content thead tr {
|
|
display: table;
|
|
width: 100%;
|
|
table-layout: fixed;
|
|
}
|
|
.scrollable-table-content thead th {
|
|
background-color: white;
|
|
border-bottom: 2px solid #dee2e6;
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.15);
|
|
font-weight: bold;
|
|
padding: 10px 8px;
|
|
}
|
|
/* Remove border-spacing */
|
|
.scrollable-table-content table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
table-layout: fixed; /* Force fixed layout */
|
|
}
|
|
.summary-box {
|
|
border-radius: 5px;
|
|
padding: 15px;
|
|
margin-bottom: 20px;
|
|
}
|
|
.bg-light-primary {
|
|
background-color: #e3f2fd;
|
|
}
|
|
/* Make sure the table header has a white background that fully covers content */
|
|
.table > thead {
|
|
background-color: white;
|
|
}
|
|
/* Copy button styling */
|
|
.copy-btn {
|
|
min-width: 80px;
|
|
}
|
|
/* Additional styling to ensure header is fully visible */
|
|
.scrollable-table-content thead::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: white;
|
|
z-index: -1;
|
|
}
|
|
/* Fixed column layout */
|
|
.name-col {
|
|
width: 70%;
|
|
}
|
|
.control-panel-col {
|
|
width: 30%;
|
|
text-align: right;
|
|
}
|
|
/* Table structure */
|
|
.comparison-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
table-layout: fixed; /* Force fixed layout */
|
|
}
|
|
.comparison-table th:first-child,
|
|
.comparison-table td:first-child {
|
|
text-align: left;
|
|
padding-right: 10px;
|
|
}
|
|
.comparison-table th:last-child,
|
|
.comparison-table td:last-child {
|
|
text-align: right;
|
|
padding-left: 10px;
|
|
}
|
|
.comparison-table tr {
|
|
border-bottom: 1px solid #e0e0e0;
|
|
}
|
|
/* Ensure rows maintain proper layout */
|
|
.table tbody tr {
|
|
display: table;
|
|
width: 100%;
|
|
table-layout: fixed;
|
|
}
|
|
/* Reset any overflow settings on card containers */
|
|
.card, .card-body {
|
|
overflow: visible !important;
|
|
}
|
|
|
|
/* Main container for the table */
|
|
.table-container {
|
|
position: relative;
|
|
min-height: 200px;
|
|
border-top: none;
|
|
overflow: visible;
|
|
max-height: none;
|
|
}
|
|
|
|
/* Scrollable content area - ONLY this should have scrolling */
|
|
.scrollable-table-content {
|
|
overflow-y: auto;
|
|
max-height: 500px;
|
|
min-height: 200px;
|
|
scrollbar-width: thin; /* For Firefox */
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
/* Ensure parent containers don't create scrollbars */
|
|
.card, .card-body, .card-header, .card-footer {
|
|
overflow: visible !important;
|
|
}
|
|
|
|
/* Remove height constraints from parent containers */
|
|
.card.h-100 {
|
|
height: auto !important;
|
|
min-height: auto !important;
|
|
max-height: none !important;
|
|
}
|
|
|
|
/* Fixed table with scrollable body */
|
|
.table-container {
|
|
position: relative;
|
|
border-top: none;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Set the table header to be sticky */
|
|
.table-fixed thead {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
background-color: white;
|
|
}
|
|
|
|
/* Make only the tbody scroll */
|
|
.scrollable-table-content {
|
|
overflow-y: auto;
|
|
max-height: 400px;
|
|
scrollbar-width: thin;
|
|
}
|
|
|
|
/* Remove any height constraints from parent cards */
|
|
.card.h-100 {
|
|
height: auto !important;
|
|
}
|
|
|
|
/* Ensure the table fills its container */
|
|
.table-fixed {
|
|
width: 100%;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
/* Give header cells proper styling */
|
|
.table-fixed th {
|
|
background-color: white;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
border-bottom: 2px solid #dee2e6;
|
|
}
|
|
|
|
/* COMPLETELY REWRITTEN TABLE STYLES */
|
|
/* Basic reset - clear previous styles */
|
|
.table-container, .scrollable-table-content, .card.h-100 {
|
|
max-height: none;
|
|
min-height: 0;
|
|
overflow: visible;
|
|
height: auto !important;
|
|
}
|
|
|
|
/* Table container - only for positioning */
|
|
.table-container {
|
|
position: relative;
|
|
border-top: none;
|
|
}
|
|
|
|
/* Scrollable content container - ONLY place with scrollbar */
|
|
.scrollable-table-content {
|
|
display: block;
|
|
width: 100%;
|
|
overflow-y: auto;
|
|
max-height: 400px; /* Adjust height as needed */
|
|
border: 1px solid #dee2e6;
|
|
}
|
|
|
|
/* Ensure table takes full width */
|
|
.table-fixed {
|
|
width: 100%;
|
|
margin-bottom: 0;
|
|
border-collapse: separate;
|
|
border-spacing: 0;
|
|
}
|
|
|
|
/* Make header stick to top */
|
|
.table-fixed thead {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 5;
|
|
}
|
|
|
|
/* Header styling */
|
|
.table-fixed th {
|
|
background-color: #fff;
|
|
box-shadow: 0 1px 1px rgba(0,0,0,0.1);
|
|
border-bottom: 2px solid #dee2e6;
|
|
position: sticky;
|
|
top: 0;
|
|
}
|
|
|
|
/* Fix for last row being cut off */
|
|
.scrollable-table-content tbody tr:last-child td {
|
|
border-bottom: 1px solid #dee2e6;
|
|
}
|
|
|
|
/* Add bottom padding to ensure all content visible */
|
|
.scrollable-table-content {
|
|
padding-bottom: 1px;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- Store name mappings for JavaScript -->
|
|
<script>
|
|
// Store name mappings as a JavaScript object
|
|
window.nameMappings = {};
|
|
|
|
{% if data.name_mappings and data.name_mappings.scada %}
|
|
window.nameMappings.scada = {};
|
|
{% for key, value in data.name_mappings.scada.items() %}
|
|
window.nameMappings.scada["{{ key }}"] = {
|
|
originalName: {{ value.original_name|tojson }},
|
|
controlPanel: {{ value.control_panel|tojson }}
|
|
};
|
|
{% endfor %}
|
|
{% else %}
|
|
window.nameMappings.scada = {};
|
|
{% endif %}
|
|
|
|
{% if data.name_mappings and data.name_mappings.manifest %}
|
|
window.nameMappings.manifest = {};
|
|
{% for key, value in data.name_mappings.manifest.items() %}
|
|
window.nameMappings.manifest["{{ key }}"] = {
|
|
originalName: {{ value.original_name|tojson }},
|
|
controlPanel: {{ value.control_panel|tojson }}
|
|
};
|
|
{% endfor %}
|
|
{% else %}
|
|
window.nameMappings.manifest = {};
|
|
{% endif %}
|
|
|
|
{% if data.name_mappings and data.name_mappings.dwg %}
|
|
window.nameMappings.dwg = {};
|
|
{% for key, value in data.name_mappings.dwg.items() %}
|
|
window.nameMappings.dwg["{{ key }}"] = {
|
|
originalName: {{ value.original_name|tojson }},
|
|
controlPanel: {{ value.control_panel|tojson }}
|
|
};
|
|
{% endfor %}
|
|
{% else %}
|
|
window.nameMappings.dwg = {};
|
|
{% endif %}
|
|
</script>
|
|
|
|
<!-- Comparison Selector -->
|
|
{% if comparisons %}
|
|
<div class="card mb-4">
|
|
<div class="card-header bg-info text-white d-flex justify-content-between align-items-center">
|
|
<h4 class="mb-0">Available Comparisons</h4>
|
|
<a href="{{ url_for('index') }}" class="btn btn-light btn-sm">Back to Upload</a>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<select id="comparison-selector" class="form-select form-select-lg mb-3">
|
|
<option value="">-- Select a different comparison --</option>
|
|
{% for comparison_id, comparison in comparisons.items() %}
|
|
<option value="{{ comparison_id }}" {% if comparison_id == data.comparison_id %}selected{% endif %}>
|
|
{{ comparison.name }} ({{ comparison.timestamp }})
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="d-grid gap-2">
|
|
<button id="view-comparison-btn" class="btn btn-success">
|
|
<i class="fas fa-chart-bar"></i> View Selected Comparison
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="comparison-actions" class="row mt-3">
|
|
<div class="col-md-8">
|
|
<div class="input-group">
|
|
<input type="text" id="comparison-name" class="form-control" placeholder="Rename comparison" value="{{ data.name }}">
|
|
<button id="rename-comparison-btn" class="btn btn-outline-secondary">
|
|
<i class="fas fa-edit"></i> Rename
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="d-grid gap-2">
|
|
<button id="delete-comparison-btn" class="btn btn-danger">
|
|
<i class="fas fa-trash"></i> Delete Comparison
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="row mb-4">
|
|
<div class="col-md-12">
|
|
<div class="card">
|
|
<div class="card-header bg-primary text-white">
|
|
<h4 class="mb-0">Comparison Results: {{ data.name }}</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
<!-- Summary Statistics -->
|
|
<div class="summary-box bg-light-primary mb-4">
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<h5>SCADA Items: {{ data.scada_vs_manifest.scada_count }}</h5>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<h5>Manifest Items: {{ data.scada_vs_manifest.manifest_count }}</h5>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<h5>DWG Items: {{ data.scada_vs_dwg.dwg_count }}</h5>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Repository Information -->
|
|
<div class="summary-box bg-light mb-4">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h5>Repository: {{ data.repository_url }}</h5>
|
|
<p>Last updated: {{ data.timestamp }}</p>
|
|
</div>
|
|
<div class="col-md-6 text-end">
|
|
<form id="refresh-repo-form" action="{{ url_for('refresh_repository') }}" method="post">
|
|
<input type="hidden" name="repo_id" value="{{ data.repo_id }}">
|
|
<input type="hidden" name="comparison_id" value="{{ data.comparison_id }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="btn btn-outline-primary">
|
|
<i class="fas fa-sync-alt"></i> Refresh Repository & Reload Data
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabs Navigation -->
|
|
<ul class="nav nav-tabs" id="comparisonTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="scada-dwg-tab" data-bs-toggle="tab" data-bs-target="#scada-dwg" type="button" role="tab" aria-controls="scada-dwg" aria-selected="true">SCADA vs DWG</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="manifest-dwg-tab" data-bs-toggle="tab" data-bs-target="#manifest-dwg" type="button" role="tab" aria-controls="manifest-dwg" aria-selected="false">Manifest vs DWG</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="tab-content mt-3" id="comparisonTabsContent">
|
|
|
|
<!-- SCADA vs DWG Tab -->
|
|
<div class="tab-pane fade show active" id="scada-dwg" role="tabpanel" aria-labelledby="scada-dwg-tab">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-header bg-danger text-white">
|
|
<h5 class="mb-0">In SCADA but Missing from DWG ({{ data.scada_vs_dwg.only_in_scada|length }})</h5>
|
|
</div>
|
|
<div class="card-body table-container">
|
|
{% if data.scada_vs_dwg.only_in_scada %}
|
|
<div class="scrollable-table-content">
|
|
<table class="table table-striped table-bordered table-fixed">
|
|
<thead>
|
|
<tr>
|
|
<th class="name-col">Name</th>
|
|
<th class="control-panel-col">Control Panel</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for item in data.scada_vs_dwg.only_in_scada %}
|
|
<tr class="missing-item">
|
|
<td class="name-col">{{ item.name }}</td>
|
|
<td class="control-panel-col">{{ item.control_panel }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-success">No items in SCADA are missing from DWG.</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-header bg-danger text-white">
|
|
<h5 class="mb-0">In DWG but Missing from SCADA ({{ data.scada_vs_dwg.only_in_dwg|length }})</h5>
|
|
</div>
|
|
<div class="card-body table-container">
|
|
{% if data.scada_vs_dwg.only_in_dwg %}
|
|
<div class="scrollable-table-content">
|
|
<table class="table table-striped table-bordered table-fixed">
|
|
<thead>
|
|
<tr>
|
|
<th class="name-col">Name</th>
|
|
<th class="control-panel-col">Control Panel</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for item in data.scada_vs_dwg.only_in_dwg %}
|
|
<tr class="missing-item">
|
|
<td class="name-col">{{ item.name }}</td>
|
|
<td class="control-panel-col">{{ item.control_panel }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-success">No items in DWG are missing from SCADA.</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mt-4">
|
|
<div class="col-md-12">
|
|
<div class="card">
|
|
<div class="card-header bg-success text-white">
|
|
<h5 class="mb-0">Names Found in Both SCADA and DWG ({{ data.scada_vs_dwg.common|length }})</h5>
|
|
</div>
|
|
<div class="card-body table-container">
|
|
{% if data.scada_vs_dwg.common %}
|
|
<div class="scrollable-table-content">
|
|
<table class="table table-striped table-bordered table-fixed">
|
|
<thead>
|
|
<tr>
|
|
<th class="name-col">Name</th>
|
|
<th class="control-panel-col">Control Panel</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for item in data.scada_vs_dwg.common %}
|
|
<tr>
|
|
<td class="name-col">{{ item.name }}</td>
|
|
<td class="control-panel-col">{{ item.control_panel }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-danger">No common names found between SCADA and DWG.</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Manifest vs DWG Tab -->
|
|
<div class="tab-pane fade" id="manifest-dwg" role="tabpanel" aria-labelledby="manifest-dwg-tab">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-header bg-danger text-white">
|
|
<h5 class="mb-0">In Manifest but Missing from DWG ({{ data.manifest_vs_dwg.only_in_manifest|length }})</h5>
|
|
</div>
|
|
<div class="card-body table-container">
|
|
{% if data.manifest_vs_dwg.only_in_manifest %}
|
|
<div class="scrollable-table-content">
|
|
<table class="table table-striped table-bordered table-fixed">
|
|
<thead>
|
|
<tr>
|
|
<th class="name-col">Name</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for item in data.manifest_vs_dwg.only_in_manifest %}
|
|
<tr class="missing-item">
|
|
<td class="name-col">{{ item.name }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-success">No items in Manifest are missing from DWG.</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card h-100">
|
|
<div class="card-header bg-danger text-white">
|
|
<h5 class="mb-0">In DWG but Missing from Manifest ({{ data.manifest_vs_dwg.only_in_dwg|length }})</h5>
|
|
</div>
|
|
<div class="card-body table-container">
|
|
{% if data.manifest_vs_dwg.only_in_dwg %}
|
|
<div class="scrollable-table-content">
|
|
<table class="table table-striped table-bordered table-fixed">
|
|
<thead>
|
|
<tr>
|
|
<th class="name-col">Name</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for item in data.manifest_vs_dwg.only_in_dwg %}
|
|
<tr class="missing-item">
|
|
<td class="name-col">{{ item.name }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-success">No items in DWG are missing from Manifest.</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mt-4">
|
|
<div class="col-md-12">
|
|
<div class="card">
|
|
<div class="card-header bg-success text-white">
|
|
<h5 class="mb-0">Names Found in Both Manifest and DWG ({{ data.manifest_vs_dwg.common|length }})</h5>
|
|
</div>
|
|
<div class="card-body table-container">
|
|
{% if data.manifest_vs_dwg.common %}
|
|
<div class="scrollable-table-content">
|
|
<table class="table table-striped table-bordered table-fixed">
|
|
<thead>
|
|
<tr>
|
|
<th class="name-col">Name</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for item in data.manifest_vs_dwg.common %}
|
|
<tr>
|
|
<td class="name-col">{{ item.name }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<p class="text-danger">No common names found between Manifest and DWG.</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer">
|
|
<h5>Update Comparison</h5>
|
|
<form id="update-comparison-form" action="{{ url_for('update_files') }}" method="post" enctype="multipart/form-data">
|
|
<input type="hidden" name="repo_id" value="{{ data.repo_id }}">
|
|
<input type="hidden" name="comparison_id" value="{{ data.comparison_id }}">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="manifest_file" class="form-label">New Manifest Excel File (Optional)</label>
|
|
<input type="file" class="form-control" id="manifest_file" name="manifest_file" accept=".xlsx">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="dwg_file" class="form-label">New DWG Excel File (Optional)</label>
|
|
<input type="file" class="form-control" id="dwg_file" name="dwg_file" accept=".xlsx">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-grid gap-2">
|
|
<button type="submit" class="btn btn-primary">Update Comparison</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
// Store CSRF token for JavaScript use
|
|
const csrfToken = "{{ csrf_token() }}";
|
|
|
|
// Initialize the Bootstrap tabs
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// This ensures tabs work properly
|
|
var triggerTabList = [].slice.call(document.querySelectorAll('#comparisonTabs button'))
|
|
triggerTabList.forEach(function(triggerEl) {
|
|
new bootstrap.Tab(triggerEl);
|
|
});
|
|
|
|
// Handle the refresh repository form submission
|
|
const refreshForm = document.getElementById('refresh-repo-form');
|
|
if (refreshForm) {
|
|
refreshForm.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
const formData = new FormData(this);
|
|
|
|
// Update button text and disable
|
|
const submitBtn = this.querySelector('button[type="submit"]');
|
|
const originalBtnText = submitBtn.innerHTML;
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Refreshing...';
|
|
|
|
fetch(this.action, {
|
|
method: 'POST',
|
|
body: formData,
|
|
redirect: 'manual'
|
|
})
|
|
.then(response => {
|
|
if (response.type === 'opaqueredirect') {
|
|
window.location.href = response.url || '/';
|
|
return;
|
|
}
|
|
return response.text();
|
|
})
|
|
.then(html => {
|
|
if (html) {
|
|
// If we got HTML back, refresh the page
|
|
window.location.reload();
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('An error occurred while refreshing the repository. Please try again.');
|
|
submitBtn.disabled = false;
|
|
submitBtn.innerHTML = originalBtnText;
|
|
});
|
|
});
|
|
}
|
|
|
|
// Handle the update comparison form submission
|
|
const updateForm = document.getElementById('update-comparison-form');
|
|
if (updateForm) {
|
|
updateForm.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
const formData = new FormData(this);
|
|
|
|
// Show loading indicator
|
|
const submitBtn = this.querySelector('button[type="submit"]');
|
|
const originalBtnText = submitBtn.innerHTML;
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = 'Updating...';
|
|
|
|
fetch(this.action, {
|
|
method: 'POST',
|
|
body: formData,
|
|
redirect: 'manual'
|
|
})
|
|
.then(response => {
|
|
if (response.type === 'opaqueredirect') {
|
|
// Server redirected, follow the redirect
|
|
window.location.href = response.url || '/';
|
|
return;
|
|
}
|
|
return response.text();
|
|
})
|
|
.then(html => {
|
|
if (html) {
|
|
// If we got HTML back and not a redirect, handle it
|
|
window.location.reload();
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('An error occurred during update. Please try again.');
|
|
submitBtn.disabled = false;
|
|
submitBtn.innerHTML = originalBtnText;
|
|
});
|
|
});
|
|
}
|
|
|
|
// Comparison selector functionality
|
|
const comparisonSelector = document.getElementById('comparison-selector');
|
|
const viewComparisonBtn = document.getElementById('view-comparison-btn');
|
|
const comparisonActions = document.getElementById('comparison-actions');
|
|
const comparisonNameInput = document.getElementById('comparison-name');
|
|
const renameComparisonBtn = document.getElementById('rename-comparison-btn');
|
|
const deleteComparisonBtn = document.getElementById('delete-comparison-btn');
|
|
|
|
if (viewComparisonBtn) {
|
|
viewComparisonBtn.addEventListener('click', function() {
|
|
const selectedValue = comparisonSelector.value;
|
|
if (selectedValue) {
|
|
window.location.href = '/comparison/' + selectedValue;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (renameComparisonBtn) {
|
|
renameComparisonBtn.addEventListener('click', function() {
|
|
const selectedValue = comparisonSelector.value || '{{ data.comparison_id }}';
|
|
const newName = comparisonNameInput.value.trim();
|
|
|
|
if (selectedValue && newName) {
|
|
fetch('/rename_comparison/' + selectedValue, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
'X-CSRFToken': csrfToken
|
|
},
|
|
body: 'name=' + encodeURIComponent(newName)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// Update the option text
|
|
if (comparisonSelector) {
|
|
const selectedOption = comparisonSelector.options[comparisonSelector.selectedIndex];
|
|
const timestampPart = selectedOption.text.split(' (')[1];
|
|
selectedOption.text = newName + ' (' + timestampPart;
|
|
}
|
|
|
|
// Update the page title
|
|
const headerTitle = document.querySelector('.card-header h4');
|
|
if (headerTitle) {
|
|
headerTitle.textContent = 'Comparison Results: ' + newName;
|
|
}
|
|
|
|
alert('Comparison renamed successfully!');
|
|
} else {
|
|
alert('Failed to rename comparison: ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('An error occurred while renaming the comparison.');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
if (deleteComparisonBtn) {
|
|
deleteComparisonBtn.addEventListener('click', function() {
|
|
const selectedValue = comparisonSelector ? comparisonSelector.value : '{{ data.comparison_id }}';
|
|
|
|
if (selectedValue && confirm('Are you sure you want to delete this comparison? This action cannot be undone.')) {
|
|
// Use fetch API instead of form submission
|
|
fetch('/delete_comparison/' + selectedValue, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': csrfToken
|
|
},
|
|
redirect: 'manual'
|
|
})
|
|
.then(response => {
|
|
if (response.type === 'opaqueredirect') {
|
|
window.location.href = response.url || '/';
|
|
return;
|
|
}
|
|
return response.text();
|
|
})
|
|
.then(html => {
|
|
if (html) {
|
|
window.location.href = '/';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('An error occurred while deleting the comparison. Please try again.');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |