Resuelto conflicto en X
This commit is contained in:
parent
758da0ad4c
commit
e9264bc6ce
9 changed files with 348 additions and 96 deletions
57
templates/add_feed.html
Normal file
57
templates/add_feed.html
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Añadir Nuevo Feed{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<header>
|
||||
<h1>Añadir Nuevo Feed</h1>
|
||||
<p class="subtitle">Introduce los detalles de la nueva fuente de noticias RSS.</p>
|
||||
<a href="{{ url_for('dashboard') }}" class="top-link" style="margin-top:15px;">← Volver al Dashboard</a>
|
||||
</header>
|
||||
|
||||
<div class="form-section">
|
||||
<form action="{{ url_for('add_feed') }}" method="post" autocomplete="off">
|
||||
<div>
|
||||
<label for="nombre">Nombre del feed</label>
|
||||
<input id="nombre" name="nombre" type="text" placeholder="Ej: Noticias de Tecnología" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:15px;">
|
||||
<label for="url">URL del RSS</label>
|
||||
<input id="url" name="url" type="url" placeholder="https://ejemplo.com/rss" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:15px;">
|
||||
<label for="descripcion">Descripción</label>
|
||||
<textarea id="descripcion" name="descripcion" rows="2" placeholder="Breve descripción del contenido del feed"></textarea>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin-top: 15px;">
|
||||
<div>
|
||||
<label for="categoria_id">Categoría</label>
|
||||
<select id="categoria_id" name="categoria_id" required>
|
||||
<option value="">— Elige categoría —</option>
|
||||
{% for cat in categorias %}
|
||||
<option value="{{ cat.id }}">{{ cat.nombre }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="pais_id">País</label>
|
||||
<select name="pais_id" id="pais_id">
|
||||
<option value="">— Global / No aplica —</option>
|
||||
{% for pais in paises %}
|
||||
<option value="{{ pais.id }}">{{ pais.nombre }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="idioma">Idioma (código)</label>
|
||||
<input id="idioma" name="idioma" type="text" maxlength="2" placeholder="ej: es, en">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn" style="margin-top: 25px; width: 100%;">Añadir Feed</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
57
templates/add_feeds.html
Normal file
57
templates/add_feeds.html
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Añadir Nuevo Feed{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<header>
|
||||
<h1>Añadir Nuevo Feed</h1>
|
||||
<p class="subtitle">Introduce los detalles de la nueva fuente de noticias RSS.</p>
|
||||
<a href="{{ url_for('dashboard') }}" class="top-link" style="margin-top:15px;">← Volver al Dashboard</a>
|
||||
</header>
|
||||
|
||||
<div class="form-section">
|
||||
<form action="{{ url_for('add_feed') }}" method="post" autocomplete="off">
|
||||
<div>
|
||||
<label for="nombre">Nombre del feed</label>
|
||||
<input id="nombre" name="nombre" type="text" placeholder="Ej: Noticias de Tecnología" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:15px;">
|
||||
<label for="url">URL del RSS</label>
|
||||
<input id="url" name="url" type="url" placeholder="https://ejemplo.com/rss" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:15px;">
|
||||
<label for="descripcion">Descripción</label>
|
||||
<textarea id="descripcion" name="descripcion" rows="2" placeholder="Breve descripción del contenido del feed"></textarea>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px; margin-top: 15px;">
|
||||
<div>
|
||||
<label for="categoria_id">Categoría</label>
|
||||
<select id="categoria_id" name="categoria_id" required>
|
||||
<option value="">— Elige categoría —</option>
|
||||
{% for cat in categorias %}
|
||||
<option value="{{ cat.id }}">{{ cat.nombre }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="pais_id">País</label>
|
||||
<select name="pais_id" id="pais_id">
|
||||
<option value="">— Global / No aplica —</option>
|
||||
{% for pais in paises %}
|
||||
<option value="{{ pais.id }}">{{ pais.nombre }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="idioma">Idioma (código)</label>
|
||||
<input id="idioma" name="idioma" type="text" maxlength="2" placeholder="ej: es, en">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn" style="margin-top: 25px; width: 100%;">Añadir Feed</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -67,6 +67,7 @@
|
|||
.btn, button { padding: 12px 25px; background: var(--gradiente-principal); color: white !important; border: none; border-radius: var(--border-radius-sm); font-size: 1rem; font-weight: 600; cursor: pointer; transition: all var(--transition-speed) ease; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); text-decoration: none; display: inline-block; text-align: center; }
|
||||
.btn:hover, button:hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2); text-decoration: none; }
|
||||
.btn-secondary { background: #34495e; } .btn-secondary:hover { background: #2c3e50; }
|
||||
.btn-small { padding: 6px 14px; font-size: 0.9rem; }
|
||||
a { color: var(--secondary-color); text-decoration: none; font-weight: 500; } a:hover { text-decoration: underline; }
|
||||
.top-link { display: inline-block; margin-bottom: 25px; font-weight: 500; color: var(--primary-color); }
|
||||
.top-link:hover { text-decoration: underline; }
|
||||
|
|
@ -82,14 +83,6 @@
|
|||
.noticia-texto h3 a:hover { color: var(--primary-color); }
|
||||
.noticia-meta { font-size: 0.8rem; color: var(--text-color-light); margin-bottom: 8px; }
|
||||
|
||||
/* --- Tabla de Gestión de Feeds --- */
|
||||
.table-wrapper { overflow-x: auto; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
|
||||
th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid var(--border-color); }
|
||||
th { background-color: rgba(106, 27, 203, 0.05); font-weight: 600; }
|
||||
tr:last-child td { border-bottom: none; }
|
||||
td .actions a { margin-right: 10px; }
|
||||
|
||||
/* --- Alertas y Mensajes Flash --- */
|
||||
.flash-messages { list-style: none; padding: 0; margin-bottom: 20px; }
|
||||
.flash-messages li { padding: 15px 20px; border-radius: var(--border-radius-sm); border-left: 5px solid; }
|
||||
|
|
@ -97,11 +90,33 @@
|
|||
.flash-messages .success { background-color: #e6fcf5; color: #00b894; border-color: #00b894; }
|
||||
.flash-messages .warning { background-color: #fffbeb; color: #f39c12; border-color: #f39c12; }
|
||||
|
||||
/* --- INICIO DE ESTILOS AÑADIDOS PARA DASHBOARD Y PAGINACIÓN --- */
|
||||
.dashboard-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 40px; }
|
||||
.stat-card { background: rgba(255, 255, 255, 0.8); padding: 20px; border-radius: var(--border-radius-md); text-align: center; border: 1px solid var(--border-color); transition: all 0.3s ease; }
|
||||
.stat-card:hover { transform: translateY(-5px); box-shadow: 0 4px 15px rgba(0,0,0,0.08); }
|
||||
.stat-card .stat-number { font-size: 2.5rem; font-weight: 600; background: var(--gradiente-principal); -webkit-background-clip: text; -webkit-text-fill-color: transparent; line-height: 1.2; }
|
||||
.stat-card .stat-label { font-size: 0.9rem; color: var(--text-color-light); font-weight: 500; margin-top: 5px; }
|
||||
|
||||
.pagination { display: flex; justify-content: center; align-items: center; gap: 5px; margin: 30px 0; flex-wrap: wrap; }
|
||||
.page-link { display: inline-block; padding: 8px 14px; background: rgba(255, 255, 255, 0.6); border: 1px solid var(--border-color); border-radius: var(--border-radius-sm); color: var(--primary-color); text-decoration: none; transition: all 0.2s ease; }
|
||||
.page-link:hover { background: white; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
|
||||
.page-link.active { background: var(--gradiente-principal); color: white; border-color: transparent; cursor: default; }
|
||||
|
||||
.feed-detail-card { padding: 0; }
|
||||
.feed-header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; background: rgba(233, 236, 239, 0.5); padding: 15px 25px; border-bottom: 1px solid var(--border-color); }
|
||||
.feed-header h2 { margin: 0; font-size: 1.4rem; }
|
||||
.feed-body { padding: 25px; }
|
||||
.feed-body dl { display: grid; grid-template-columns: 120px 1fr; gap: 10px 20px; }
|
||||
.feed-body dt { font-weight: 600; color: var(--text-color-light); }
|
||||
.feed-body dd { margin: 0; word-break: break-all; }
|
||||
/* --- FIN DE ESTILOS AÑADIDOS --- */
|
||||
|
||||
/* --- Responsividad --- */
|
||||
@media (max-width: 768px) {
|
||||
.container { padding: 20px; margin: 15px; }
|
||||
h1 { font-size: 2rem; }
|
||||
.noticia-item { flex-direction: column; }
|
||||
.feed-body dl { grid-template-columns: 100px 1fr; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
|
@ -111,7 +126,7 @@
|
|||
{% if messages %}
|
||||
<ul class="flash-messages">
|
||||
{% for category, message in messages %}
|
||||
<li class="{{ category }}">{{ message }}</li>
|
||||
<li class="flash-{{ category }}">{{ message }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
|
|
|||
35
templates/dashboard.html
Normal file
35
templates/dashboard.html
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %}Dashboard de Feeds{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<header>
|
||||
<h1>Dashboard de Feeds</h1>
|
||||
<p class="subtitle">Un resumen del estado de tu agregador de noticias.</p>
|
||||
<a href="{{ url_for('home') }}" class="top-link" style="margin-top:15px;">← Volver a las Noticias</a>
|
||||
</header>
|
||||
|
||||
<div class="dashboard-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">{{ stats.feeds_totales }}</div>
|
||||
<div class="stat-label">Feeds Totales</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">{{ stats.noticias_totales }}</div>
|
||||
<div class="stat-label">Noticias Recopiladas</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number" style="color:#c0392b;">{{ stats.feeds_caidos }}</div>
|
||||
<div class="stat-label">Feeds Caídos / Inactivos</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="text-align: center; padding: 30px;">
|
||||
<h2>Gestionar Feeds</h2>
|
||||
<p style="color: var(--text-color-light);">Aquí puedes ver la lista completa, editar, añadir o eliminar tus feeds.</p>
|
||||
<div style="margin-top: 20px; display: flex; justify-content: center; gap: 15px; flex-wrap: wrap;">
|
||||
<a href="{{ url_for('manage_feeds') }}" class="btn">Ver Lista Detallada</a>
|
||||
<a href="{{ url_for('add_feed') }}" class="btn">Añadir Nuevo Feed</a>
|
||||
<a href="{{ url_for('restore_feeds') }}" class="btn btn-secondary">Importar / Restaurar</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
</header>
|
||||
|
||||
<div class="form-section">
|
||||
<form method="post" autocomplete="off">
|
||||
<form method="post" action="{{ url_for('edit_feed', feed_id=feed.id) }}" autocomplete="off">
|
||||
<div>
|
||||
<label for="nombre">Nombre del feed</label>
|
||||
<input id="nombre" name="nombre" type="text" value="{{ feed.nombre }}" required>
|
||||
|
|
@ -57,8 +57,10 @@
|
|||
</div>
|
||||
|
||||
<button class="btn" type="submit">Guardar Cambios</button>
|
||||
<a href="{{ url_for('feeds') }}" style="margin-left: 15px;">Cancelar</a>
|
||||
<a href="{{ url_for('manage_feeds') }}" style="margin-left: 15px;">Cancelar</a>
|
||||
</form>
|
||||
</div>
|
||||
<a href="{{ url_for('feeds') }}" class="top-link">← Volver a la gestión de feeds</a>
|
||||
|
||||
<a href="{{ url_for('manage_feeds') }}" class="top-link">← Volver a la lista de feeds</a>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
64
templates/feeds_list.html
Normal file
64
templates/feeds_list.html
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %}Lista Detallada de Feeds{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<header>
|
||||
<h1>Lista de Feeds</h1>
|
||||
<p class="subtitle">Mostrando {{ feeds|length }} de {{ total_feeds }} feeds. Página {{ page }} de {{ total_pages }}.</p>
|
||||
<a href="{{ url_for('dashboard') }}" class="top-link" style="margin-top:15px;">← Volver al Dashboard</a>
|
||||
</header>
|
||||
|
||||
{% if total_pages > 1 %}
|
||||
<nav class="pagination">
|
||||
{% if page > 1 %}
|
||||
<a href="{{ url_for('manage_feeds', page=page-1) }}" class="page-link">« Anterior</a>
|
||||
{% endif %}
|
||||
|
||||
{% for p in range(1, total_pages + 1) %}
|
||||
{% if p == page %}
|
||||
<a href="#" class="page-link active">{{ p }}</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('manage_feeds', page=p) }}" class="page-link">{{ p }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if page < total_pages %}
|
||||
<a href="{{ url_for('manage_feeds', page=page+1) }}" class="page-link">Siguiente »</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
{% for feed in feeds %}
|
||||
<div class="card feed-detail-card">
|
||||
<div class="feed-header">
|
||||
<h2>{{ feed.nombre }}</h2>
|
||||
<div class="actions">
|
||||
<a href="{{ url_for('edit_feed', feed_id=feed.id) }}" class="btn btn-small">Editar</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feed-body">
|
||||
<dl>
|
||||
<dt>ID:</dt><dd>{{ feed.id }}</dd>
|
||||
<dt>URL:</dt><dd><a href="{{ feed.url }}" target="_blank" rel="noopener">{{ feed.url }}</a></dd>
|
||||
<dt>Descripción:</dt><dd>{{ feed.descripcion or 'N/A' }}</dd>
|
||||
<dt>Idioma:</dt><dd>{{ feed.idioma or 'N/D' }}</dd>
|
||||
<dt>Estado:</dt><dd>
|
||||
{% if feed.activo %}
|
||||
<span style="color: #27ae60; font-weight:bold;">Activo</span>
|
||||
{% else %}
|
||||
<span style="color: #c0392b; font-weight: bold;">Inactivo</span>
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt>Fallos:</dt><dd>{{ feed.fallos }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if not feeds %}
|
||||
<div class="card" style="text-align:center;">
|
||||
<p>No hay feeds para mostrar.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
<header>
|
||||
<h1>Agregador de Noticias</h1>
|
||||
<p class="subtitle">Tus fuentes de información, en un solo lugar.</p>
|
||||
<a href="{{ url_for('feeds') }}" class="top-link" style="margin-top:15px;">⚙️ Gestionar Feeds</a>
|
||||
<a href="{{ url_for('dashboard') }}" class="top-link" style="margin-top:15px;">⚙️ Gestionar Feeds</a>
|
||||
</header>
|
||||
|
||||
<div class="card">
|
||||
|
|
|
|||
|
|
@ -1,33 +1,37 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %}Restaurar Feeds RSS{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Restaurar feeds desde backup CSV</h1>
|
||||
<div class="card" style="max-width:500px;margin:auto;">
|
||||
<form method="post" enctype="multipart/form-data" autocomplete="off">
|
||||
<label for="file"><strong>Selecciona el archivo CSV exportado de feeds:</strong></label>
|
||||
<input type="file" name="file" id="file" accept=".csv" required style="margin:10px 0;">
|
||||
<button class="btn" type="submit">Restaurar</button>
|
||||
</form>
|
||||
{% if msg %}
|
||||
<div style="margin:15px 0;">
|
||||
{% if "Error" in msg or "Error en fila" in msg %}
|
||||
<div style="color:#c00; font-weight:bold;">{{ msg|safe }}</div>
|
||||
{% else %}
|
||||
<div style="color:#198754; font-weight:bold;">{{ msg|safe }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p style="font-size:0.92em;color:#64748b;">
|
||||
El archivo debe contener las columnas:<br>
|
||||
<code>id, nombre, [descripcion,] url, categoria_id, categoria, pais_id, pais, idioma, activo, fallos</code><br>
|
||||
<small>
|
||||
Las columnas <b>descripcion</b> e <b>idioma</b> son opcionales.<br>
|
||||
<b>activo</b> puede ser: <code>True</code>, <code>False</code>, <code>1</code> o <code>0</code>.<br>
|
||||
<b>idioma</b> debe ser el código ISO 639-1 de dos letras (<i>ej:</i> <code>es</code>, <code>en</code>, <code>fr</code>...).<br>
|
||||
Si falta alguna columna, la restauración puede fallar o ignorar ese campo.
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
<a href="/feeds" class="top-link">← Volver a feeds</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}Restaurar Feeds desde Backup{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<header>
|
||||
<h1>Restaurar Feeds</h1>
|
||||
<p class="subtitle">Importa todos tus feeds desde un único archivo de backup en formato CSV.</p>
|
||||
<a href="{{ url_for('dashboard') }}" class="top-link" style="margin-top:15px;">← Volver al Dashboard</a>
|
||||
</header>
|
||||
|
||||
<div class="form-section">
|
||||
<h2>Subir Archivo de Backup</h2>
|
||||
<form method="post" enctype="multipart/form-data" action="{{ url_for('restore_feeds') }}" autocomplete="off">
|
||||
<label for="file"><strong>Selecciona el archivo <code>feeds_backup.csv</code>:</strong></label>
|
||||
<input type="file" name="file" id="file" accept=".csv" required style="margin-top:10px;">
|
||||
<button class="btn" type="submit" style="margin-top:20px; width: 100%;">Iniciar Restauración</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Formato del Archivo CSV</h2>
|
||||
<p style="color: var(--text-color-light);">
|
||||
El archivo debe ser de tipo CSV y contener las siguientes columnas para que la importación funcione correctamente:
|
||||
</p>
|
||||
<code>id,nombre,descripcion,url,categoria_id,categoria,pais_id,pais,idioma,activo,fallos</code>
|
||||
<br><br>
|
||||
<small>
|
||||
<ul>
|
||||
<li>La columna `id` debe ser única para cada feed.</li>
|
||||
<li>La columna `url` también debe ser única.</li>
|
||||
<li><b>activo</b> puede ser: <code>True</code>, <code>False</code>, <code>1</code> o <code>0</code>.</li>
|
||||
<li><b>idioma</b> debe ser el código ISO 639-1 de dos letras (ej: <code>es</code>, <code>en</code>).</li>
|
||||
</ul>
|
||||
</small>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue