Skip to content

users: подписка как список ссылок из бэка (Mihomo URL + clashmi-диплинк) - #116

Merged
Postlog merged 6 commits into
mainfrom
feat/subscription-link-popup
Jun 18, 2026
Merged

users: подписка как список ссылок из бэка (Mihomo URL + clashmi-диплинк)#116
Postlog merged 6 commits into
mainfrom
feat/subscription-link-popup

Conversation

@Postlog

@Postlog Postlog commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Колонка «Подписка» в списке пользователей открывала одну кнопку «Mihomo», копировавшую единственный URL. Конфигов/клиентов становится больше — нужно копировать и сам URL подписки, и диплинк приложения Clashmi (clashmi://install-config?url=<enc>&name=<title>&overwrite=false), а в будущем и другое.

Теперь это попап со списком копируемых ссылок. Главное требование: фронт ничего не хардкодит — состав ссылок и их тайтлы целиком приходят с бэка.

Как сделано

  • internal/service/sublinks (новый сервис) — владеет упорядоченным каталогом ссылок []linkSpec{title, kind, build}: для Mihomo build — сам URL, для Clashmi — формат диплинка. Добавить движок/приложение = одна строка каталога, фронт не меняется.
  • name clashmi-диплинка = profile title эффективного конфига пользователя (кастомный, иначе базовый). Резолв эффективен: базовый title читается один раз на движок, кастомы — только у кого есть.
  • Контракт sub в GET /admin/api/users: {id,url}{links:[{title,value}]} (id фронтом не использовался, url заменён списком).
  • Фронт: кнопка «Ссылки» открывает модалку; на каждую ссылку — только тайтл + кнопка «Копировать» (значение приватное, не отображается). Рендерится sub.links вербатим.
  • apitest (чёрный ящик): модель UserSub.Links + хелпер SubURL() (берёт сырой URL по http-схеме, не по тайтлу).

Решение и альтернативы — ADR-0008.

Проверка

go build/vet/test зелёные; go vet -tags apitest ./apitest/... ок. Подняли локально с тест-данными: alice/bob → name=Freedom (база), carol → name=Freedom Pro (её кастом) — per-user title резолвится верно.

Заметки для ревью

  • Ветка отрезана от чистого main (без responsive-работы из feat/responsive-admin).
  • (#PR) в CHANGELOG/ADR проставлю номером после открытия PR.

🤖 Generated with Claude Code

Колонка «Подписка» открывает попап со списком копируемых ссылок вместо одной
кнопки Mihomo. Каталог ссылок (тайтлы + форматы) живёт на бэке — новый сервис
internal/service/sublinks; фронт ничего не хардкодит, в попапе только тайтл и
кнопка «Копировать» (значение приватное, не показывается).

Форма sub в GET /admin/api/users: {id,url} -> {links:[{title,value}]}.
name clashmi-диплинка = profile title эффективного (кастомного, иначе базового)
конфига пользователя. См. ADR-0008.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Postlog
Postlog force-pushed the feat/subscription-link-popup branch from 599d885 to 00f83e2 Compare June 17, 2026 11:54
…thooks

- Config-вкладка: карточка «Параметры подписки» поднята на первое место
  (перед proxy-groups).
- Убраны остатки githooks: таргет `make hooks` из Makefile и локальный
  core.hooksPath (каталог .githooks/ был удалён ранее).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Postlog

Postlog commented Jun 17, 2026

Copy link
Copy Markdown
Owner Author

Добавлено в этот PR (по ходу ревью-сессии):

  • Config-вкладка: блок «Параметры подписки» поднят на первое место (перед proxy-groups).
  • Чистка githooks: убран таргет make hooks из Makefile и локальный core.hooksPath (каталог .githooks/ был удалён ранее в #—). Упоминаний githooks в репо не осталось.

Коммит a1bd8d2.

Comment thread internal/service/sublinks/sublinks.go Outdated
Comment thread internal/handlers/users_get/contract.go Outdated
Comment thread internal/service/sublinks/contract.go Outdated
По ревью PR #116:
- Интерфейсы зависимостей во всех contract.go названы по конкретной зависимости
  (repo -> <сущность>Repo, service -> <сущность>Service, client -> <сущность>Client)
  вместо ролевых имён (subLinker/configResolver/mihomoReader/creator/deleter/...).
  Правило закреплено в AGENTS.md.
- sublinks: catalogKinds вычисляется один раз на import-time (var-IIFE вместо
  вызова функции на каждый запрос).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI линтил закоммиченный ce873ff без этих переводов строк и падал на wsl_v5
(assign под ExprStmt в sub_valid_test; if под несвязанным присваиванием в
sublinks.go; два присваивания над if в sublinks_test). Локально не
воспроизводилось: format-хук уже добавил их в рабочее дерево, но в коммит они
не попали. Коммичу рабочее дерево — оно wsl_v5-чистое.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Postlog and others added 2 commits June 18, 2026 13:13
make lint больше не зависит от локального golangci-lint (host-сборка под darwin/arm64
расходилась с CI на wsl_v5). Теперь запускает тот же pinned образ
golangci/golangci-lint:v2.12.2 на linux/amd64, что и GitHub Actions, — локальный линт
байт-в-байт совпадает с CI. DOCKER_CONTEXT в Makefile не зашит (специфика машины) —
передаётся при вызове: `DOCKER_CONTEXT=desktop-linux make lint`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… нативная арка)

ci.yml и deploy.yml больше не используют golangci-lint-action: оба вызывают
`make lint`, который гоняет pinned-образ golangci/golangci-lint:v2.12.2 в Docker
(версия пиннится в одном месте — Makefile). Платформа не фиксируется -> нативная для
хоста (CI amd64 / Apple Silicon arm64); все включённые линтеры арх-независимы на
64-бит, поэтому результат идентичный без эмуляции (максимум скорости). Кэш модулей и
build/analysis — в gitignored .lintcache/ (на CI — через actions/cache).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Postlog
Postlog merged commit 323ef07 into main Jun 18, 2026
8 checks passed
Postlog pushed a commit that referenced this pull request Jun 18, 2026
Make the admin panel usable on every screen — mini-phones (320px) through
ultrawide. Static-only (app.css/app.js/index.html); Go is untouched and the
desktop (>=992px) is visually unchanged.

- navbar collapses into a hamburger below lg (992px): animated toggle + a
  full-width collapsible menu; the tabs became <button> (keyboard-operable) with
  a focus-visible ring;
- the Users/Nodes tables turn into data-label cards below 992px (two-up on
  tablets via CSS-grid; the table returns at >=992px);
- the config constructors and rule/group/sub-rule rows stack below 768px; the
  orphaned delete "x" is right-aligned;
- touch targets >=40px and 16px inputs (iOS no-zoom) below 768px; hover popovers
  (connections, description) open on tap (:focus-within + tabindex);
- modals are a flex column that scrolls only the body (header/footer pinned even
  on a short landscape screen), with a vh fallback before dvh;
- the Monaco base-YAML editor no longer traps page scroll on touch;
- ultrawide widens the container (>=1600px -> 1320px, >=2200px -> 1560px).

Re-applied on top of the English UI (#117) and the subscription-links popup
(#116) — the new sublinks modal reuses the generic responsive modal. Verified
with headless Chrome / Playwright at 320/360/500/768/850/1024/1280/1600px (no
horizontal overflow, no desktop regression).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Postlog pushed a commit that referenced this pull request Jun 18, 2026
Make the admin panel usable on every screen — mini-phones (320px) through
ultrawide. Static-only (app.css/app.js/index.html); Go is untouched and the
desktop (>=992px) is visually unchanged.

- navbar collapses into a hamburger below lg (992px): animated toggle + a
  full-width collapsible menu; the tabs became <button> (keyboard-operable) with
  a focus-visible ring;
- the Users/Nodes tables turn into data-label cards below 992px (two-up on
  tablets via CSS-grid; the table returns at >=992px);
- the config constructors and rule/group/sub-rule rows stack below 768px; the
  orphaned delete "x" is right-aligned;
- touch targets >=40px and 16px inputs (iOS no-zoom) below 768px; hover popovers
  (connections, description) open on tap (:focus-within + tabindex);
- modals are a flex column that scrolls only the body (header/footer pinned even
  on a short landscape screen), with a vh fallback before dvh;
- the Monaco base-YAML editor no longer traps page scroll on touch;
- ultrawide widens the container (>=1600px -> 1320px, >=2200px -> 1560px).

Re-applied on top of the English UI (#117) and the subscription-links popup
(#116) — the new sublinks modal reuses the generic responsive modal. Verified
with headless Chrome / Playwright at 320/360/500/768/850/1024/1280/1600px (no
horizontal overflow, no desktop regression).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Postlog pushed a commit that referenced this pull request Jun 18, 2026
Make the admin panel usable on every screen — mini-phones (320px) through
ultrawide. Static-only (app.css/app.js/index.html); Go is untouched and the
desktop (>=992px) is visually unchanged.

- navbar collapses into a hamburger below lg (992px): animated toggle + a
  full-width collapsible menu; the tabs became <button> (keyboard-operable) with
  a focus-visible ring;
- the Users/Nodes tables turn into data-label cards below 992px (two-up on
  tablets via CSS-grid; the table returns at >=992px);
- the config constructors and rule/group/sub-rule rows stack below 768px; the
  orphaned delete "x" is right-aligned;
- touch targets >=40px and 16px inputs (iOS no-zoom) below 768px; hover popovers
  (connections, description) open on tap (:focus-within + tabindex);
- modals are a flex column that scrolls only the body (header/footer pinned even
  on a short landscape screen), with a vh fallback before dvh;
- the Monaco base-YAML editor no longer traps page scroll on touch;
- ultrawide widens the container (>=1600px -> 1320px, >=2200px -> 1560px).

Re-applied on top of the English UI (#117) and the subscription-links popup
(#116) — the new sublinks modal reuses the generic responsive modal. Verified
with headless Chrome / Playwright at 320/360/500/768/850/1024/1280/1600px (no
horizontal overflow, no desktop regression).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Postlog pushed a commit that referenced this pull request Jun 19, 2026
Make the admin panel usable on every screen — mini-phones (320px) through
ultrawide. Static-only (app.css/app.js/index.html); Go is untouched and the
desktop (>=992px) is visually unchanged.

- navbar collapses into a hamburger below lg (992px): animated toggle + a
  full-width collapsible menu; the tabs became <button> (keyboard-operable) with
  a focus-visible ring;
- the Users/Nodes tables turn into data-label cards below 992px (two-up on
  tablets via CSS-grid; the table returns at >=992px);
- the config constructors and rule/group/sub-rule rows stack below 768px; the
  orphaned delete "x" is right-aligned;
- touch targets >=40px and 16px inputs (iOS no-zoom) below 768px; hover popovers
  (connections, description) open on tap (:focus-within + tabindex);
- modals are a flex column that scrolls only the body (header/footer pinned even
  on a short landscape screen), with a vh fallback before dvh;
- the Monaco base-YAML editor no longer traps page scroll on touch;
- ultrawide widens the container (>=1600px -> 1320px, >=2200px -> 1560px).

Re-applied on top of the English UI (#117) and the subscription-links popup
(#116) — the new sublinks modal reuses the generic responsive modal. Verified
with headless Chrome / Playwright at 320/360/500/768/850/1024/1280/1600px (no
horizontal overflow, no desktop regression).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants