From abd62d8f735104d6a7a7379c5d78053d82cbe3d0 Mon Sep 17 00:00:00 2001 From: Dokril Date: Sat, 6 Dec 2025 09:45:17 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3?= =?UTF-8?q?=D1=83=D1=80=D0=B0=D1=86=D0=B8=D1=8F=20Glance=20=D1=81=20=D0=B2?= =?UTF-8?q?=D0=B8=D0=B4=D0=B6=D0=B5=D1=82=D0=B0=D0=BC=D0=B8=20=D0=B8=20?= =?UTF-8?q?=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D0=B0=D0=BC=D0=B8=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B7=D0=B2=D0=B5=D1=80=D1=82=D1=8B=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Главная конфигурация home.yml и виджеты (countdown, qbittorrent) - PowerShell и Bash скрипты для автоматической загрузки на сервер - Обновлен README с документацией --- README.md | 161 +++++++++++++++- home.yml | 60 ++++++ upload-glance-config.ps1 | 71 +++++++ upload-glance-config.sh | 72 +++++++ widgets/bookmarks-general.yml | 45 +++++ widgets/bookmarks-homelab.yml | 44 +++++ widgets/countdown.yml | 348 ++++++++++++++++++++++++++++++++++ widgets/qbittorrent.yml | 187 ++++++++++++++++++ 8 files changed, 987 insertions(+), 1 deletion(-) create mode 100644 home.yml create mode 100644 upload-glance-config.ps1 create mode 100644 upload-glance-config.sh create mode 100644 widgets/bookmarks-general.yml create mode 100644 widgets/bookmarks-homelab.yml create mode 100644 widgets/countdown.yml create mode 100644 widgets/qbittorrent.yml diff --git a/README.md b/README.md index 1c8c77b..5ba9692 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,161 @@ -# glance +# Glance Dashboard Configuration + +Конфигурация для [Glance](https://github.com/glanceapp/glance) - минималистичного дашборда для самостоятельного хостинга. + +## 📁 Структура проекта + +``` +glance/ +├── home.yml # Главный файл конфигурации +├── widgets/ # Отдельные виджеты (модульная структура) +│ ├── bookmarks-homelab.yml # Закладки Homelab +│ ├── qbittorrent.yml # Виджет qBittorrent +│ └── ... # Другие виджеты +├── upload-glance-config.sh # Shell скрипт для загрузки конфигурации +├── upload-glance-config.ps1 # PowerShell обертка (использует WSL) +└── README.md # Этот файл +``` + +## 🚀 Установка и настройка + +### Требования + +**Для Linux/macOS:** +- `bash` +- `ssh` и `scp` (обычно предустановлены) +- SSH доступ к серверу с Glance + +**Для Windows:** +- [WSL (Windows Subsystem for Linux)](https://docs.microsoft.com/ru-ru/windows/wsl/install) +- PowerShell 5.1+ (предустановлен в Windows 10/11) +- SSH доступ к серверу с Glance + +### Установка WSL (только для Windows) + +```powershell +# Запустите PowerShell от имени администратора +wsl --install +``` + +После установки перезагрузите компьютер. + +## 📤 Загрузка конфигурации на сервер + +### Linux/macOS + +```bash +# С параметрами по умолчанию (./glance, root@192.168.50.114) +./upload-glance-config.sh + +# С произвольными параметрами +./upload-glance-config.sh /path/to/glance username 192.168.50.100 +``` + +### Windows + +```powershell +# С параметрами по умолчанию +.\upload-glance-config.ps1 + +# С произвольными параметрами +.\upload-glance-config.ps1 -LocalDir "e:\repos\glance" -User "root" -Host "192.168.50.114" + +# Или в кратком виде +.\upload-glance-config.ps1 "e:\repos\glance" "root" "192.168.50.114" +``` + +> **Примечание:** PowerShell скрипт является оберткой, которая вызывает shell скрипт через WSL, избегая дублирования кода. + +## ⚙️ Параметры скрипта + +| Параметр | Описание | Значение по умолчанию | +|----------|----------|----------------------| +| `LOCAL_DIR` / `-LocalDir` | Локальная директория с конфигурацией | `./glance` | +| `USER` / `-User` | Пользователь SSH | `root` | +| `HOST` / `-Host` | IP адрес или hostname сервера | `192.168.50.114` | + +## 📋 Что делает скрипт + +1. **Загружает `home.yml`** → `/opt/glance/config/glance.yml` на сервере +2. **Синхронизирует директорию `widgets/`** → `/opt/glance/config/widgets/` на сервере +3. **Автоматически создает** необходимые директории на сервере +4. **Проверяет наличие** файлов и директорий перед загрузкой + +> Glance автоматически перезагружает конфигурацию при обнаружении изменений. + +## 🎨 Модульная структура виджетов + +Конфигурация использует функцию `include` для организации виджетов: + +```yaml +# В home.yml +pages: + - name: Главная + columns: + - size: full + widgets: + - type: include + path: widgets/bookmarks-homelab.yml +``` + +### Преимущества модульной структуры + +- ✅ Легче управлять и редактировать отдельные виджеты +- ✅ Переиспользование виджетов в разных страницах +- ✅ Простота версионирования и отладки +- ✅ Чище git история изменений + +## 🔧 Настройка SSH ключей (рекомендуется) + +Для автоматической загрузки без ввода пароля: + +```bash +# Сгенерируйте SSH ключ (если еще не создан) +ssh-keygen -t ed25519 -C "glance-config" + +# Скопируйте ключ на сервер +ssh-copy-id root@192.168.50.114 +``` + +## 🐛 Устранение неполадок + +### Windows: "wsl: команда не найдена" + +Убедитесь, что WSL установлен: +```powershell +wsl --version +``` + +Если WSL не установлен, выполните: +```powershell +wsl --install +``` + +### Ошибка SSH подключения + +Проверьте подключение вручную: +```bash +ssh root@192.168.50.114 +``` + +### Директория не найдена + +Убедитесь, что вы запускаете скрипт из правильной директории: +```bash +# Linux/macOS +ls -la glance/ + +# PowerShell +Get-ChildItem glance/ +``` + +## 📚 Полезные ссылки + +- [Glance Documentation](https://github.com/glanceapp/glance) +- [Glance Configuration Examples](https://github.com/glanceapp/glance/blob/main/docs/configuration.md) +- [WSL Documentation (RU)](https://docs.microsoft.com/ru-ru/windows/wsl/) + +## 📝 Лицензия + +Этот проект следует лицензии основного проекта [Glance](https://github.com/glanceapp/glance). diff --git a/home.yml b/home.yml new file mode 100644 index 0000000..09841e7 --- /dev/null +++ b/home.yml @@ -0,0 +1,60 @@ +theme: + name: nord + +pages: + - name: Home + columns: + - size: small + widgets: + - type: calendar + first-day-of-week: monday + - type: repository + repository: awesome-selfhosted/awesome-selfhosted + - type: rss + title: News + limit: 5 + feeds: + - url: https://habr.com/ru/rss/all/ + title: Habr + + - size: full + widgets: + - type: search + search-engine: google + - $include: widgets/countdown.yml + - $include: widgets/bookmarks-general.yml + - $include: widgets/bookmarks-homelab.yml + + - size: small + widgets: + - type: clock + title: Time + - type: to-do + - type: custom-api + title: Immich stats + cache: 1d + url: http://192.168.50.101:2283/api/server/statistics + headers: + x-api-key: izNdobgXRRg7agxZzEQvNEKpsnwPT8x0cs6Vi7E + Accept: application/json + template: | +
+
+
{{ .JSON.Int "photos" | formatNumber }}
+
PHOTOS
+
+
+
{{ .JSON.Int "videos" | formatNumber }}
+
VIDEOS
+
+
+
{{ div (.JSON.Int "usage" | toFloat) 1073741824 | toInt | formatNumber }}GB
+
USAGE
+
+
+ - type: dns-stats + service: adguard + url: http://192.168.50.2/ + username: Dokril + password: baHYc2VgeRJfdZ + - $include: widgets/qbittorrent.yml diff --git a/upload-glance-config.ps1 b/upload-glance-config.ps1 new file mode 100644 index 0000000..5524dd2 --- /dev/null +++ b/upload-glance-config.ps1 @@ -0,0 +1,71 @@ +# PowerShell скрипт для обновления конфигурации Glance +# Использование: .\upload-glance-config.ps1 [LOCAL_DIR] [USER] [HOST] + +param( + [string]$LocalDir = ".", + [string]$User = "root", + [string]$RemoteHost = "192.168.50.114" +) + +$RemoteGlanceConfig = "/opt/glance/config" +$SshOpts = @("-q", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null") +$ScpOpts = @("-q", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null") + +# Функция для выхода с ошибкой +function Exit-OnError { + param([string]$Message) + Write-Host "Ошибка: $Message" -ForegroundColor Red + exit 1 +} + +# Проверяем наличие локальной директории glance +if (-not (Test-Path $LocalDir -PathType Container)) { + Exit-OnError "Директория $LocalDir не найдена!" +} + +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "Обновление конфигурации Glance" -ForegroundColor Cyan +Write-Host "========================================" -ForegroundColor Cyan + +# 1. Загружаем home.yml на сервер +$LocalHomeYml = Join-Path $LocalDir "home.yml" +if (Test-Path $LocalHomeYml -PathType Leaf) { + Write-Host "`n📄 Загружаем главный файл конфигурации..." -ForegroundColor Yellow + + & scp @ScpOpts $LocalHomeYml "${User}@${RemoteHost}:${RemoteGlanceConfig}/glance.yml" + + if ($LASTEXITCODE -ne 0) { + Exit-OnError "Ошибка при загрузке home.yml" + } + Write-Host "✅ home.yml успешно загружен" -ForegroundColor Green +} else { + Write-Host "⚠️ Файл home.yml не найден в $LocalDir" -ForegroundColor Yellow +} + +# 2. Загружаем директорию widgets рекурсивно +$LocalWidgetsDir = Join-Path $LocalDir "widgets" +if (Test-Path $LocalWidgetsDir -PathType Container) { + Write-Host "`n📦 Загружаем директорию widgets..." -ForegroundColor Yellow + + # Создаем директорию widgets на сервере, если не существует + & ssh @SshOpts "${User}@${RemoteHost}" "mkdir -p ${RemoteGlanceConfig}/widgets" + + if ($LASTEXITCODE -ne 0) { + Exit-OnError "Не удалось создать директорию widgets на сервере" + } + + # Копируем все файлы из widgets рекурсивно + & scp @ScpOpts -r "$LocalWidgetsDir/*" "${User}@${RemoteHost}:${RemoteGlanceConfig}/widgets/" + + if ($LASTEXITCODE -ne 0) { + Exit-OnError "Ошибка при загрузке директории widgets" + } + Write-Host "✅ Директория widgets успешно загружена" -ForegroundColor Green +} else { + Write-Host "⚠️ Директория widgets не найдена в $LocalDir" -ForegroundColor Yellow +} + +Write-Host "`n========================================" -ForegroundColor Cyan +Write-Host "Конфигурация успешно обновлена! 🎉" -ForegroundColor Green +Write-Host "========================================" -ForegroundColor Cyan +Write-Host "Примечание: Glance автоматически перезагрузит конфигурацию" -ForegroundColor Yellow diff --git a/upload-glance-config.sh b/upload-glance-config.sh new file mode 100644 index 0000000..377eef5 --- /dev/null +++ b/upload-glance-config.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# Скрипт для обновления только конфигурации Glance (без бэкенда) +# Использование: ./upload-glance-config.sh [LOCAL_DIR] [USER] [HOST] + +# Параметры по умолчанию +LOCAL_GLANCE_DIR="${1:-./glance}" +REMOTE_USER="${2:-root}" +REMOTE_HOST="${3:-192.168.50.114}" +REMOTE_GLANCE_CONFIG="/opt/glance/config" +SSH_OPTS="-q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" +SCP_OPTS="-q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +# Функция для выхода с ошибкой +exit_on_error() { + echo -e "${RED}Ошибка: $1${NC}" >&2 + exit 1 +} + +# Проверяем наличие локальной директории glance +if [ ! -d "$LOCAL_GLANCE_DIR" ]; then + exit_on_error "Директория $LOCAL_GLANCE_DIR не найдена!" +fi + +echo -e "${CYAN}========================================${NC}" +echo -e "${CYAN}Обновление конфигурации Glance${NC}" +echo -e "${CYAN}========================================${NC}" + +# 1. Загружаем home.yml на сервер +LOCAL_HOME_YML="$LOCAL_GLANCE_DIR/home.yml" +if [ -f "$LOCAL_HOME_YML" ]; then + echo -e "\n${YELLOW}📄 Загружаем главный файл конфигурации...${NC}" + scp $SCP_OPTS "$LOCAL_HOME_YML" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_GLANCE_CONFIG}/glance.yml" + if [ $? -ne 0 ]; then + exit_on_error "Ошибка при загрузке home.yml" + fi + echo -e "${GREEN}✅ home.yml успешно загружен${NC}" +else + echo -e "${YELLOW}⚠️ Файл home.yml не найден в $LOCAL_GLANCE_DIR${NC}" +fi + +# 2. Загружаем директорию widgets рекурсивно +LOCAL_WIDGETS_DIR="$LOCAL_GLANCE_DIR/widgets" +if [ -d "$LOCAL_WIDGETS_DIR" ]; then + echo -e "\n${YELLOW}📦 Загружаем директорию widgets...${NC}" + # Создаем директорию widgets на сервере, если не существует + ssh $SSH_OPTS "${REMOTE_USER}@${REMOTE_HOST}" "mkdir -p ${REMOTE_GLANCE_CONFIG}/widgets" + if [ $? -ne 0 ]; then + exit_on_error "Не удалось создать директорию widgets на сервере" + fi + + # Копируем все файлы из widgets рекурсивно + scp $SCP_OPTS -r "${LOCAL_WIDGETS_DIR}"/* "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_GLANCE_CONFIG}/widgets/" + if [ $? -ne 0 ]; then + exit_on_error "Ошибка при загрузке директории widgets" + fi + echo -e "${GREEN}✅ Директория widgets успешно загружена${NC}" +else + echo -e "${YELLOW}⚠️ Директория widgets не найдена в $LOCAL_GLANCE_DIR${NC}" +fi + +echo -e "\n${CYAN}========================================${NC}" +echo -e "${GREEN}Конфигурация успешно обновлена! 🎉${NC}" +echo -e "${CYAN}========================================${NC}" +echo -e "${YELLOW}Примечание: Glance автоматически перезагрузит конфигурацию${NC}" diff --git a/widgets/bookmarks-general.yml b/widgets/bookmarks-general.yml new file mode 100644 index 0000000..27d25cf --- /dev/null +++ b/widgets/bookmarks-general.yml @@ -0,0 +1,45 @@ +- type: bookmarks + groups: + - title: Email + type: main + links: + - title: Gmail + url: https://mail.google.com/mail/u/0/ + - title: Games + type: main + links: + - title: itch.io Idle + url: https://itch.io/games/new-and-popular/tag-idle + - title: Entertainment + color: 10 70 50 + type: public + links: + - title: YouTube + url: https://www.youtube.com/ + - title: Social + type: main + links: + - title: Twitter(X) + url: https://x.com/home + - title: Shopping + color: 10 70 50 + type: public + links: + - title: Yandex + url: https://market.yandex.ru/ + - title: Ozon + url: https://www.ozon.ru/ + - title: AI + type: main + links: + - title: ChatGPT + url: https://chatgpt.com/ + - title: Gemini + url: https://gemini.google.com/ + - title: Trackers + type: main + links: + - title: Rutracker + url: https://rutracker.net/ + - title: RuTor + url: https://pornolab.net/ diff --git a/widgets/bookmarks-homelab.yml b/widgets/bookmarks-homelab.yml new file mode 100644 index 0000000..7bf16eb --- /dev/null +++ b/widgets/bookmarks-homelab.yml @@ -0,0 +1,44 @@ +- type: bookmarks + groups: + - title: Homelab - Services + color: 200 50 50 + type: homelab + links: + - title: Router + url: https://192.168.50.1:8443 + - title: Proxmox + url: http://192.168.50.113:8006 + - title: NPM + url: http://192.168.50.38:81/nginx/proxy + - title: Gitea + url: http://192.168.50.109:3000/ + - title: Immich + url: http://192.168.50.101:2283/ + - title: Media + url: http://192.168.50.108:9999 + - title: qBittorrent + url: http://192.168.50.108:8080 + - title: Homelab - Useful Links + color: 200 50 50 + type: homelab + links: + - title: Proxmox Community Scripts + url: https://community-scripts.github.io/ProxmoxVE/scripts + - title: Homelab - Monitoring + color: 200 50 50 + type: homelab + links: + - title: Grafana + url: http://192.168.50.106:3000/d/af346btjrod8gd/main?from=now-6h&to=now&timezone=browser&refresh=5s + - title: Prometheus + url: http://192.168.50.105:9090/targets + - title: Homelab - Cloud + color: 200 50 50 + type: homelab + links: + - title: Timeweb Cloud + url: https://timeweb.cloud/my/projects/1882441 + - title: Senko VM + url: https://vm.senko.digital/vm/manager/host/list + - title: Aeza VM + url: https://my.aeza.net/services diff --git a/widgets/countdown.yml b/widgets/countdown.yml new file mode 100644 index 0000000..b038d3d --- /dev/null +++ b/widgets/countdown.yml @@ -0,0 +1,348 @@ +- type: custom-api + title: "Важные даты" + url: http://192.168.50.114:5555/countdown + cache: 5s + template: | + {{/* ===== НАСТРОЙКИ ВРЕМЕННОЙ ШКАЛЫ ===== */}} + {{/* Переменная шкала: 2 недели (14 дней) + 40 недель = ~1 год */}} + {{/* Первая зона: 2 недели = 14 делений (по дням) = 25% шкалы */}} + {{/* Вторая зона: 40 недель = 40 делений (по неделям) = 75% шкалы */}} + {{ $daysInFirstZone := 14 }} + {{ $weeksInSecondZone := 40 }} + {{ $firstZonePercent := 25.0 }} + {{ $secondZonePercent := 75.0 }} + +
+ +
+ + {{/* ===== ОТРИСОВКА ЛИНИИ ВРЕМЕНИ ===== */}} + {{/* Зона 1 (0-25%): 14 делений по дням - с мягким красным градиентом */}} + {{/* Зона 2 (25-100%): 40 делений по неделям */}} +
+
+
+
+ + {{/* ===== МАРКЕР "СЕГОДНЯ" ===== */}} + {{/* Огонёк показывает текущий момент */}} +
+ 🔥 +
+ + {{/* ===== ОТОБРАЖЕНИЕ СОБЫТИЙ ===== */}} + {{/* Позиции рассчитываются с учетом двух зон шкалы */}} + {{ $daysZ1 := toFloat $daysInFirstZone }} + {{ $weeksZ2 := toFloat $weeksInSecondZone }} + {{ $z1Pct := $firstZonePercent }} + {{ $z2Pct := $secondZonePercent }} + {{ $maxDays := add $daysZ1 (mul $weeksZ2 7.0) }} + + {{ range .JSON.Array "events" }} + {{/* Получаем количество дней до события из API /countdown */}} + {{ $days := .Int "days" }} + {{ $daysFloat := toFloat $days }} + + {{/* ФОРМУЛА: Рассчитываем позицию в зависимости от зоны */}} + {{ $pos := 0.0 }} + + {{/* Показываем только события в пределах диапазона (14 дней + 40 недель = 294 дня) */}} + {{ if lt $daysFloat $maxDays }} + {{ if lt $daysFloat $daysZ1 }} + {{/* Зона 1: 0-14 дней -> 0-25% шкалы */}} + {{ $pos = mul (div $daysFloat $daysZ1) $z1Pct }} + {{ else }} + {{/* Зона 2: 14+ дней -> 25-100% шкалы */}} + {{ $daysAfterZ1 := sub $daysFloat $daysZ1 }} + {{ $weeksInZ2 := div $daysAfterZ1 7.0 }} + {{ $posInZ2 := mul (div $weeksInZ2 $weeksZ2) $z2Pct }} + {{ $pos = add $z1Pct $posInZ2 }} + {{ end }} + + {{/* ===== КОНТЕЙНЕР СОБЫТИЯ ===== */}} + {{/* Абсолютное позиционирование: left = рассчитанный процент */}} + {{/* transform: translate(-50%, -50%) центрирует emoji точно на линии */}} +
+ + {{/* ===== EMOJI СОБЫТИЯ ===== */}} + {{/* При наведении увеличивается и раздвигает соседей */}} +
{{ .String "emoji" }}
+ + {{/* ===== РАСШИРЕННЫЙ TOOLTIP ===== */}} + {{/* Большой информативный tooltip с детальной информацией */}} +
+ + {{/* Emoji и название */}} +
+
{{ .String "emoji" }}
+
{{ .String "name" }}
+
+ + {{/* Точная дата события */}} +
+
📅 Дата:
+
{{ .String "date" }}
+
+ + {{/* Информация о годовщине (если это anniversary) */}} + {{ if eq (.String "type") "anniversary" }} +
+
🎉 Годовщина
+ {{ $years := .Int "years_passed" }} + {{ $lastDigit := mod $years 10 }} + {{ $lastTwoDigits := mod $years 100 }} +
{{ $years }} {{ if and (eq $lastDigit 1) (ne $lastTwoDigits 11) }}год{{ else if and (or (eq $lastDigit 2) (eq $lastDigit 3) (eq $lastDigit 4)) (not (or (eq $lastTwoDigits 12) (eq $lastTwoDigits 13) (eq $lastTwoDigits 14))) }}года{{ else }}лет{{ end }}
+
+ {{ end }} + + {{/* Детальный countdown */}} +
+
⏰ Осталось
+ {{ $days := .Int "days" }} + {{ if ge $days 30 }} + {{/* Если >= 30 дней: показываем месяцы и дни */}} + {{ $months := div $days 30 }} + {{ $remainingDays := mod $days 30 }} +
+
+
{{ $months }}
+
{{ if eq $months 1 }}месяц{{ else if or (eq $months 2) (eq $months 3) (eq $months 4) }}месяца{{ else }}месяцев{{ end }}
+
+
+
{{ $remainingDays }}
+
{{ if eq $remainingDays 1 }}день{{ else if or (eq $remainingDays 2) (eq $remainingDays 3) (eq $remainingDays 4) }}дня{{ else }}дней{{ end }}
+
+
+ {{ else }} + {{/* Если < 30 дней: показываем дни и часы */}} +
+
+
{{ $days }}
+
{{ if eq $days 1 }}день{{ else if or (eq $days 2) (eq $days 3) (eq $days 4) }}дня{{ else }}дней{{ end }}
+
+
+
{{ .Int "hours" }}
+
{{ if eq (.Int "hours") 1 }}час{{ else if or (eq (.Int "hours") 2) (eq (.Int "hours") 3) (eq (.Int "hours") 4) }}часа{{ else }}часов{{ end }}
+
+
+ {{ end }} +
+ + {{/* Треугольная стрелка вниз */}} +
+
+
+ {{ end }} + {{ end }} + + {{/* ===== ИНТЕРАКТИВНАЯ СЕТКА ДЛЯ НАВЕДЕНИЯ ===== */}} + {{/* Невидимые зоны, которые показывают дату при наведении */}} + {{ range .JSON.Array "timeline_grid" }} + {{ $gridDays := .Int "days" }} + {{ $gridDaysFloat := toFloat $gridDays }} + + {{/* Рассчитываем позицию точки на шкале */}} + {{ $gridPos := 0.0 }} + {{ $gridWidth := 0.0 }} + + {{ if lt $gridDaysFloat $maxDays }} + {{ if lt $gridDaysFloat $daysZ1 }} + {{/* Зона 1: деления по дням */}} + {{ $gridPos = mul (div $gridDaysFloat $daysZ1) $z1Pct }} + {{ $gridWidth = div $z1Pct $daysZ1 }} + {{ else }} + {{/* Зона 2: деления по неделям */}} + {{ $daysAfterZ1 := sub $gridDaysFloat $daysZ1 }} + {{ $weeksInZ2 := div $daysAfterZ1 7.0 }} + {{ $posInZ2 := mul (div $weeksInZ2 $weeksZ2) $z2Pct }} + {{ $gridPos = add $z1Pct $posInZ2 }} + {{ $gridWidth = div $z2Pct $weeksZ2 }} + {{ end }} + + {{/* Интерактивная зона */}} +
+ + {{/* Tooltip с датой */}} +
+ + {{/* День недели */}} +
+ {{ .String "weekday" }} +
+ + {{/* Дата */}} +
+ 📅 {{ .String "date" }} +
+ + {{/* Треугольная стрелка */}} +
+
+
+ {{ end }} + {{ end }} + + {{/* ===== МЕТКИ МЕСЯЦЕВ ПОД ШКАЛОЙ ===== */}} + {{/* Используем данные о месяцах из API для элегантного отображения */}} + {{ range .JSON.Array "months" }} + {{ $monthDays := .Int "days" }} + {{ $monthDaysFloat := toFloat $monthDays }} + + {{/* Показываем только метки в пределах видимого диапазона */}} + {{ if lt $monthDaysFloat $maxDays }} + {{/* Рассчитываем позицию метки */}} + {{ $monthPos := 0.0 }} + {{if lt $monthDaysFloat $daysZ1 }} + {{ $monthPos = mul (div $monthDaysFloat $daysZ1) $z1Pct }} + {{ else }} + {{ $daysAfter := sub $monthDaysFloat $daysZ1 }} + {{ $weeksIn := div $daysAfter 7.0 }} + {{ $posIn := mul (div $weeksIn $weeksZ2) $z2Pct }} + {{ $monthPos = add $z1Pct $posIn }} + {{ end }} + + {{/* Отображаем метку месяца */}} +
+ {{ .String "name" }} +
+ {{ end }} + {{ end }} + +
+
+ + + + +
+ ⚙️ Управление +
+ + diff --git a/widgets/qbittorrent.yml b/widgets/qbittorrent.yml new file mode 100644 index 0000000..0b42d7e --- /dev/null +++ b/widgets/qbittorrent.yml @@ -0,0 +1,187 @@ +- type: custom-api + title: qBittorrent + cache: 10s + options: + view: "basic" # "basic" or "detailed" + mode: "default" # "default" or "upload" + subrequests: + transfer: + url: http://192.168.50.108:8080/api/v2/transfer/info + seeding: + url: http://192.168.50.108:8080/api/v2/torrents/info + parameters: + filter: seeding + leeching: + url: http://192.168.50.108:8080/api/v2/torrents/info + parameters: + filter: downloading + template: | + {{ $transfer := .Subrequest "transfer" }} + {{ $seeding := .Subrequest "seeding" }} + {{ $leeching := .Subrequest "leeching" }} + + {{ if and (eq $transfer.Response.StatusCode 200) (eq $seeding.Response.StatusCode 200) (eq $leeching.Response.StatusCode 200) }} + + {{ $isDetailed := eq (.Options.StringOr "view" "detailed") "detailed" }} + {{ $mode := .Options.StringOr "mode" "default" }} + + {{ if $isDetailed }} + +
+
+
+ {{ $dlSpeed := $transfer.JSON.Float "dl_info_speed" }} + {{ if eq $mode "upload" }} +
{{ printf "%.1f MB/s" (div $dlSpeed 1000000.0) }}
+ {{ else }} + {{ if lt $dlSpeed 1048576.0 }} +
{{ printf "%.0f KiB/s" (div $dlSpeed 1024.0) }}
+ {{ else }} +
{{ printf "%.1f MiB/s" (div $dlSpeed 1048576.0) }}
+ {{ end }} + {{ end }} +
DOWNLOADING
+
+ + {{ if eq $mode "upload" }} +
+ {{ $ulSpeed := $transfer.JSON.Float "up_info_speed" }} +
{{ printf "%.1f MB/s" (div $ulSpeed 1000000.0) }}
+
UPLOADING
+
+ {{ end }} + +
+
{{ len ($seeding.JSON.Array "") }}
+
SEEDING
+
+ + {{ if eq $mode "default" }} +
+
{{ len ($leeching.JSON.Array "") }}
+
LEECHING
+
+ {{ end }} +
+ + + {{ $downloadingTorrents := $leeching.JSON.Array "" }} + {{ if gt (len $downloadingTorrents) 0 }} +
+
    + {{ range $t := $downloadingTorrents }} + {{ $state := $t.String "state" }} + {{ $icon := "?" }} + {{ if ge ($t.Int "completed") ($t.Int "size") }}{{ $icon = "✔" }} + {{ else if eq $state "downloading" "forcedDL" }}{{ $icon = "↓" }} + {{ else if eq $state "pausedDL" "stoppedDL" "pausedUP" "stalledDL" "stalledUP" "queuedDL" "queuedUP" }}{{ $icon = "❚❚" }} + {{ else if eq $state "error" "missingFiles" }}{{ $icon = "!" }} + {{ else if eq $state "checkingDL" "checkingUP" "allocating" }}{{ $icon = "…" }} + {{ else if eq $state "checkingResumeData" }}{{ $icon = "⟳" }} + {{ end }} + +
  • +
    {{ $icon }}
    +
    +
    {{ $t.String "name" }}
    +
    +
    +
    +
    +
    + {{ $dlSpeed := $t.Float "dlspeed" }} +
    + {{ if eq $mode "upload" }} + {{ if lt $dlSpeed 1000.0 }}--{{ else }}{{ printf "%.1f MB/s" (div $dlSpeed 1000000.0) }}{{ end }} + {{ else }} + {{ if lt $dlSpeed 1024.0 }}--{{ else if lt $dlSpeed 1048576.0 }}{{ printf "%.0f KiB/s" (div $dlSpeed 1024.0) }}{{ else }}{{ printf "%.1f MiB/s" (div $dlSpeed 1048576.0) }}{{ end }} + {{ end }} +
    + {{ $eta := $t.Int "eta" }} +
    + {{ if eq $eta 8640000 }}∞ + {{ else if gt $eta 3600 }}{{ printf "%dh %dm" (div $eta 3600) (mod (div $eta 60) 60) }} + {{ else if gt $eta 0 }}{{ printf "%dm" (div $eta 60) }} + {{ else }}--{{ end }} +
    +
    +
  • + {{ end }} +
