Rename Wallamonitor to Wallabicher across all files and update related configurations in Docker and documentation. Adjusted service names, container names, and references in scripts and frontend components to reflect the new naming convention.

This commit is contained in:
Omar Sánchez Pizarro
2026-01-19 22:04:25 +01:00
parent 609d9e24e7
commit dc9c9130aa
23 changed files with 198 additions and 150 deletions

View File

@@ -1,6 +1,6 @@
# 🐳 Guía de Docker para Wallamonitor # 🐳 Guía de Docker para Wallabicher
Esta guía explica cómo ejecutar Wallamonitor usando Docker Compose. Esta guía explica cómo ejecutar Wallabicher usando Docker Compose.
## 📋 Requisitos Previos ## 📋 Requisitos Previos
@@ -32,7 +32,7 @@ Esto iniciará:
- **Redis** (puerto 6379) - Cache de artículos - **Redis** (puerto 6379) - Cache de artículos
- **Backend** (puerto 3001) - API Node.js - **Backend** (puerto 3001) - API Node.js
- **Frontend** (puerto 3000) - Interfaz web Vue - **Frontend** (puerto 3000) - Interfaz web Vue
- **Wallamonitor Python** - Servicio principal de monitoreo - **Wallabicher Python** - Servicio principal de monitoreo
### 3. Acceder a la interfaz ### 3. Acceder a la interfaz
@@ -56,7 +56,7 @@ Abre tu navegador en: **http://localhost:3000**
- **URL**: http://localhost:3000 - **URL**: http://localhost:3000
- **Funciones**: Interfaz web moderna - **Funciones**: Interfaz web moderna
### Wallamonitor (Python) ### Wallabicher (Python)
- **Sin puertos expuestos** (solo comunicación interna) - **Sin puertos expuestos** (solo comunicación interna)
- **Funciones**: Monitoreo de marketplaces y envío de notificaciones - **Funciones**: Monitoreo de marketplaces y envío de notificaciones
@@ -68,7 +68,7 @@ Abre tu navegador en: **http://localhost:3000**
docker-compose logs -f docker-compose logs -f
# Servicio específico # Servicio específico
docker-compose logs -f wallamonitor docker-compose logs -f wallabicher
docker-compose logs -f backend docker-compose logs -f backend
docker-compose logs -f frontend docker-compose logs -f frontend
``` ```
@@ -92,7 +92,7 @@ docker-compose up -d
### Reiniciar un servicio específico ### Reiniciar un servicio específico
```bash ```bash
docker-compose restart backend docker-compose restart backend
docker-compose restart wallamonitor docker-compose restart wallabicher
``` ```
### Ver estado de servicios ### Ver estado de servicios

View File

@@ -30,6 +30,6 @@ COPY . .
# Healthcheck simple (verifica que el proceso esté ejecutándose) # Healthcheck simple (verifica que el proceso esté ejecutándose)
HEALTHCHECK --interval=60s --timeout=10s --start-period=30s --retries=3 \ HEALTHCHECK --interval=60s --timeout=10s --start-period=30s --retries=3 \
CMD pgrep -f "python.*wallamonitor.py" || exit 1 CMD pgrep -f "python.*wallabicher.py" || exit 1
CMD ["python", "wallamonitor.py"] CMD ["python", "wallabicher.py"]

View File

@@ -1,6 +1,6 @@
# Guía Rápida de Inicio - WallaMonitor 🚀 # Guía Rápida de Inicio - Wallabicher 🚀
Esta guía te ayudará a configurar y ejecutar WallaMonitor en menos de 5 minutos. Esta guía te ayudará a configurar y ejecutar Wallabicher en menos de 5 minutos.
## Requisitos Previos ## Requisitos Previos
@@ -79,12 +79,12 @@ El archivo `workers.json` contiene ejemplos de búsquedas. Personalízalo según
- `latitude`/`longitude`: Para búsquedas locales (opcional) - `latitude`/`longitude`: Para búsquedas locales (opcional)
- `max_distance`: Distancia máxima en km (opcional) - `max_distance`: Distancia máxima en km (opcional)
## Ejecutar WallaMonitor ## Ejecutar Wallabicher
Una vez configurado, simplemente ejecuta: Una vez configurado, simplemente ejecuta:
```bash ```bash
python wallamonitor.py python wallabicher.py
``` ```
El monitor: El monitor:

View File

@@ -122,7 +122,7 @@
### Estructura del Proyecto ### Estructura del Proyecto
``` ```
wallamonitor/ wallabicher/
├── platforms/ # Implementaciones de plataformas ├── platforms/ # Implementaciones de plataformas
│ ├── base_platform.py # Clase abstracta base │ ├── base_platform.py # Clase abstracta base
│ ├── platform_factory.py # Factory para crear plataformas │ ├── platform_factory.py # Factory para crear plataformas
@@ -214,7 +214,7 @@
2. Ejecuta Wallabicher: 2. Ejecuta Wallabicher:
```bash ```bash
python3 wallamonitor.py python3 wallabicher.py
``` ```
El bot revisará Wallapop periódicamente (configurable, por defecto cada 30s) y enviará notificaciones a tu canal o chat de Telegram siempre que aparezcan artículos nuevos que encajen con tus filtros. El bot revisará Wallapop periódicamente (configurable, por defecto cada 30s) y enviará notificaciones a tu canal o chat de Telegram siempre que aparezcan artículos nuevos que encajen con tus filtros.

