diff --git a/.tasks-imported/cms-admin-assets-root-folders-seed.inserted-rows.txt b/.tasks-imported/cms-admin-assets-root-folders-seed.inserted-rows.txt deleted file mode 100644 index bf59f0b..0000000 --- a/.tasks-imported/cms-admin-assets-root-folders-seed.inserted-rows.txt +++ /dev/null @@ -1,21 +0,0 @@ - -(15 rows affected) -Tag,FolderId,OwnerId ----,--------,------- -INSERTED-ROW,580369C8-DB8B-4352-97EB-4F525C989A7C,07E0290C-8735-474E-B494-1230C1E259B3 -INSERTED-ROW,52A0DE78-BF1C-4F6D-BE2E-7C401D64F690,96EBC481-D26A-47BE-B660-13D49E7D0A61 -INSERTED-ROW,465E2F07-525B-4772-BBEB-F7965F605966,81B2461F-78C0-40DF-9B13-2B255DE502FC -INSERTED-ROW,03150541-5278-4FB5-B815-630A17611B3D,E924A354-0377-4E1E-80C6-2EB0194AA55F -INSERTED-ROW,228DDFE9-112E-42BB-8B61-CF88163B94AA,C3B1FD79-663D-4271-A251-380E8B74EB7F -INSERTED-ROW,775F159A-72B7-4B2C-BF13-E57F84BDFCCE,2C82EF33-009E-4779-A51E-389C65D4A3D4 -INSERTED-ROW,0E6B2936-0522-4897-814E-B16FB4091B4C,663F9410-A6CC-4651-9A5C-62844A313957 -INSERTED-ROW,4BD7784F-7BA4-431B-952A-33D422161191,F8FBBF59-8F34-4327-96CA-63A8103D8EE6 -INSERTED-ROW,878D64DF-07B5-4973-BAA4-BC7DA4C7C8BE,4DC7E9E4-8D5D-4ECC-9459-84F7A1BF1759 -INSERTED-ROW,9F368D86-9CB0-48FD-A871-1AA565808BE8,D8E84734-9E35-414D-9342-862A63AD87FB -INSERTED-ROW,542AFFDC-4632-4080-B6C6-19611FB2CEA2,592D399B-31C0-4150-9BFD-880CF7A244C5 -INSERTED-ROW,A7B028C1-D6BD-49FA-8C16-93CF566C6101,37E67FC4-4B9E-4C06-A522-A26A73BAC9B0 -INSERTED-ROW,7AA0D6AC-94F1-49B7-8FFE-A13A15144A84,F30E83BB-2FCA-40A1-A658-AE7ED22F294F -INSERTED-ROW,6033AC61-19F0-4A4C-AD5E-BE54DE64DF6A,5A826EB9-A05A-41D4-A29C-BDEBE926E8DB -INSERTED-ROW,AF61A942-23D0-469C-BB89-B7294BD1DB68,1F12D23C-A9A2-4863-A94D-C6278AD7835E - -(15 rows affected) diff --git a/.tasks-imported/cms-admin-assets-root-folders-seed.md b/.tasks-imported/cms-admin-assets-root-folders-seed.md deleted file mode 100644 index 380d936..0000000 --- a/.tasks-imported/cms-admin-assets-root-folders-seed.md +++ /dev/null @@ -1,74 +0,0 @@ -# cms-admin-assets-root-folders-seed - -## Goal -Для каждого `Sites.SiteId` в DB `MoreThenCms` где нет соответствующего root `Folders` row с `Discriminator='AssetsFolder'`, `LoweredPath='/'`, `OwnerId=` — добавить недостающий root row. Это unblock'нёт admin `/admin/assets//getList?path=` который сейчас crash'ит 500 NullReferenceException на пустых assets для 15+ sites (включая `emspb.ru`, `pilorama98.ru`, `labtools.pro`, `kupimknigi.spb.ru`, `sestech.ru`, etc.). - -## Root cause -`MoreThenCms.Admin.ViewModels.Builders.AssetsJsonViewModelBuilder.Build` (`AssetsJsonViewModelBuilder.cs:22`) делает `new JValue(model.ParentPath)` БЕЗ null-check на model. `_assetsFoldersService.GetFolderByPath(ctx, ownerId, '')` возвращает null когда root folder не существует. Это происходит для sites которые **никогда не открывали admin assets UI** (root создаётся lazy при first upload, видимо). - -Frontend код (`AssetsAppFunc.cs:66-86`) делает proper null-check → HTTP 404. Только admin view-model builder упустил. - -Двух-уровневое решение: -- **Краткосрочно (этой task'и):** seed missing root rows в DB. Один INSERT per site. Risk: minimal (lookup existing row pattern; используем same shape). -- **Долгосрочно (отдельный task):** patch `AssetsJsonViewModelBuilder.Build` → null-guard → empty response. Требует recompile DLL `MoreThenCms.Admin.dll` (нет полного build env, рискованно). - -## Key files -- `MoreThenCms\Assets\Services\AssetsFoldersService.cs:47-54` — `GetFolderByPath` query (returns null if not found). -- `MoreThenCms.Web\ViewModels\Builders\AssetsJsonViewModelBuilder.cs:22` — точка падения. -- `MoreThenCms.Web\Admin\Controllers\AssetsController.cs:44-54` — вызов GetList. -- DB: `[MoreThenCms].[dbo].[Folders]` (polymorphic, Discriminator='AssetsFolder' для assets). - -## Discovery (2026-05-19) -- 15 sites без root AssetsFolder подтверждены через `SELECT s.SiteId, s.PrimaryDomain, ... FROM Sites s LEFT JOIN ...`. См. полный список в Decisions. -- Sample existing root row: `FolderId=1591A38C-..., OwnerId=7EB313DD-F289-..., Path='/', LoweredPath='/', DateCreated=2017-03-13 07:10:15, CreatedById=E4CC416B-...`. Тот же shape надо воспроизвести для missing. -- Затрагивает: emspb.ru (`96EBC481-...`), pilorama98.ru (`37E67FC4-...`), labtools.pro, kupimknigi.spb.ru, sestech.ru, aquamax.spb.ru, artmone.pro, priemka-kvartiry.ru, profund.spb.ru, ics-artmaterials.com, _voda-indigo.ru, plus 4 sites с NULL `PrimaryDomain`. - -## План -1. **Dry-run**: `SELECT COUNT(*) FROM Sites s WHERE NOT EXISTS (root for s.SiteId)` — должно быть = 15 (sanity check). -2. **Seed**: INSERT root row per missing site. Один transaction, no rollback unless count mismatch. -3. **Verify**: повторить original probe — root count should be 0 missing. -4. **Test**: hit `https://emspb.snolla.com/admin/assets/96EBC481-.../getList?path=` (через docker exec без auth → 302→login без crash). Затем user-side browser test. - -## SQL -```sql -DECLARE @CreatedById uniqueidentifier = ( - SELECT TOP 1 CreatedById FROM Folders - WHERE LoweredPath='/' AND Discriminator='AssetsFolder' -); -INSERT INTO Folders (FolderId, OwnerId, Path, LoweredPath, DateCreated, UtcDateCreated, CreatedById, Discriminator) -SELECT NEWID(), s.SiteId, '/', '/', SYSDATETIME(), SYSUTCDATETIME(), @CreatedById, 'AssetsFolder' -FROM Sites s -WHERE NOT EXISTS ( - SELECT 1 FROM Folders f - WHERE f.OwnerId = s.SiteId - AND f.LoweredPath = '/' - AND f.Discriminator = 'AssetsFolder' -); -``` - -## Atomic revert -Сохранить список inserted FolderId через `OUTPUT INSERTED.FolderId` в temp file. Если что: -```sql -DELETE FROM Folders WHERE FolderId IN (); -``` - -## Open questions -- [ ] Аналогичная проблема для ImagesFolder/StylesheetsFolder/ScriptsFolder в admin Themes UI? Проверить. -- [ ] DLL patch `AssetsJsonViewModelBuilder.Build` нужен в долгую — отдельная task. Сейчас обходим seed'ом. - -## Decisions log -- 2026-05-19: вынес из `cms-port-leak-fix` (там был open issue #8 на emspb). User подтвердил воспроизводимость на pilorama98 → не site-specific, общая data issue. -- 2026-05-19: **DB seed применён** — 15 rows inserted (single transaction, idempotent NOT EXISTS). Inserted FolderId/OwnerId captured в `cms-admin-assets-root-folders-seed.inserted-rows.txt`. Server-side verified (probe вернул 302→login вместо 500). User browser-verified на pilorama98/emspb — admin assets открывается, empty list без ошибок. -- 2026-05-19: **DLL recompile отложен** — `AssetsJsonViewModelBuilder.Build` нужен null-guard как defensive code, но требует build env (нет gitea/build/registry на recovery host'е). Ждёт [[vds-kzntsv-bootstrap]] для восстановления pipeline'а. На текущем seed-only fix'е admin assets работает для всех 15 sites. - -## Completed steps -- [x] Diagnostic: stack trace из Application event log → `AssetsJsonViewModelBuilder.cs:22` NullRef on `model.ParentPath`. Source code analysis показал отсутствие null-check (vs proper null-check во frontend `AssetsAppFunc.cs:66-86`). -- [x] DB probe schema: `Folders` table polymorphic с Discriminator, columns FolderId/OwnerId/Path/LoweredPath/DateCreated/CreatedById обязательные. -- [x] Dry-run: 15 sites без root AssetsFolder (включая emspb/pilorama98/labtools.pro/kupimknigi/sestech/etc), 4 sites с NULL PrimaryDomain тоже в списке. -- [x] **SQL seed** через `docker exec mssql sqlcmd` — INSERT 15 rows с `OUTPUT INSERTED.FolderId, INSERTED.OwnerId INTO @Inserted`, output saved в `.tasks/cms-admin-assets-root-folders-seed.inserted-rows.txt`. -- [x] Verify: missing-root count = 0. Probe `wget --header='Host: emspb.snolla.com'` на getList URL → 302 Found (auth redirect — normal), нет больше 500. -- [x] User browser-verified pilorama98 admin assets открывается. -- [x] Wiki ingest: новый concept `cms-admin-assets-root-folder-seed.md`, snapshot issue #8 → resolved, `cms-server-port-leak-fix` sibling-link, log.md + index.md обновлены. - -## Notes -- Связано: [[cms-server-port-leak-fix]] (предыдущая task'а закрыта; admin URLs теперь без `:8089`); [[recovery-architecture-snapshot]] (issue #8 — resolved); [[vds-kzntsv-bootstrap]] (build env для долгосрочного DLL fix'а). diff --git a/.tasks-imported/cms-maljarka-https-mode-bug-fix.md b/.tasks-imported/cms-maljarka-https-mode-bug-fix.md deleted file mode 100644 index 0b9f995..0000000 --- a/.tasks-imported/cms-maljarka-https-mode-bug-fix.md +++ /dev/null @@ -1,52 +0,0 @@ -# cms-maljarka-https-mode-bug-fix - -## Goal - -Починить `maljarka.tandemmebel.ru` чтобы он отвечал 200 (не 502) через публичный HTTPS chain. Root cause diagnosed в [[traefik-maljarka-502-bug]]: IIS+CMS падает на этом hostname когда `HTTPS=on`/`SERVER_PORT=443` выставлены URL Rewrite rule'ом. Full mechanism — wiki concept [[cms-maljarka-https-mode-crash]]. - -## Key files - -- `C:\sites\snolla\Web.config` — URL Rewrite rule "ForwardedProto-HTTPS" из [[cms-server-port-leak-fix]] -- `C:\inetpub\logs\LogFiles\W3SVC*\` — IIS logs site `snolla` (нужно найти sub-status 502.x для конкретного request) -- CMS DB — table с per-host config (per [[recovery-architecture-snapshot]], CMS использует MSSQL контейнер) -- CMS source: `MoreThenCms.Admin\Mis\Web\Mvc\UrlHelpers\UrlHelpers.cs:14-37` (`Url.SiteRoot()`) — паттерн из [[cms-server-port-leak-fix]]; возможно тут helper падает specifically на этом host - -## Bisect-trace evidence - -``` -direct backend Host: maljarka.tandemmebel.ru → 200 OK (43KB) -direct backend Host: maljarka.tandemmebel.ru + X-Forwarded-For → 200 OK -direct backend Host: maljarka.tandemmebel.ru + X-Forwarded-Proto:https → 502 ← триггер -direct backend Host: tandemmebel.ru + X-Forwarded-Proto:https → 301 (correct) -``` - -Single variable: `X-Forwarded-Proto: https` для этого specific hostname. - -## Next action - -1. **IIS logs**: - ```powershell - Get-Content 'C:\inetpub\logs\LogFiles\W3SVC1\u_extend1*.log' -Tail 500 | Select-String 'maljarka' | Select-Object -Last 20 - ``` - Найти sub-status (502.3 = bad gateway, 502.4 = no server, 502.5 = ARR config, etc.). - -2. **Event Viewer**: - ```powershell - Get-EventLog -LogName Application -Source 'ASP.NET*' -Newest 50 | Where-Object Message -Match 'maljarka' | Format-List - ``` - Stack trace покажет точное место crash. - -3. **CMS DB inspection** — есть ли запись для `maljarka.tandemmebel.ru` в table с per-host URLs, и поле HttpsUrl/BaseUrl не NULL ли там. - -4. **Quick workaround если real customer traffic** — apply Workaround B из [[cms-maljarka-https-mode-crash]] (negate condition в URL Rewrite rule for this Host). Backup Web.config first. - -## Open questions - -- [ ] Какой priority? Зависит от того real customer traffic на `maljarka.tandemmebel.ru` или это internal/test subdomain. -- [ ] Если CMS bug — fix CMS code или DB hot-fix? -- [ ] DLL patch для null-guard (от [[cms-admin-assets-root-folders-seed]]) — может тот же класс bugов; может в одном вылазе чинить вместе. - -## Notes - -- 3 workarounds + 5 investigation places задокументированы в [[cms-maljarka-https-mode-crash]] wiki concept. -- Не trivially связан с [[cms-server-port-leak-fix]] — там URL Rewrite rule добавлен правильно, но CMS код в этом конкретном случае ломается. Регрессия CMS, не fix'а. diff --git a/.tasks-imported/cms-port-leak-fix.md b/.tasks-imported/cms-port-leak-fix.md deleted file mode 100644 index d389e52..0000000 --- a/.tasks-imported/cms-port-leak-fix.md +++ /dev/null @@ -1,104 +0,0 @@ -# cms-port-leak-fix - -## Goal -Убрать утечку internal-портов в URL'ы, которые CMS генерирует серверно. Сейчас часть admin-роутов выдают URL'ы вида `https://labtools.snolla.com:8089/admin/...` — `:8089` это IIS site binding на хосте, не должен попадать наружу. - -Связанный, тот же класс баг (open issue #1 в [recovery-architecture-snapshot.md](../.wiki/concepts/recovery-architecture-snapshot.md)): CMS делает HTTP→HTTPS redirect с `:4443` (traefik external HTTPS port на хосте). Желательно фиксить общим решением. - -## Симптомы (от user, 2026-05-19) -Сломанные URLs admin'ки на `labtools.snolla.com` (наверняка и на остальных 10 cms доменах тоже): -- `https://labtools.snolla.com:8089/admin/assets//getList?path=` -- `https://labtools.snolla.com:8089/admin/themes/getImageSizes/` -- `https://labtools.snolla.com:8089/admin/templates/editors.tmpl.html?v=2.006` - -Browser сразу делает request на `:8089` напрямую → fail (этот порт не открыт наружу через router NAT; traefik слушает 443/4443 only). - -## Hypothesis — root cause -Цепочка: -1. Traefik backend = `http://host.docker.internal:8089/`, `passHostHeader: true` → IIS видит `Host: labtools.snolla.com` (без порта). -2. IIS binding на site `snolla` = `*:8089` → connection-level порт = 8089. -3. `HttpRequest.Url.Port` / `Url.Authority` в ASP.NET возвращают **порт сокета**, не порт из Host header. Значит `Authority = "labtools.snolla.com:8089"`. -4. Где-то в admin views / controllers / partials CMS строит absolute URL через `Request.Url.GetLeftPart(UriPartial.Authority)` или `string.Format("{0}://{1}", scheme, host_with_port)` → утечка `:8089`. -5. Альтернатива — admin SPA получает baseUrl в server-rendered JS-переменной (`window.adminBaseUrl = '@Request.Url.Scheme://@Request.Url.Authority/admin'` или подобное). Это нужно подтвердить grep'ом по deployed-ASP-views + .js. - -Аналогичная hypothesis для `:4443`: -- Не из `Url.Port` (он = 8089 на attempt2-host, был 18080 на VM-эпохе). -- Скорее CMS читает `Request.Headers["X-Forwarded-Host"]` который traefik по умолчанию шлёт как `host:port`, ИЛИ кто-то один раз протестил через `https://host:4443/` напрямую и попал в captured-redirect. -- Возможно: CMS делает `Response.Redirect` с absolute URL, и старый CMS-код в эпоху production-traefik (на синке тоже :4443?) научился возвращать публичный port из конфига. -- **Эта часть требует тест-кейса** — повторить на host'е сейчас и посмотреть, попадает ли вообще `:4443` в response Location header. - -## Key files (текущие deployed) -- `C:\sites\snolla\Views\Shared\_Layout.cshtml` — main layout, есть `var hostingUrl = new Uri("https://" + AppSettings["primaryDomain"]);` (line 11-12). Не порт, но паттерн «строим absolute URL» подсветить. -- `C:\sites\snolla\Areas\Admin\Views\_ViewStart.cshtml` — корень всех admin views, скорее всего тянет admin-specific layout. -- `C:\sites\snolla\Areas\Admin\Views\**\Shared\*` — admin layout (искать; возможно тут baseUrl). -- `C:\sites\snolla\admin\templates\*.tmpl.html` — статические template'ы admin SPA. Сами URL'ы не генерируют, но загружаются из JS которая видит baseUrl откуда-то. -- `C:\sites\snolla\Web.config` — appSettings (`primaryDomain` уже есть), будущие `` rules. -- `C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\*.yml` — site routes (тут middleware подключаем). -- `C:\Users\vitya\projects\docker\diskstation\traefik\data\custom\snolla.yml` `:1` — already has `passHostHeader: true`. Добавить middleware на forwarded-headers. - -## План этапов - -### Phase 1 — diagnostic (не write-op) -- [ ] Сделать XHR call на `https://labtools.snolla.com/admin/themes/getImageSizes/` (с auth-cookie если есть) и записать response headers + body. Подтвердить откуда именно `:8089` приходит (Location header? `` tag? `