139 lines
7.3 KiB
Twig
139 lines
7.3 KiB
Twig
{###########################################################################
|
||
# Copyright (c) 2025, Антон Аксенов
|
||
# This file is part of iptv.axenov.dev web interface
|
||
# MIT License: https://git.axenov.dev/IPTV/web/src/branch/master/LICENSE
|
||
###########################################################################}
|
||
|
||
{% extends "template.twig" %}
|
||
|
||
{% block metadescription %}Самообновляемые бесплатные IPTV-плейлисты для домашнего просмотра по коротким ссылкам, списки каналов, проверка доступности{% endblock %}
|
||
|
||
{% block metakeywords %}самообновляемые,бесплатные,iptv-плейлисты,iptv,плейлисты{% endblock %}
|
||
|
||
{% block head %}
|
||
<style>
|
||
.card {transition: box-shadow .2s, transform .2s}
|
||
.card.hover-success:hover {transform: translateY(-7px); box-shadow: rgba(var(--bs-success-rgb), 1) 0 5px 20px -5px}
|
||
.card.hover-danger:hover {transform: translateY(-7px); box-shadow: rgba(var(--bs-danger-rgb), 1) 0 5px 20px -5px}
|
||
.card.hover-secondary:hover {transform: translateY(-7px); box-shadow: rgba(var(--bs-secondary-rgb), 1) 0 5px 20px -5px}
|
||
</style>
|
||
<script>
|
||
function setDefaultLogo(imgtag) {
|
||
imgtag.onerror = null
|
||
imgtag.src = '/no-tvg-logo.png'
|
||
}
|
||
</script>
|
||
{% endblock %}
|
||
|
||
{% block header %}
|
||
<div class="d-flex flex-wrap justify-content-between align-items-center mb-4">
|
||
<div class="mb-2">
|
||
<h2 class="mb-0">Список плейлистов ({{ count }})</h2>
|
||
<div class="text-muted small">Изменён {{ updatedAt }} МСК</div>
|
||
</div>
|
||
<div class="d-flex flex-wrap gap-2 mb-2">
|
||
<span class="badge bg-success">online: {{ onlineCount }}</span>
|
||
<span class="badge bg-danger">offline: {{ offlineCount }}</span>
|
||
<span class="badge bg-secondary">unknown: {{ uncheckedCount }}</span>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="row g-4">
|
||
{% for code, playlist in playlists %}
|
||
{% set statusClass = 'secondary' %}
|
||
{% if playlist.isOnline is same as(true) %}
|
||
{% set statusClass = 'success' %}
|
||
{% elseif playlist.isOnline is same as(false) %}
|
||
{% set statusClass = 'danger' %}
|
||
{% endif %}
|
||
|
||
<div class="col-md-6 col-lg-4">
|
||
<div class="card bg-dark text-light h-100 border border-{{ statusClass }} hover-{{ statusClass }} position-relative">
|
||
<a href="/{{ code }}/details" class="text-decoration-none">
|
||
<div class="card-header d-flex align-items-center gap-2">
|
||
<span class="font-monospace text-{{ statusClass }}">{{ code }}</span>
|
||
<span class="badge bg-{{ statusClass }} ms-auto">
|
||
{% if playlist.isOnline is same as(true) %}online
|
||
{% elseif playlist.isOnline is same as(false) %}offline
|
||
{% elseif playlist.isOnline is same as(null) %}unknown
|
||
{% endif %}
|
||
</span>
|
||
{% if "adult" in playlist.tags %}
|
||
<span class="badge bg-warning text-dark" title="Есть каналы для взрослых!">18+</span>
|
||
{% endif %}
|
||
</div>
|
||
</a>
|
||
|
||
<div class="card-body position-relative z-2">
|
||
<a href="/{{ code }}/details" class="text-decoration-none">
|
||
<h5 class="card-title text-light">{{ playlist.name }}</h5>
|
||
</a>
|
||
{% if playlist.description is not same as(null) %}
|
||
<p class="card-text small text-secondary d-none d-md-block">{{ playlist.description }}</p>
|
||
{% endif %}
|
||
<div class="d-flex flex-wrap gap-2 mb-1">
|
||
{% if playlist.isOnline is not same as(null) %}
|
||
<span class="badge border border-secondary">
|
||
<ion-icon name="videocam-outline" class="me-1"></ion-icon> {{ playlist.channels|length }}<span class="d-none d-xl-inline-block"> каналов</span>
|
||
</span>
|
||
{% endif %}
|
||
{% if playlist.groups|length > 0 %}
|
||
<span class="badge border border-secondary">
|
||
<ion-icon name="folder-open-outline" class="me-1"></ion-icon> {{ playlist.groups|length }}<span class="d-none d-xl-inline-block"> групп</span>
|
||
</span>
|
||
{% endif %}
|
||
{% if playlist.hasTvg %}
|
||
<span class="badge border border-secondary">
|
||
<ion-icon name="newspaper-outline" class="me-1"></ion-icon><span class="d-none d-xl-inline-block"> ТВ-программа</span>
|
||
</span>
|
||
{% endif %}
|
||
{% if playlist.hasCatchup %}
|
||
<span class="badge border border-secondary">
|
||
<ion-icon name="play-back-outline" class="me-1"></ion-icon><span class="d-none d-xl-inline-block"> Архив</span>
|
||
</span>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card-footer cursor-pointer"
|
||
onclick="prompt('Скопируй адрес плейлиста. Если не работает, добавь \'.m3u\' в конец.', '{{ mirror_url(playlist.code) }}')"
|
||
title="Нажми чтобы скопировать"
|
||
>
|
||
<div class="d-flex justify-content-between align-items-center small">
|
||
<span class="font-monospace text-truncate">
|
||
{{ mirror_url(playlist.code) }}
|
||
</span>
|
||
<ion-icon name="copy-outline"></ion-icon>
|
||
</div>
|
||
</div>
|
||
<a href="/{{ code }}/details" class="text-decoration-none">
|
||
</a>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
|
||
{% if pageCount > 1 %}
|
||
<nav class="mt-4">
|
||
<ul class="pagination justify-content-center">
|
||
{% for page in range(1, pageCount) %}
|
||
{% if page == pageCurrent %}
|
||
<li class="page-item active" aria-current="page">
|
||
<span class="page-link">{{ page }}</span>
|
||
</li>
|
||
{% else %}
|
||
<li class="page-item">
|
||
<a class="page-link bg-dark text-light" href="page/{{ page }}">{{ page }}</a>
|
||
</li>
|
||
{% endif %}
|
||
{% endfor %}
|
||
</ul>
|
||
</nav>
|
||
{% endif %}
|
||
{% endblock %}
|
||
|
||
{% block footer %}
|
||
{% endblock %}
|