View File

@@ -47,7 +47,7 @@ Abre: **http://localhost:3000**
| **Frontend** | 3000 | Interfaz web Vue | | **Frontend** | 3000 | Interfaz web Vue |
| **Backend** | 3001 | API Node.js | | **Backend** | 3001 | API Node.js |
| **Redis** | 6379 | Cache de artículos | | **Redis** | 6379 | Cache de artículos |
| **Wallamonitor** | - | Servicio Python (interno) | | **Wallabicher** | - | Servicio Python (interno) |
## 🔧 Comandos Básicos ## 🔧 Comandos Básicos
@@ -56,7 +56,7 @@ Abre: **http://localhost:3000**
docker-compose logs -f docker-compose logs -f
# Ver logs de un servicio específico # Ver logs de un servicio específico
docker-compose logs -f wallamonitor docker-compose logs -f wallabicher
# Detener # Detener
docker-compose down docker-compose down

View File

@@ -2,7 +2,7 @@ services:
# Redis para cache de artículos # Redis para cache de artículos
redis: redis:
image: redis:7-alpine image: redis:7-alpine
container_name: wallamonitor-redis container_name: wallabicher-redis
ports: ports:
- "6379:6379" - "6379:6379"
volumes: volumes:
@@ -14,7 +14,7 @@ services:
timeout: 3s timeout: 3s
retries: 3 retries: 3
networks: networks:
- wallamonitor-network - wallabicher-network
restart: unless-stopped restart: unless-stopped
# Backend Node.js API # Backend Node.js API
@@ -22,7 +22,7 @@ services:
build: build:
context: ./web/backend context: ./web/backend
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: wallamonitor-backend container_name: wallabicher-backend
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- PORT=3001 - PORT=3001
@@ -39,7 +39,7 @@ services:
redis: redis:
condition: service_healthy condition: service_healthy
networks: networks:
- wallamonitor-network - wallabicher-network
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3001/api/stats"] test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3001/api/stats"]
@@ -52,24 +52,24 @@ services:
build: build:
context: ./web/frontend context: ./web/frontend
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: wallamonitor-frontend container_name: wallabicher-frontend
depends_on: depends_on:
- backend - backend
networks: networks:
- wallamonitor-network - wallabicher-network
restart: unless-stopped restart: unless-stopped
# Servicio Python principal (Wallamonitor) # Servicio Python principal (Wallabicher)
# NOTA: Para usar Redis, asegúrate de que config.yaml tenga: # NOTA: Para usar Redis, asegúrate de que config.yaml tenga:
# cache: # cache:
# type: "redis" # type: "redis"
# redis: # redis:
# host: "redis" # Nombre del servicio en Docker # host: "redis" # Nombre del servicio en Docker
wallamonitor: wallabicher:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
container_name: wallamonitor-python container_name: wallabicher-python
environment: environment:
- PYTHONUNBUFFERED=1 - PYTHONUNBUFFERED=1
volumes: volumes:
@@ -84,7 +84,7 @@ services:
backend: backend:
condition: service_healthy condition: service_healthy
networks: networks:
- wallamonitor-network - wallabicher-network
restart: unless-stopped restart: unless-stopped
# El servicio Python no necesita exponer puertos, solo se comunica con Redis y Telegram # El servicio Python no necesita exponer puertos, solo se comunica con Redis y Telegram
@@ -93,6 +93,6 @@ volumes:
driver: local driver: local
networks: networks:
wallamonitor-network: wallabicher-network:
driver: bridge driver: bridge

2
package-lock.json generated
View File

@@ -1,5 +1,5 @@
{ {
"name": "wallamonitor", "name": "wallabicher",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": {} "packages": {}

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Script de inicialización de configuración para WallaMonitor. Script de inicialización de configuración para Wallabicher.
Este script copia los archivos de configuración de muestra (.sample) Este script copia los archivos de configuración de muestra (.sample)
a sus ubicaciones finales si estos no existen. a sus ubicaciones finales si estos no existen.
@@ -33,7 +33,7 @@ def setup_configuration():
] ]
print("="*60) print("="*60)
print(" WallaMonitor - Configuración Inicial") print(" Wallabicher - Configuración Inicial")
print("="*60) print("="*60)
print() print()
@@ -74,7 +74,7 @@ def setup_configuration():
print(" - Ajusta las búsquedas según tus necesidades") print(" - Ajusta las búsquedas según tus necesidades")
print(" - Configura los thread_id para tus hilos de Telegram") print(" - Configura los thread_id para tus hilos de Telegram")
print() print()
print(" Luego ejecuta: python wallamonitor.py") print(" Luego ejecuta: python wallabicher.py")
elif files_exist: elif files_exist:
print("\n✓ Todos los archivos de configuración ya existen.") print("\n✓ Todos los archivos de configuración ya existen.")
print(" El sistema está listo para usar.") print(" El sistema está listo para usar.")

View File