+
+ {{ end }} + + + {{ if eq $mode "upload" }} + {{ $seedingTorrents := $seeding.JSON.Array "" }} + {{ if gt (len $seedingTorrents) 0 }} +
+
    + {{ range $t := $seedingTorrents }} + {{ $state := $t.String "state" }} + {{ $icon := "↑" }} + {{ if eq $state "pausedUP" "stoppedUP" "stalledUP" "queuedUP" }}{{ $icon = "❚❚" }} + {{ else if eq $state "error" "missingFiles" }}{{ $icon = "!" }} + {{ else if eq $state "checkingUP" }}{{ $icon = "…" }} + {{ else if eq $state "checkingResumeData" }}{{ $icon = "⟳" }} + {{ end }} + +
  • +
    {{ $icon }}
    +
    +
    {{ $t.String "name" }}
    +
    + Ratio: {{ printf "%.2f" ($t.Float "ratio") }} | + Size: {{ printf "%.1f GB" (div ($t.Float "size") 1073741824.0) }} +
    +
    +
    + {{ $ulSpeed := $t.Float "upspeed" }} +
    + {{ if lt $ulSpeed 1000.0 }}--{{ else }}{{ printf "%.1f MB/s" (div $ulSpeed 1000000.0) }}{{ end }} +
    +
    Upload
    +
    +
  • + {{ end }} +
+
+ {{ end }} + {{ end }} + +
+ + {{ else }} + +
+
+ {{ $dlSpeed := $transfer.JSON.Float "dl_info_speed" }} +
{{ printf "%.1f MB/s" (div $dlSpeed 1000000.0) }}
+
DOWNLOADING
+
+ {{ if eq $mode "upload" }} +
+ {{ $ulSpeed := $transfer.JSON.Float "up_info_speed" }} +
{{ printf "%.1f MB/s" (div $ulSpeed 1000000.0) }}
+
UPLOADING
+
+ {{ end }} +
+
{{ len ($seeding.JSON.Array "") }}
+
SEEDING
+
+ {{ if eq $mode "default" }} +
+
{{ len ($leeching.JSON.Array "") }}
+
LEECHING
+
+ {{ end }} +
+ {{ end }} + + {{ else }} +
+

Error fetching qBittorrent data.

+

Check URL and authentication bypass settings.

+
+ {{ end }}