Add monitoring PF service

This commit is contained in:
Grendgi
2026-06-04 14:55:41 +03:00
commit dd3edd7088
41 changed files with 3194 additions and 0 deletions

View File

@@ -0,0 +1,136 @@
{% extends "base.html" %}
{% block title %}{{ project.title }} — DLD Monitor{% endblock %}
{% block content %}
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start gap-3 mb-3">
<div>
<h3 class="mb-1">{{ project.title }}</h3>
<div class="text-muted">
{% if project.deal_type.value == 'sale' %}
<span class="badge badge-sale">Продажа</span>
{% else %}
<span class="badge badge-rent">Аренда</span>
{% endif %}
· Владелец: {{ project.owner.name }}
{% if project.last_checked_at %}
· Проверено: {{ project.last_checked_at | msk }} МСК
{% endif %}
</div>
<div class="small text-muted mt-1">
{% if project.building %}🏢 {{ project.building }}{% endif %}
{% if project.bedrooms is not none %} · 🛏️ {{ project.bedrooms }} BR{% endif %}
{% if project.size_sqft %} · 📐 {{ "{:,.0f}".format(project.size_sqft).replace(",", " ") }} sqft{% endif %}
{% if project.dld_permit %} · permit: <code>{{ project.dld_permit }}</code>{% endif %}
</div>
{% if project.our_price %}
<div class="mt-2">Наша цена: <b>{{ "{:,.0f}".format(project.our_price).replace(",", " ") }} AED</b></div>
{% endif %}
{% if project.our_url %}
<div class="small mt-1">Наше объявление: <a href="{{ project.our_url }}" target="_blank" rel="noopener">{{ project.our_url }}</a></div>
{% endif %}
{% if project.notes %}<div class="mt-2 text-muted"><i>{{ project.notes }}</i></div>{% endif %}
</div>
<div class="d-flex gap-2 flex-shrink-0">
<form action="{{ url_path('/projects/' ~ project.id ~ '/check') }}" method="post">
<button class="btn btn-primary text-nowrap">Проверить сейчас</button>
</form>
{% if request.state.is_admin %}
<form action="{{ url_path('/projects/' ~ project.id ~ '/delete') }}" method="post"
onsubmit="return confirm('Удалить проект и всю историю?');">
<button class="btn btn-outline-danger">Удалить</button>
</form>
{% endif %}
</div>
</div>
{% if error %}
<div class="alert alert-danger">{{ error }}</div>
{% endif %}
{% if message %}
<div class="alert alert-success">{{ message }}</div>
{% endif %}
<div class="bg-white rounded shadow-sm p-3 mb-4">
<form method="post" action="{{ url_path('/projects/' ~ project.id ~ '/listings') }}" class="row g-2">
<div class="col-md-9">
<input name="url" type="url" required class="form-control"
placeholder="Вставьте URL объявления конкурента с propertyfinder.ae или bayut.com">
</div>
<div class="col-md-3">
<button class="btn btn-primary w-100">+ Добавить конкурента</button>
</div>
</form>
{% if project.our_url %}
<div class="mt-2">
<a href="{{ url_path('/projects/' ~ project.id ~ '/suggest') }}" class="btn btn-sm btn-outline-secondary">
🔍 Подобрать похожие на PropertyFinder
</a>
<span class="text-muted small ms-2">
— по зданию из вашего объявления{% if project.bedrooms is not none %}, {{ project.bedrooms }} BR{% endif %};
совпадения по DLD permit — первыми. Займёт ~1520 сек.
</span>
</div>
{% else %}
<div class="mt-2 text-muted small">
🔍 «Подобрать похожие» появится, когда у проекта заполнены <b>наше объявление (URL)</b> и <b>спальни</b>.
</div>
{% endif %}
</div>
<h5 class="mb-3">Отслеживаемые конкуренты ({{ project.listings|length }})</h5>
{% if not project.listings %}
<div class="alert alert-light border">
Пока ничего не отслеживается. Добавьте URL объявления конкурента выше.
</div>
{% else %}
{% for l in project.listings %}
<div class="card mb-3 listing-card {{ 'removed' if l.status.value == 'removed' else 'active' }}">
<div class="card-body">
<div class="d-flex flex-column flex-sm-row justify-content-between gap-2">
<div>
<span class="src-{{ 'pf' if l.source.value == 'propertyfinder' else 'bayut' }}">
{{ 'PropertyFinder' if l.source.value == 'propertyfinder' else 'Bayut' }}
</span>
{% if l.status.value == 'removed' %}<span class="badge bg-danger ms-2">Удалено</span>{% endif %}
<h6 class="mt-1 mb-1">
<a href="{{ l.url }}" target="_blank" rel="noopener">{{ l.title or 'без названия' }}</a>
</h6>
<div class="text-muted small">
Брокер: {{ l.agent_name or '—' }} ({{ l.agency_name or '—' }})<br>
Добавлено: {{ l.first_seen_at | msk }} ·
Последний раз видели активным: {{ l.last_seen_at | msk }} (МСК)
</div>
</div>
<div class="text-sm-end flex-shrink-0">
<div class="fs-4 fw-bold">
{% if l.current_price %}
{{ "{:,.0f}".format(l.current_price).replace(",", " ") }} {{ l.currency or 'AED' }}
{% else %}—{% endif %}
</div>
{% if request.state.is_admin %}
<form method="post" action="{{ url_path('/listings/' ~ l.id ~ '/delete') }}"
onsubmit="return confirm('Перестать отслеживать?');" class="mt-2">
<button class="btn btn-sm btn-outline-danger">Удалить</button>
</form>
{% endif %}
</div>
</div>
{% if l.price_history|length > 1 %}
<details class="mt-2">
<summary class="text-muted small">История цены ({{ l.price_history|length }} записей)</summary>
<ul class="small mt-2">
{% for h in l.price_history %}
<li>{{ h.recorded_at | msk }} —
{% if h.price %}{{ "{:,.0f}".format(h.price).replace(",", " ") }} AED{% else %}—{% endif %}</li>
{% endfor %}
</ul>
</details>
{% endif %}
</div>
</div>
{% endfor %}
{% endif %}
{% endblock %}