mirror of
https://github.com/HyPnoTiiK/stream-fusion.git
synced 2026-07-30 07:49:17 +00:00
Introduce a metadata_mappings table with DAO/model layers for admin-managed IMDB→TMDB/title overrides, and overhaul the admin panel with a modern Bootstrap 5 sidebar UI. - Add metadata_mappings table with migrations (search_titles, year_override) - Add MetadataMappingDAO with standalone session helpers for use outside FastAPI - Add new admin pages: dashboard, mappings CRUD, maintenance, config, hash/TMDB search - Integrate DB mapping lookups into Cinemeta and TMDB metadata providers - Update catalog views to use DB mappings before hitting external APIs - Rewrite all admin templates with Bootstrap 5, sidebar navigation, French labels - Add session-based auth for admin routes with Redis session store
244 lines
9.1 KiB
HTML
244 lines
9.1 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Recherche par hash — StreamFusion Admin{% endblock %}
|
|
{% block page_title %}Recherche par hash{% endblock %}
|
|
|
|
{% block extra_head %}
|
|
<style>
|
|
.result-card {
|
|
border: 1px solid var(--bs-border-color);
|
|
border-radius: 10px;
|
|
background: var(--bs-secondary-bg);
|
|
overflow: hidden;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
.result-card-header {
|
|
padding: 14px 20px;
|
|
border-bottom: 1px solid var(--bs-border-color);
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
}
|
|
.raw-title {
|
|
font-size: 0.95rem;
|
|
font-weight: 600;
|
|
word-break: break-word;
|
|
line-height: 1.3;
|
|
}
|
|
.meta-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
gap: 0;
|
|
}
|
|
.meta-row {
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 10px 16px;
|
|
border-right: 1px solid var(--bs-border-color);
|
|
border-bottom: 1px solid var(--bs-border-color);
|
|
}
|
|
.meta-row:last-child { border-right: none; }
|
|
.meta-label { font-size: 0.68rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--bs-secondary-color); margin-bottom: 3px; }
|
|
.meta-value { font-size: 0.85rem; word-break: break-all; }
|
|
.parsed-grid {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
padding: 16px 20px;
|
|
}
|
|
.parsed-item {
|
|
background: var(--bs-body-bg);
|
|
border: 1px solid var(--bs-border-color);
|
|
border-radius: 6px;
|
|
padding: 7px 12px;
|
|
}
|
|
.parsed-item-key { font-size: 0.68rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--bs-secondary-color); }
|
|
.parsed-item-value { font-size: 0.85rem; margin-top: 2px; word-break: break-word; }
|
|
.section-label {
|
|
font-size: 0.72rem; font-weight: 600; text-transform: uppercase;
|
|
letter-spacing: 0.07em; color: var(--bs-secondary-color);
|
|
padding: 10px 20px 6px;
|
|
border-top: 1px solid var(--bs-border-color);
|
|
}
|
|
.tracker-list {
|
|
padding: 8px 20px 14px;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
}
|
|
.tracker-badge {
|
|
font-size: 0.72rem;
|
|
font-family: var(--bs-font-monospace);
|
|
background: var(--bs-body-bg);
|
|
border: 1px solid var(--bs-border-color);
|
|
border-radius: 4px;
|
|
padding: 2px 8px;
|
|
color: var(--bs-secondary-color);
|
|
word-break: break-all;
|
|
}
|
|
.lang-badge {
|
|
font-size: 0.75rem;
|
|
background: rgba(13,110,253,0.12);
|
|
color: #6ea8fe;
|
|
border: 1px solid rgba(13,110,253,0.25);
|
|
border-radius: 4px;
|
|
padding: 1px 7px;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
|
|
<!-- Search bar -->
|
|
<form method="get" action="{{ url_for('search_hash_page')(request) }}" class="d-flex gap-2 mb-4" style="max-width:560px;">
|
|
<input type="text" name="q" value="{{ query }}"
|
|
class="form-control font-monospace"
|
|
placeholder="info_hash (40 caractères hexadécimaux)"
|
|
maxlength="40" autocomplete="off" autofocus>
|
|
<button type="submit" class="btn btn-info flex-shrink-0">
|
|
<i class="bi bi-search me-1"></i>Rechercher
|
|
</button>
|
|
</form>
|
|
|
|
{% if error %}
|
|
<div class="alert alert-danger">{{ error }}</div>
|
|
{% endif %}
|
|
|
|
{% if query and not error %}
|
|
{% if not results %}
|
|
<div class="text-center text-secondary py-5">
|
|
<i class="bi bi-inbox fs-2 d-block mb-2"></i>
|
|
Aucun résultat pour <code class="text-info">{{ query }}</code>
|
|
</div>
|
|
{% else %}
|
|
<div class="text-secondary mb-3" style="font-size:0.85rem;">
|
|
{{ results|length }} entrée(s) pour <code class="text-info">{{ query }}</code>
|
|
</div>
|
|
|
|
{% for r in results %}
|
|
<div class="result-card">
|
|
<!-- Header -->
|
|
<div class="result-card-header">
|
|
<div>
|
|
<div class="raw-title">{{ r.raw_title }}</div>
|
|
<div class="mt-1 d-flex align-items-center gap-2 flex-wrap">
|
|
<code style="font-size:0.78rem;color:var(--bs-secondary-color);">{{ r.info_hash }}</code>
|
|
<button class="btn btn-link btn-sm p-0 copy-btn text-secondary" data-value="{{ r.info_hash }}" title="Copier le hash">
|
|
<i class="bi bi-clipboard"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex gap-2 flex-shrink-0 flex-wrap justify-content-end">
|
|
{% if r.type == 'series' %}
|
|
<span class="badge" style="background:rgba(13,202,240,0.15);color:#0dcaf0;border:1px solid rgba(13,202,240,0.3);">Série</span>
|
|
{% elif r.type == 'movie' %}
|
|
<span class="badge" style="background:rgba(13,110,253,0.15);color:#6ea8fe;border:1px solid rgba(13,110,253,0.3);">Film</span>
|
|
{% endif %}
|
|
{% if r.privacy == 'private' %}
|
|
<span class="badge bg-warning-subtle text-warning border border-warning-subtle">Privé</span>
|
|
{% else %}
|
|
<span class="badge bg-success-subtle text-success border border-success-subtle">Public</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Core metadata -->
|
|
<div class="meta-grid">
|
|
<div class="meta-row">
|
|
<span class="meta-label">Taille</span>
|
|
<span class="meta-value">{{ r.size | fmt_size }}</span>
|
|
</div>
|
|
<div class="meta-row">
|
|
<span class="meta-label">Indexeur</span>
|
|
<span class="meta-value">{{ r.indexer }}</span>
|
|
</div>
|
|
<div class="meta-row">
|
|
<span class="meta-label">Seeders</span>
|
|
<span class="meta-value">{{ r.seeders }}</span>
|
|
</div>
|
|
<div class="meta-row">
|
|
<span class="meta-label">TMDB ID</span>
|
|
<span class="meta-value">
|
|
{% if r.tmdb_id %}
|
|
<a href="{{ url_for('search_tmdb_page')(request) }}?q={{ r.tmdb_id }}" class="text-info text-decoration-none">{{ r.tmdb_id }}</a>
|
|
{% else %}—{% endif %}
|
|
</span>
|
|
</div>
|
|
<div class="meta-row">
|
|
<span class="meta-label">Langues</span>
|
|
<span class="meta-value d-flex flex-wrap gap-1 mt-1">
|
|
{% for lang in (r.languages or []) %}
|
|
<span class="lang-badge">{{ lang }}</span>
|
|
{% else %}—{% endfor %}
|
|
</span>
|
|
</div>
|
|
<div class="meta-row">
|
|
<span class="meta-label">Créé le</span>
|
|
<span class="meta-value">{{ r.created_at | fmt_date }}</span>
|
|
</div>
|
|
<div class="meta-row">
|
|
<span class="meta-label">Mis à jour</span>
|
|
<span class="meta-value">{{ r.updated_at | fmt_date }}</span>
|
|
</div>
|
|
<div class="meta-row">
|
|
<span class="meta-label">Disponibilité</span>
|
|
<span class="meta-value">
|
|
{% if r.availability %}
|
|
<span class="text-success"><i class="bi bi-check-circle-fill"></i> Oui</span>
|
|
{% else %}
|
|
<span class="text-secondary"><i class="bi bi-dash-circle"></i> Non</span>
|
|
{% endif %}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Parsed data -->
|
|
{% if r.parsed_data %}
|
|
<div class="section-label">Données parsées</div>
|
|
<div class="parsed-grid">
|
|
{% for key, val in r.parsed_data.items() %}
|
|
{% if val is not none and val != '' and val != [] and val != false and val != 0 %}
|
|
<div class="parsed-item">
|
|
<div class="parsed-item-key">{{ key }}</div>
|
|
<div class="parsed-item-value">
|
|
{% if val is iterable and val is not string %}
|
|
{{ val | join(', ') }}
|
|
{% else %}
|
|
{{ val }}
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Trackers -->
|
|
{% if r.trackers %}
|
|
<div class="section-label">Trackers ({{ r.trackers | length }})</div>
|
|
<div class="tracker-list">
|
|
{% for t in r.trackers %}
|
|
<span class="tracker-badge">{{ t }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
{% endblock %}
|
|
|
|
{% block extra_scripts %}
|
|
<script>
|
|
document.querySelectorAll('.copy-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
navigator.clipboard.writeText(btn.dataset.value).then(() => {
|
|
const icon = btn.querySelector('i');
|
|
icon.className = 'bi bi-clipboard-check text-success';
|
|
setTimeout(() => { icon.className = 'bi bi-clipboard'; }, 1500);
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|