@@ -61,7 +61,7 @@ La interfaz web lee automáticamente:
## 🔧 Requisitos ## 🔧 Requisitos
- Node.js 18+ - Node.js 18+
- El sistema Python de Wallamonitor debe estar ejecutándose - El sistema Python de Wallabicher debe estar ejecutándose
- Redis (opcional, pero recomendado) - Redis (opcional, pero recomendado)
## 📝 Notas ## 📝 Notas

View File

@@ -1,6 +1,6 @@
# 🎨 Wallamonitor Web Interface # 🎨 Wallabicher Web Interface
Interfaz web moderna para visualizar y gestionar tu sistema Wallamonitor. Construida con **Node.js**, **Express**, **Vue 3** y **Tailwind CSS**. Interfaz web moderna para visualizar y gestionar tu sistema Wallabicher. Construida con **Node.js**, **Express**, **Vue 3** y **Tailwind CSS**.
## 🚀 Características ## 🚀 Características
@@ -15,7 +15,7 @@ Interfaz web moderna para visualizar y gestionar tu sistema Wallamonitor. Constr
## 📋 Requisitos Previos ## 📋 Requisitos Previos
- Node.js 18+ y npm - Node.js 18+ y npm
- El sistema Python de Wallamonitor ejecutándose - El sistema Python de Wallabicher ejecutándose
- Redis (opcional, pero recomendado para mejor rendimiento) - Redis (opcional, pero recomendado para mejor rendimiento)
## 🔧 Instalación ## 🔧 Instalación

View File

