добавил рекьирементс
Some checks failed
Deploy timelaps / deploy (push) Failing after 4s

This commit is contained in:
ack
2026-04-19 18:38:39 +03:00
parent c06a67328b
commit 895d9d3391
5 changed files with 93 additions and 14 deletions

View File

@@ -3,21 +3,38 @@
{% block content %} {% block content %}
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h2>Твои камеры</h2> <h2>Твои камеры</h2>
<span class="badge bg-success">NFS смонтирована</span> {% if storage_available %}
<span class="badge bg-success">Storage доступен</span>
{% else %}
<span class="badge bg-danger">Storage недоступен</span>
{% endif %}
</div> </div>
<div class="row row-cols-1 row-cols-md-3 g-4"> <div class="row row-cols-1 row-cols-md-3 g-4">
{% for camera in cameras %}
<div class="col"> <div class="col">
<div class="card h-100 shadow-sm"> <div class="card h-100 shadow-sm">
<img
src="{% url 'camlaps:camera_preview' camera.id %}"
class="card-img-top"
style="object-fit: cover; height: 220px;"
alt="{{ camera.name }}"
onerror="this.style.display='none'"
/>
<div class="card-body"> <div class="card-body">
<h5 class="card-title text-primary">Camera 1 (Вишня)</h5> <h5 class="card-title text-primary">{{ camera.name }}</h5>
<p class="card-text text-muted">Путь: /app/storage/Camera1</p> <p class="card-text text-muted">Путь: {{ camera.storage_path }}</p>
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<button class="btn btn-primary">Собрать таймлапс</button> <a class="btn btn-primary" href="{% url 'camlaps:job_create' camera.id %}">Собрать таймлапс</a>
<button class="btn btn-outline-secondary btn-sm">История видео</button> <a class="btn btn-outline-secondary btn-sm" href="{% url 'camlaps:job_list' %}?camera={{ camera.id }}">История</a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% empty %}
<div class="col">
<div class="alert alert-warning mb-0">Нет активных камер. Добавь их в админке.</div>
</div>
{% endfor %}
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -1,8 +1,13 @@
from django.urls import path from django.urls import path
from . import views from . import views
app_name = 'camlaps' app_name = 'camlaps'
urlpatterns = [ urlpatterns = [
path('', views.index, name='index'), path('', views.index, name='index'),
path('cameras/<int:camera_id>/preview/', views.camera_preview, name='camera_preview'),
path('cameras/<int:camera_id>/jobs/new/', views.job_create, name='job_create'),
path('jobs/', views.job_list, name='job_list'),
path('jobs/<int:job_id>/', views.job_detail, name='job_detail'),
] ]

View File

@@ -1,5 +1,62 @@
from django.shortcuts import render from django.http import FileResponse, Http404
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from .forms import TimelapseJobCreateForm
from .models import Camera, TimelapseJob
from .services.cameras import get_camera_lastsnap_path, is_storage_available, list_active_cameras
# Create your views here.
def index(request): def index(request):
return render(request, 'camlaps/index.html') cameras = list_active_cameras()
context = {
'cameras': cameras,
'storage_available': is_storage_available(),
}
return render(request, 'camlaps/index.html', context)
def camera_preview(request, camera_id: int):
camera = get_object_or_404(Camera, pk=camera_id, is_active=True)
path = get_camera_lastsnap_path(camera)
if not path:
raise Http404
return FileResponse(path.open('rb'), content_type='image/jpeg')
def job_list(request):
qs = TimelapseJob.objects.select_related('camera').all()
camera_id = request.GET.get('camera')
if camera_id:
qs = qs.filter(camera_id=camera_id)
jobs = qs.order_by('-created_at')[:200]
return render(request, 'camlaps/job_list.html', {'jobs': jobs})
def job_detail(request, job_id: int):
job = get_object_or_404(TimelapseJob.objects.select_related('camera'), pk=job_id)
video_url = None
if job.output_rel_path:
rel = job.output_rel_path.lstrip('/')
if rel.startswith('timelapses/'):
video_url = '/' + rel
else:
video_url = '/timelapses/' + rel.split('/')[-1]
return render(request, 'camlaps/job_detail.html', {'job': job, 'video_url': video_url})
def job_create(request, camera_id: int):
camera = get_object_or_404(Camera, pk=camera_id, is_active=True)
if request.method == 'POST':
form = TimelapseJobCreateForm(request.POST)
if form.is_valid():
job = form.save(commit=False)
job.camera = camera
job.save()
return redirect(reverse('camlaps:job_detail', kwargs={'job_id': job.id}))
else:
form = TimelapseJobCreateForm()
return render(request, 'camlaps/job_create.html', {'camera': camera, 'form': form})

BIN
requirements Normal file

Binary file not shown.

View File

@@ -10,7 +10,7 @@
<a class="nav-link active" href="/">Камеры</a> <a class="nav-link active" href="/">Камеры</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="#">Очередь задач</a> <a class="nav-link" href="{% url 'camlaps:job_list' %}">Очередь задач</a>
</li> </li>
<li class="nav-item ms-lg-3"> <li class="nav-item ms-lg-3">
<button class="btn btn-link nav-link shadow-none py-2 px-0 px-lg-2" id="theme-toggle" type="button"> <button class="btn btn-link nav-link shadow-none py-2 px-0 px-lg-2" id="theme-toggle" type="button">