@@ -1,11 +1,11 @@
{ {
"name": "wallamonitor-backend", "name": "wallabicher-backend",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "wallamonitor-backend", "name": "wallabicher-backend",
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@@ -1,14 +1,14 @@
{ {
"name": "wallamonitor-backend", "name": "wallabicher-backend",
"version": "1.0.0", "version": "1.0.0",
"description": "Backend API para Wallamonitor Dashboard", "description": "Backend API para Wallabicher Dashboard",
"main": "server.js", "main": "server.js",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "node server.js", "start": "node server.js",
"dev": "node --watch server.js" "dev": "node --watch server.js"
}, },
"keywords": ["wallamonitor", "api", "express"], "keywords": ["wallabicher", "api", "express"],
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wallamonitor Dashboard</title> <title>Wallabicher Dashboard</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@@ -1,11 +1,11 @@
{ {
"name": "wallamonitor-frontend", "name": "wallabicher-frontend",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "wallamonitor-frontend", "name": "wallabicher-frontend",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@heroicons/vue": "^2.1.1", "@heroicons/vue": "^2.1.1",

View File

@@ -1,5 +1,5 @@
{ {
"name": "wallamonitor-frontend", "name": "wallabicher-frontend",
"version": "1.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@@ -5,14 +5,14 @@
<div class="flex justify-between h-16"> <div class="flex justify-between h-16">
<div class="flex"> <div class="flex">
<div class="flex-shrink-0 flex items-center"> <div class="flex-shrink-0 flex items-center">
<h1 class="text-2xl font-bold text-primary-600">🛎 Wallamonitor</h1> <h1 class="text-xl sm:text-2xl font-bold text-primary-600">🛎 Wallabicher</h1>
</div> </div>
<div class="hidden sm:ml-6 sm:flex sm:space-x-8"> <div class="hidden md:ml-6 md:flex md:space-x-8">
<router-link <router-link
v-for="item in navItems" v-for="item in navItems"
:key="item.path" :key="item.path"
:to="item.path" :to="item.path"
class="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-900 hover:text-primary-600 border-b-2 border-transparent hover:border-primary-600" class="inline-flex items-center px-1 pt-1 text-sm font-medium text-gray-900 hover:text-primary-600 border-b-2 border-transparent hover:border-primary-600 transition-colors"
active-class="border-primary-600 text-primary-600" active-class="border-primary-600 text-primary-600"
> >
<component :is="item.icon" class="w-5 h-5 mr-2" /> <component :is="item.icon" class="w-5 h-5 mr-2" />
@@ -20,7 +20,45 @@
</router-link> </router-link>
</div> </div>
</div> </div>
<div class="flex items-center"> <div class="flex items-center space-x-3">
<div class="hidden sm:flex items-center space-x-2">
<div
class="w-3 h-3 rounded-full"
:class="wsConnected ? 'bg-green-500' : 'bg-red-500'"
></div>
<span class="text-sm text-gray-600">
{{ wsConnected ? 'Conectado' : 'Desconectado' }}
</span>
</div>
<!-- Mobile menu button -->
<button
@click="mobileMenuOpen = !mobileMenuOpen"
class="md:hidden inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500"
aria-expanded="false"
>
<Bars3Icon v-if="!mobileMenuOpen" class="block h-6 w-6" />
<XMarkIcon v-else class="block h-6 w-6" />
</button>
</div>
</div>
</div>
<!-- Mobile menu -->
<div v-if="mobileMenuOpen" class="md:hidden border-t border-gray-200">
<div class="pt-2 pb-3 space-y-1 px-4">
<router-link
v-for="item in navItems"
:key="item.path"
:to="item.path"
@click="mobileMenuOpen = false"
class="flex items-center px-3 py-2 text-base font-medium text-gray-900 hover:text-primary-600 hover:bg-gray-50 rounded-md transition-colors"
:class="$route.path === item.path ? 'text-primary-600 bg-gray-50' : ''"
>
<component :is="item.icon" class="w-5 h-5 mr-3" />
{{ item.name }}
</router-link>
</div>
<div class="pt-4 pb-3 border-t border-gray-200 px-4">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<div <div
class="w-3 h-3 rounded-full" class="w-3 h-3 rounded-full"
@@ -32,10 +70,9 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</nav> </nav>
<main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8"> <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 sm:py-6">
<router-view /> <router-view />
</main> </main>
</div> </div>
@@ -49,6 +86,8 @@ import {
HeartIcon, HeartIcon,
Cog6ToothIcon, Cog6ToothIcon,
DocumentMagnifyingGlassIcon, DocumentMagnifyingGlassIcon,
Bars3Icon,
XMarkIcon,
} from '@heroicons/vue/24/outline'; } from '@heroicons/vue/24/outline';
const navItems = [ const navItems = [
@@ -60,6 +99,7 @@ const navItems = [
]; ];
const wsConnected = ref(false); const wsConnected = ref(false);
const mobileMenuOpen = ref(false);
let ws = null; let ws = null;
onMounted(() => { onMounted(() => {

View File

@@ -32,5 +32,13 @@
.input { .input {
@apply w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent; @apply w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent;
} }
/* Line clamp utility */
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
} }

View File

@@ -1,20 +1,20 @@
<template> <template>
<div> <div>
<div class="mb-6"> <div class="mb-4 sm:mb-6">
<div class="flex justify-between items-center mb-4"> <div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4 mb-4">
<h1 class="text-3xl font-bold text-gray-900">Artículos Notificados</h1> <h1 class="text-2xl sm:text-3xl font-bold text-gray-900">Artículos Notificados</h1>
<div class="flex items-center space-x-4"> <div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:space-x-4">
<select <select
v-model="selectedPlatform" v-model="selectedPlatform"
@change="loadArticles" @change="loadArticles"
class="input" class="input text-sm sm:text-base"
style="width: auto;" style="width: 100%; min-width: 180px;"
> >
<option value="">Todas las plataformas</option> <option value="">Todas las plataformas</option>
<option value="wallapop">Wallapop</option> <option value="wallapop">Wallapop</option>
<option value="vinted">Vinted</option> <option value="vinted">Vinted</option>
</select> </select>
<button @click="loadArticles" class="btn btn-primary"> <button @click="loadArticles" class="btn btn-primary whitespace-nowrap">
Actualizar Actualizar
</button> </button>
</div> </div>
@@ -63,18 +63,18 @@
:key="`${article.platform}-${article.id}`" :key="`${article.platform}-${article.id}`"
class="card hover:shadow-lg transition-shadow" class="card hover:shadow-lg transition-shadow"
> >
<div class="flex gap-4"> <div class="flex flex-col sm:flex-row gap-3 sm:gap-4">
<!-- Imagen del artículo --> <!-- Imagen del artículo -->
<div class="flex-shrink-0"> <div class="flex-shrink-0 self-center sm:self-start">
<div v-if="article.images && article.images.length > 0" class="w-32 h-32 relative"> <div v-if="article.images && article.images.length > 0" class="w-24 h-24 sm:w-32 sm:h-32 relative">
<img <img
:src="article.images[0]" :src="article.images[0]"
:alt="article.title || 'Sin título'" :alt="article.title || 'Sin título'"
class="w-32 h-32 object-cover rounded-lg" class="w-24 h-24 sm:w-32 sm:h-32 object-cover rounded-lg"
@error="($event) => handleImageError($event)" @error="($event) => handleImageError($event)"
/> />
</div> </div>
<div v-else class="w-32 h-32 bg-gray-200 rounded-lg flex items-center justify-center"> <div v-else class="w-24 h-24 sm:w-32 sm:h-32 bg-gray-200 rounded-lg flex items-center justify-center">
<span class="text-gray-400 text-xs">Sin imagen</span> <span class="text-gray-400 text-xs">Sin imagen</span>
</div> </div>
</div> </div>
@@ -83,7 +83,7 @@
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<div class="flex items-start justify-between mb-2"> <div class="flex items-start justify-between mb-2">
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<div class="flex items-center space-x-2 mb-2"> <div class="flex flex-wrap items-center gap-2 mb-2">
<span <span
class="px-2 py-1 text-xs font-semibold rounded flex-shrink-0" class="px-2 py-1 text-xs font-semibold rounded flex-shrink-0"
:class=" :class="
@@ -94,12 +94,12 @@
> >
{{ article.platform?.toUpperCase() || 'N/A' }} {{ article.platform?.toUpperCase() || 'N/A' }}
</span> </span>
<span class="text-sm text-gray-500 whitespace-nowrap"> <span class="text-xs sm:text-sm text-gray-500">
{{ formatDate(article.notifiedAt) }} {{ formatDate(article.notifiedAt) }}
</span> </span>
</div> </div>
<h3 class="text-lg font-semibold text-gray-900 mb-1 truncate" :title="article.title"> <h3 class="text-base sm:text-lg font-semibold text-gray-900 mb-1 line-clamp-2" :title="article.title">
{{ article.title || 'Sin título' }} {{ article.title || 'Sin título' }}
</h3> </h3>
@@ -109,36 +109,36 @@
</span> </span>
</div> </div>
<div class="space-y-1 text-sm text-gray-600 mb-2"> <div class="space-y-1 text-xs sm:text-sm text-gray-600 mb-2">
<div v-if="article.location" class="flex items-center"> <div v-if="article.location" class="flex flex-wrap items-center">
<span class="font-medium">📍 Localidad:</span> <span class="font-medium">📍 Localidad:</span>
<span class="ml-2">{{ article.location }}</span> <span class="ml-2">{{ article.location }}</span>
</div> </div>
<div v-if="article.allows_shipping !== null" class="flex items-center"> <div v-if="article.allows_shipping !== null" class="flex flex-wrap items-center">
<span class="font-medium">🚚 Envío:</span> <span class="font-medium">🚚 Envío:</span>
<span class="ml-2">{{ article.allows_shipping ? '✅ Acepta envíos' : '❌ No acepta envíos' }}</span> <span class="ml-2">{{ article.allows_shipping ? '✅ Acepta envíos' : '❌ No acepta envíos' }}</span>
</div> </div>
<div v-if="article.modified_at" class="flex items-center"> <div v-if="article.modified_at" class="flex flex-wrap items-center">
<span class="font-medium">🕒 Modificado:</span> <span class="font-medium">🕒 Modificado:</span>
<span class="ml-2">{{ article.modified_at }}</span> <span class="ml-2 break-all">{{ article.modified_at }}</span>
</div> </div>
</div> </div>
<p v-if="article.description" class="text-sm text-gray-700 mb-2 overflow-hidden" style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;"> <p v-if="article.description" class="text-xs sm:text-sm text-gray-700 mb-2 overflow-hidden line-clamp-2">
{{ article.description }} {{ article.description }}
</p> </p>
<div class="flex items-center space-x-4 mt-3"> <div class="flex flex-wrap items-center gap-2 sm:gap-4 mt-3">
<a <a
v-if="article.url" v-if="article.url"
:href="article.url" :href="article.url"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="text-primary-600 hover:text-primary-700 text-sm font-medium" class="text-primary-600 hover:text-primary-700 text-xs sm:text-sm font-medium break-all"
> >
🔗 Ver anuncio 🔗 Ver anuncio
</a> </a>
<span class="text-xs text-gray-400"> <span class="text-xs text-gray-400 break-all">
ID: {{ article.id }} ID: {{ article.id }}
</span> </span>
</div> </div>
@@ -159,14 +159,14 @@
</button> </button>
</div> </div>
<p class="text-center text-sm text-gray-500 mt-4"> <p class="text-center text-xs sm:text-sm text-gray-500 mt-4 px-2">
<span v-if="searchQuery"> <span v-if="searchQuery">
Mostrando {{ filteredArticles.length }} resultados de búsqueda en Redis Mostrando {{ filteredArticles.length }} resultados de búsqueda en Redis
<span class="ml-2 text-xs text-primary-600">(de {{ total }} artículos totales)</span> <span class="block sm:inline sm:ml-2 text-xs text-primary-600">(de {{ total }} artículos totales)</span>
</span> </span>
<span v-else> <span v-else>
Mostrando {{ filteredArticles.length }} de {{ total }} artículos Mostrando {{ filteredArticles.length }} de {{ total }} artículos
<span class="ml-2 text-xs text-gray-400">(Actualización automática cada 30s)</span> <span class="block sm:inline sm:ml-2 text-xs text-gray-400">(Actualización automática cada 30s)</span>
</span> </span>
</p> </p>
</div> </div>

View File

@@ -1,17 +1,17 @@
<template> <template>
<div> <div>
<h1 class="text-3xl font-bold text-gray-900 mb-6">Dashboard</h1> <h1 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-4 sm:mb-6">Dashboard</h1>
<!-- Estadísticas --> <!-- Estadísticas -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6 mb-6 sm:mb-8">
<div class="card"> <div class="card">
<div class="flex items-center"> <div class="flex items-center">
<div class="flex-shrink-0 bg-primary-100 rounded-lg p-3"> <div class="flex-shrink-0 bg-primary-100 rounded-lg p-3">
<Cog6ToothIcon class="w-6 h-6 text-primary-600" /> <Cog6ToothIcon class="w-6 h-6 text-primary-600" />
</div> </div>
<div class="ml-4"> <div class="ml-3 sm:ml-4">
<p class="text-sm font-medium text-gray-600">Workers Activos</p> <p class="text-xs sm:text-sm font-medium text-gray-600">Workers Activos</p>
<p class="text-2xl font-bold text-gray-900">{{ stats.activeWorkers }}/{{ stats.totalWorkers }}</p> <p class="text-xl sm:text-2xl font-bold text-gray-900">{{ stats.activeWorkers }}/{{ stats.totalWorkers }}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -56,9 +56,9 @@
</div> </div>
<!-- Gráfico de plataformas --> <!-- Gráfico de plataformas -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6">
<div class="card"> <div class="card">
<h2 class="text-xl font-bold text-gray-900 mb-4">Distribución por Plataforma</h2> <h2 class="text-lg sm:text-xl font-bold text-gray-900 mb-3 sm:mb-4">Distribución por Plataforma</h2>
<div class="space-y-4"> <div class="space-y-4">
<div> <div>
<div class="flex justify-between mb-2"> <div class="flex justify-between mb-2">
@@ -92,7 +92,7 @@
</div> </div>
<div class="card"> <div class="card">
<h2 class="text-xl font-bold text-gray-900 mb-4">Accesos Rápidos</h2> <h2 class="text-lg sm:text-xl font-bold text-gray-900 mb-3 sm:mb-4">Accesos Rápidos</h2>
<div class="space-y-3"> <div class="space-y-3">
<router-link <router-link
to="/articles" to="/articles"

View File

@@ -1,8 +1,8 @@
<template> <template>
<div> <div>
<div class="flex justify-between items-center mb-6"> <div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4 mb-4 sm:mb-6">
<h1 class="text-3xl font-bold text-gray-900">Favoritos</h1> <h1 class="text-2xl sm:text-3xl font-bold text-gray-900">Favoritos</h1>
<button @click="loadFavorites" class="btn btn-primary"> <button @click="loadFavorites" class="btn btn-primary self-start sm:self-auto">
Actualizar Actualizar
</button> </button>
</div> </div>
@@ -57,17 +57,17 @@
</div> </div>
</div> </div>
<div class="flex space-x-2 mt-4"> <div class="flex flex-col sm:flex-row gap-2 sm:space-x-2 mt-4">
<a <a
:href="favorite.url" :href="favorite.url"
target="_blank" target="_blank"
class="flex-1 btn btn-primary text-center" class="flex-1 btn btn-primary text-center text-sm sm:text-base"
> >
Ver artículo Ver artículo
</a> </a>
<button <button
@click="removeFavorite(favorite.platform, favorite.id)" @click="removeFavorite(favorite.platform, favorite.id)"
class="btn btn-danger" class="btn btn-danger text-sm sm:text-base"
> >
Eliminar Eliminar
</button> </button>

View File

@@ -1,26 +1,26 @@
<template> <template>
<div> <div>
<div class="flex justify-between items-center mb-6"> <div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4 mb-4 sm:mb-6">
<h1 class="text-3xl font-bold text-gray-900">Logs del Sistema</h1> <h1 class="text-2xl sm:text-3xl font-bold text-gray-900">Logs del Sistema</h1>
<div class="flex items-center space-x-4"> <div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 sm:space-x-4">
<select v-model="logLevel" @change="loadLogs" class="input" style="width: auto;"> <select v-model="logLevel" @change="loadLogs" class="input text-sm sm:text-base" style="width: 100%; min-width: 160px;">
<option value="">Todos los niveles</option> <option value="">Todos los niveles</option>
<option value="INFO">INFO</option> <option value="INFO">INFO</option>
<option value="WARNING">WARNING</option> <option value="WARNING">WARNING</option>
<option value="ERROR">ERROR</option> <option value="ERROR">ERROR</option>
<option value="DEBUG">DEBUG</option> <option value="DEBUG">DEBUG</option>
</select> </select>
<button @click="loadLogs" class="btn btn-primary"> <button @click="loadLogs" class="btn btn-primary text-sm sm:text-base whitespace-nowrap">
Actualizar Actualizar
</button> </button>
<button @click="autoRefresh = !autoRefresh" class="btn btn-secondary"> <button @click="autoRefresh = !autoRefresh" class="btn btn-secondary text-sm sm:text-base whitespace-nowrap">
{{ autoRefresh ? '⏸ Pausar' : '▶ Auto-refresh' }} {{ autoRefresh ? '⏸ Pausar' : '▶ Auto-refresh' }}
</button> </button>
</div> </div>
</div> </div>
<div class="card"> <div class="card p-2 sm:p-6">
<div class="bg-gray-900 text-green-400 font-mono text-sm p-4 rounded-lg overflow-x-auto max-h-[600px] overflow-y-auto"> <div class="bg-gray-900 text-green-400 font-mono text-xs sm:text-sm p-3 sm:p-4 rounded-lg overflow-x-auto max-h-[400px] sm:max-h-[600px] overflow-y-auto">
<div v-if="loading" class="text-center py-8"> <div v-if="loading" class="text-center py-8">
<div class="inline-block animate-spin rounded-full h-6 w-6 border-b-2 border-green-400"></div> <div class="inline-block animate-spin rounded-full h-6 w-6 border-b-2 border-green-400"></div>
<p class="mt-2 text-gray-400">Cargando logs...</p> <p class="mt-2 text-gray-400">Cargando logs...</p>

View File

@@ -1,15 +1,15 @@
<template> <template>
<div> <div>
<div class="flex justify-between items-center mb-6"> <div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3 sm:gap-4 mb-4 sm:mb-6">
<h1 class="text-3xl font-bold text-gray-900">Gestión de Workers</h1> <h1 class="text-2xl sm:text-3xl font-bold text-gray-900">Gestión de Workers</h1>
<div class="flex space-x-2"> <div class="flex flex-wrap gap-2">
<button @click="showGeneralModal = true" class="btn btn-secondary"> <button @click="showGeneralModal = true" class="btn btn-secondary text-xs sm:text-sm">
Configuración General Configuración General
</button> </button>
<button @click="handleClearCache" class="btn btn-secondary"> <button @click="handleClearCache" class="btn btn-secondary text-xs sm:text-sm">
🗑 Limpiar Caché 🗑 Limpiar Caché
</button> </button>
<button @click="showAddModal = true" class="btn btn-primary"> <button @click="showAddModal = true" class="btn btn-primary text-xs sm:text-sm whitespace-nowrap">
+ Añadir Worker + Añadir Worker
</button> </button>
</div> </div>
@@ -42,7 +42,7 @@
</span> </span>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 text-sm mb-3"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4 text-xs sm:text-sm mb-3">
<div> <div>
<span class="text-gray-600 block mb-1">Búsqueda:</span> <span class="text-gray-600 block mb-1">Búsqueda:</span>
<p class="font-medium">{{ worker.search_query }}</p> <p class="font-medium">{{ worker.search_query }}</p>
@@ -102,22 +102,22 @@
</details> </details>
</div> </div>
</div> </div>
<div class="flex space-x-2 ml-4"> <div class="flex flex-wrap gap-2 sm:space-x-2 sm:ml-4 mt-3 sm:mt-0">
<button <button
@click="editWorker(worker, activeWorkersIndex(index))" @click="editWorker(worker, activeWorkersIndex(index))"
class="btn btn-secondary text-sm" class="btn btn-secondary text-xs sm:text-sm flex-1 sm:flex-none"
> >
Editar Editar
</button> </button>
<button <button
@click="deleteWorker(worker.name)" @click="deleteWorker(worker.name)"
class="btn btn-danger text-sm" class="btn btn-danger text-xs sm:text-sm flex-1 sm:flex-none"
> >
🗑 Eliminar 🗑 Eliminar
</button> </button>
<button <button
@click="disableWorker(worker.name)" @click="disableWorker(worker.name)"
class="btn btn-secondary text-sm" class="btn btn-secondary text-xs sm:text-sm flex-1 sm:flex-none"
> >
Desactivar Desactivar
</button> </button>
@@ -128,18 +128,18 @@
</div> </div>
<!-- Workers desactivados --> <!-- Workers desactivados -->
<div v-if="disabledWorkers.length > 0" class="mt-8"> <div v-if="disabledWorkers.length > 0" class="mt-6 sm:mt-8">
<h2 class="text-xl font-semibold text-gray-900 mb-4">Workers Desactivados ({{ disabledWorkers.length }})</h2> <h2 class="text-lg sm:text-xl font-semibold text-gray-900 mb-3 sm:mb-4">Workers Desactivados ({{ disabledWorkers.length }})</h2>
<div class="grid grid-cols-1 gap-4"> <div class="grid grid-cols-1 gap-4">
<div <div
v-for="(worker, index) in disabledWorkers" v-for="(worker, index) in disabledWorkers"
:key="index" :key="index"
class="card opacity-60 hover:opacity-80 transition-opacity" class="card opacity-60 hover:opacity-80 transition-opacity"
> >
<div class="flex items-start justify-between"> <div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3">
<div class="flex-1"> <div class="flex-1">
<div class="flex items-center space-x-2 mb-2"> <div class="flex flex-wrap items-center gap-2 mb-2">
<h3 class="text-lg font-semibold text-gray-900">{{ worker.name }}</h3> <h3 class="text-base sm:text-lg font-semibold text-gray-900">{{ worker.name }}</h3>
<span class="px-2 py-1 text-xs font-semibold rounded bg-red-100 text-red-800"> <span class="px-2 py-1 text-xs font-semibold rounded bg-red-100 text-red-800">
Desactivado Desactivado
</span> </span>
@@ -147,24 +147,24 @@
{{ (worker.platform || 'wallapop').toUpperCase() }} {{ (worker.platform || 'wallapop').toUpperCase() }}
</span> </span>
</div> </div>
<p class="text-sm text-gray-600">{{ worker.search_query }}</p> <p class="text-xs sm:text-sm text-gray-600">{{ worker.search_query }}</p>
</div> </div>
<div class="flex space-x-2 ml-4"> <div class="flex flex-wrap gap-2 sm:space-x-2 sm:ml-4">
<button <button
@click="editWorker(worker, disabledWorkersIndex(index))" @click="editWorker(worker, disabledWorkersIndex(index))"
class="btn btn-secondary text-sm" class="btn btn-secondary text-xs sm:text-sm flex-1 sm:flex-none"
> >
Editar Editar
</button> </button>
<button <button
@click="enableWorker(worker.name)" @click="enableWorker(worker.name)"
class="btn btn-primary text-sm" class="btn btn-primary text-xs sm:text-sm flex-1 sm:flex-none"
> >
Activar Activar
</button> </button>
<button <button
@click="deleteWorker(worker.name)" @click="deleteWorker(worker.name)"
class="btn btn-danger text-sm" class="btn btn-danger text-xs sm:text-sm flex-1 sm:flex-none"
> >
🗑 Eliminar 🗑 Eliminar
</button> </button>
@@ -185,18 +185,18 @@
<!-- Modal para añadir/editar worker --> <!-- Modal para añadir/editar worker -->
<div <div
v-if="showAddModal || editingWorker" v-if="showAddModal || editingWorker"
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-2 sm:p-4"
@click.self="closeModal" @click.self="closeModal"
> >
<div class="bg-white rounded-lg p-6 max-w-4xl w-full mx-4 max-h-[90vh] overflow-y-auto"> <div class="bg-white rounded-lg p-4 sm:p-6 max-w-4xl w-full max-h-[95vh] sm:max-h-[90vh] overflow-y-auto">
<h2 class="text-2xl font-bold text-gray-900 mb-4"> <h2 class="text-xl sm:text-2xl font-bold text-gray-900 mb-3 sm:mb-4">
{{ editingWorker ? 'Editar Worker' : 'Añadir Worker' }} {{ editingWorker ? 'Editar Worker' : 'Añadir Worker' }}
</h2> </h2>
<form @submit.prevent="saveWorker" class="space-y-6"> <form @submit.prevent="saveWorker" class="space-y-6">
<!-- Información básica --> <!-- Información básica -->
<div class="border-b border-gray-200 pb-4"> <div class="border-b border-gray-200 pb-3 sm:pb-4">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Información Básica</h3> <h3 class="text-base sm:text-lg font-semibold text-gray-900 mb-3 sm:mb-4">Información Básica</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-1">Nombre *</label> <label class="block text-sm font-medium text-gray-700 mb-1">Nombre *</label>
<input v-model="workerForm.name" type="text" class="input" required /> <input v-model="workerForm.name" type="text" class="input" required />
@@ -216,9 +216,9 @@
</div> </div>
<!-- Precios y Thread --> <!-- Precios y Thread -->
<div class="border-b border-gray-200 pb-4"> <div class="border-b border-gray-200 pb-3 sm:pb-4">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Precios y Notificaciones</h3> <h3 class="text-base sm:text-lg font-semibold text-gray-900 mb-3 sm:mb-4">Precios y Notificaciones</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4">
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-1">Precio Mínimo ()</label> <label class="block text-sm font-medium text-gray-700 mb-1">Precio Mínimo ()</label>
<input v-model.number="workerForm.min_price" type="number" class="input" min="0" step="0.01" /> <input v-model.number="workerForm.min_price" type="number" class="input" min="0" step="0.01" />
@@ -365,11 +365,11 @@
</div> </div>
</div> </div>
<div class="flex justify-end space-x-2 pt-4"> <div class="flex flex-col-reverse sm:flex-row justify-end gap-2 sm:space-x-2 pt-4">
<button type="button" @click="closeModal" class="btn btn-secondary"> <button type="button" @click="closeModal" class="btn btn-secondary text-sm sm:text-base">
Cancelar Cancelar
</button> </button>
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary text-sm sm:text-base">
Guardar Guardar
</button> </button>
</div> </div>
@@ -380,11 +380,11 @@
<!-- Modal para configuración general --> <!-- Modal para configuración general -->
<div <div
v-if="showGeneralModal" v-if="showGeneralModal"
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-2 sm:p-4"
@click.self="closeGeneralModal" @click.self="closeGeneralModal"
> >
<div class="bg-white rounded-lg p-6 max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto"> <div class="bg-white rounded-lg p-4 sm:p-6 max-w-2xl w-full max-h-[95vh] sm:max-h-[90vh] overflow-y-auto">
<h2 class="text-2xl font-bold text-gray-900 mb-4">Configuración General</h2> <h2 class="text-xl sm:text-2xl font-bold text-gray-900 mb-3 sm:mb-4">Configuración General</h2>
<p class="text-sm text-gray-600 mb-4">Estas configuraciones se aplican a todos los workers</p> <p class="text-sm text-gray-600 mb-4">Estas configuraciones se aplican a todos los workers</p>
<form @submit.prevent="saveGeneralConfig" class="space-y-4"> <form @submit.prevent="saveGeneralConfig" class="space-y-4">
<div> <div>
@@ -405,11 +405,11 @@
placeholder="Una palabra por línea o separadas por comas" placeholder="Una palabra por línea o separadas por comas"
></textarea> ></textarea>
</div> </div>
<div class="flex justify-end space-x-2 pt-4 border-t border-gray-200"> <div class="flex flex-col-reverse sm:flex-row justify-end gap-2 sm:space-x-2 pt-4 border-t border-gray-200">
<button type="button" @click="closeGeneralModal" class="btn btn-secondary"> <button type="button" @click="closeGeneralModal" class="btn btn-secondary text-sm sm:text-base">
Cancelar Cancelar
</button> </button>
<button type="submit" class="btn btn-primary"> <button type="submit" class="btn btn-primary text-sm sm:text-base">
Guardar Guardar
</button> </button>
</div> </div>

View File

@@ -1,8 +1,8 @@
#!/bin/bash #!/bin/bash
# Script para iniciar el servidor web de Wallamonitor # Script para iniciar el servidor web de Wallabicher
echo "🚀 Iniciando Wallamonitor Web Interface..." echo "🚀 Iniciando Wallabicher Web Interface..."
echo "" echo ""
# Verificar que Node.js esté instalado # Verificar que Node.js esté instalado