{
  "schema_version": 1,
  "channel": "homelab-runtime",
  "command_id": "GRAM1-SITE-CHARTS-BROWSER-RUNTIME-V28-REDIRECT-AWARE-READ-ONLY-EXACT-DIAG-20260801T103600Z",
  "status": "OK",
  "rc": 0,
  "host": "pve01",
  "mode": "verify",
  "component": "gram1-charts-v28-redirect-diag",
  "started_at_utc": "2026-08-01T10:40:35Z",
  "finished_at_utc": "2026-08-01T10:40:36Z",
  "reference_register_checked": true,
  "reference_sha256": "5763f2f8edc75fcf6f3951f8c95896d1cc112ceccb6cf5b64a383f540a4deb8e",
  "error_register_checked": true,
  "error_register_sha256": "24934a2c7fa5c26f6e828583c4aefac7f143b40c0171736ed7571e617d55ab83",
  "command_sha256": "98af2a3d5e9c35d738d720d709b5d1bc33f6497016ebe05567f476baa93d9c2b",
  "duplicate_failed_command_blocked": false,
  "block_reason": null,
  "execution_started": true,
  "change_declared": false,
  "result_contract_valid": true,
  "result_contract_status": null,
  "result_contract_error": null,
  "command_rc": 0,
  "changes_made": false,
  "rollback_started": false,
  "rollback_restored": null,
  "mutation_outcome": "NO_MUTATION",
  "sanitized": true,
  "secrets_included": false,
  "private_addresses_included": false,
  "raw_evidence_retained_locally": true,
  "raw_evidence_sha256": "b6a56141a8cd4f0df4c91b72560fa95477d9cd1a4f37e624b33d8fbdebbab62a",
  "sanitized_output_sha256": "d42000ea7905c64d8e19e55149a81d3750ab5f9aa692923a7805001cf247c48b",
  "output_truncated_in_json": false,
  "full_sanitized_output_url": "https://git.gram1.ru/.well-known/homelab-runtime/latest.txt",
  "output": "HOMELAB_RESULT_CONTRACT={\"changes_made\":false,\"command_id\":\"GRAM1-SITE-CHARTS-BROWSER-RUNTIME-V28-REDIRECT-AWARE-READ-ONLY-EXACT-DIAG-20260801T103600Z\",\"mutation_outcome\":\"NO_MUTATION\",\"rollback_restored\":null,\"rollback_started\":false,\"status\":\"OK\",\"version\":1}\n{\"changes_made\":false,\"command_id\":\"GRAM1-SITE-CHARTS-BROWSER-RUNTIME-V28-REDIRECT-AWARE-READ-ONLY-EXACT-DIAG-20260801T103600Z\",\"control_plane_error\":null,\"gitea_changed\":false,\"mutation_outcome\":\"NO_MUTATION\",\"pinned_host_key_aliases\":[\"[PRIVATE_IP]\"],\"previous_capsule_sha256\":\"568b0311741b93ae8ece3a3346c287f195c3f727ce6d04d4e28bdf87a5a4a88b\",\"previous_command_id\":\"GRAM1-SITE-CHARTS-LAZY-ACCORDION-V26-ATOMIC-FIX-20260801T093500Z\",\"production_site_changed\":false,\"rc\":0,\"remote_result\":{\"changes_made\":false,\"chart_libraries\":[{\"api_symbols\":{\"CandlestickSeries\":1,\"ColorType\":1,\"addCandlestickSeries\":0,\"addSeries\":6,\"createChart\":2},\"bytes\":196203,\"http_status\":200,\"sha256\":\"c0992580867c4912cc9385b3c2728315bcc1a76c7f1087dca908430fccdf31d7\",\"url\":\"/vendor/lightweight-charts.js\",\"versions\":[\"5.2.0\"]}],\"command_id\":\"GRAM1-SITE-CHARTS-BROWSER-RUNTIME-V28-REDIRECT-AWARE-READ-ONLY-EXACT-DIAG-20260801T103600Z\",\"compose_services\":[\"web\",\"worker\"],\"control_plane_error\":null,\"diagnosis\":[\"LOCAL_HOME_REDIRECT_STATUS_303\",\"CANDLE_SCHEMA_INVALID:HBAR,POL,AVAX,LINK\",\"PUBLIC_ENDPOINT_SERVES_DIFFERENT_OR_CACHED_HTML:gram1.ru\",\"LEGACY_NO_DATA_FALLBACK_REMAINS_IN_HTML\"],\"duration_seconds\":0.678,\"exact_state\":{\"backup_metadata_sha256\":\"fee67cb4877e96dca3113a17d1ec2facc8ecacaa97def7d83db86c1a545f1f53\",\"compose_sha256\":\"6d344e46b3de94ad86131a914df2ff3ff25e9d82f7ce0557cbffa468b8cdc1b8\",\"current_hashes\":{\"app.py\":\"ec226952cdb7fb58edbfc0173af47f4d901f3206e15169df12294983731fbc20\",\"templates/index.html\":\"db9a1605bd37fca7d6ff651921dd3bc0ccac991f2e8f221179c4dddc3b2c3536\",\"worker.py\":\"4a7b16ece826fe2d938f392db6b0b5ca028dcb6ff02fdff4ab431f114edf0e5c\"},\"exact_v26_target_installed\":true,\"v26_target_hashes\":{\"app.py\":\"ec226952cdb7fb58edbfc0173af47f4d901f3206e15169df12294983731fbc20\",\"templates/index.html\":\"db9a1605bd37fca7d6ff651921dd3bc0ccac991f2e8f221179c4dddc3b2c3536\",\"worker.py\":\"4a7b16ece826fe2d938f392db6b0b5ca028dcb6ff02fdff4ab431f114edf0e5c\"}},\"gitea_changed\":false,\"headless_browser\":{\"available\":false,\"ports\":[8000,5000,8080,3000,80,36645]},\"local_runtime\":{\"api\":[{\"count\":0,\"http_bytes\":27,\"http_sha256\":\"913db652b5cf6ba54020fa7426c642b69f1c868742feddc69fd4077076d942d9\",\"http_status\":401,\"issues\":[],\"lightweight_charts_schema_ok\":false,\"sample\":[],\"status\":null,\"symbol\":\"HBAR\"},{\"count\":0,\"http_bytes\":27,\"http_sha256\":\"913db652b5cf6ba54020fa7426c642b69f1c868742feddc69fd4077076d942d9\",\"http_status\":401,\"issues\":[],\"lightweight_charts_schema_ok\":false,\"sample\":[],\"status\":null,\"symbol\":\"POL\"},{\"count\":0,\"http_bytes\":27,\"http_sha256\":\"913db652b5cf6ba54020fa7426c642b69f1c868742feddc69fd4077076d942d9\",\"http_status\":401,\"issues\":[],\"lightweight_charts_schema_ok\":false,\"sample\":[],\"status\":null,\"symbol\":\"AVAX\"},{\"count\":0,\"http_bytes\":27,\"http_sha256\":\"913db652b5cf6ba54020fa7426c642b69f1c868742feddc69fd4077076d942d9\",\"http_status\":401,\"issues\":[],\"lightweight_charts_schema_ok\":false,\"sample\":[],\"status\":null,\"symbol\":\"LINK\"}],\"homepage\":{\"bytes\":32738,\"id_contexts\":{\"chart\":[{\"context\":\"y=\\\"RUB\\\">RUB</button></div><form method=\\\"post\\\" action=\\\"/logout\\\"><button class=\\\"btn ghost\\\">Выйти</button></form></div></div> <section class=\\\"grid4\\\"><div class=\\\"card\\\"><span class=\\\"muted\\\">Стоимость портфеля</span><strong id=\\\"total\\\">—</strong><span id=\\\"day\\\" class=\\\"muted\\\">—</span></div><div class=\\\"card\\\"><span class=\\\"muted\\\">Вложено</span><strong id=\\\"invested\\\">—</strong></div><div class=\\\"card\\\"><span class=\\\"muted\\\">Прибыль / убыток</span><strong id=\\\"pnl\\\">—</strong><span id=\\\"pnlp\\\" class=\\\"muted\\\">0%</span></div><div class=\\\"card\\\"><span class=\\\"muted\\\">Обновление рынка</span><strong id=\\\"updated\\\">—</strong><span class=\\\"muted\\\">каждые 5 минут</span></div></section> <section id=\\\"coins\\\" class=\\\"coins\\\"></section><section class=\\\"layout\\\"><div class=\\\"panel\\\"><div class=\\\"title\\\"><div><h2 id=\\\"chartTitle\\\">График цены</h2><span class=\\\"muted\\\">7 дней</span></div><span id=\\\"marketStatus\\\" class=\\\"status\\\">Загрузка</span></div><svg id=\\\"chart\\\" class=\\\"chart\\\" viewBox=\\\"0 0 900 270\\\" preserveAspectRatio=\\\"none\\\"></svg></div><div class=\\\"panel sharp-moves-panel\\\"><div class=\\\"title\\\"><h2>Резкие движения</h2></div><div id=\\\"moves\\\" class=\\\"moves\\\"></div></div></section> <section class=\\\"panel\\\" style=\\\"margin-top:14px\\\"><div class=\\\"title\\\"><div><h2>Позиции портфеля</h2><span class=\\\"muted\\\">Количество и средняя цена покупки</span></div></div><div class=\\\"table\\\"><table><thead><tr><th>Актив</th><th>Цена</th><th>Капитализация</th><th>Количество</th><th id=\\\"avgHeader\\\">Средняя покупка (USD)</th><th>Вложено</th><th>Сейчас</th><th>P/L</th></tr></thead><tbody id=\\\"rows\\\"></tbody></table></div></section> <section class=\\\"alerts\\\"><div class=\\\"panel\\\"><div class=\\\"title\\\"><h2>Новое оповещение</h2></div><div class=\\\"form\\\"><select id=\\\"aCoin\\\"></select><select id=\\\"aKind\\\"><option value=\\\"price_above\\\">Цена выше</option><option value=\\\"price_below\\\">Цена ниже</option><option value=\\\"cap_above\\\">Капитализация выше</option><option value=\\\"cap_bel\",\"match\":\"<svg id=\\\"chart\\\" class=\\\"chart\\\" viewBox=\\\"0 0 900 270\\\" preserveAspectRatio=\\\"none\\\">\",\"offset\":5120}],\"chartTitle\":[{\"context\":\"tch\\\"><button type=\\\"button\\\" class=\\\"currency-btn\\\" data-currency=\\\"USD\\\">USD</button><button type=\\\"button\\\" class=\\\"currency-btn\\\" data-currency=\\\"RUB\\\">RUB</button></div><form method=\\\"post\\\" action=\\\"/logout\\\"><button class=\\\"btn ghost\\\">Выйти</button></form></div></div> <section class=\\\"grid4\\\"><div class=\\\"card\\\"><span class=\\\"muted\\\">Стоимость портфеля</span><strong id=\\\"total\\\">—</strong><span id=\\\"day\\\" class=\\\"muted\\\">—</span></div><div class=\\\"card\\\"><span class=\\\"muted\\\">Вложено</span><strong id=\\\"invested\\\">—</strong></div><div class=\\\"card\\\"><span class=\\\"muted\\\">Прибыль / убыток</span><strong id=\\\"pnl\\\">—</strong><span id=\\\"pnlp\\\" class=\\\"muted\\\">0%</span></div><div class=\\\"card\\\"><span class=\\\"muted\\\">Обновление рынка</span><strong id=\\\"updated\\\">—</strong><span class=\\\"muted\\\">каждые 5 минут</span></div></section> <section id=\\\"coins\\\" class=\\\"coins\\\"></section><section class=\\\"layout\\\"><div class=\\\"panel\\\"><div class=\\\"title\\\"><div><h2 id=\\\"chartTitle\\\">График цены</h2><span class=\\\"muted\\\">7 дней</span></div><span id=\\\"marketStatus\\\" class=\\\"status\\\">Загрузка</span></div><svg id=\\\"chart\\\" class=\\\"chart\\\" viewBox=\\\"0 0 900 270\\\" preserveAspectRatio=\\\"none\\\"></svg></div><div class=\\\"panel sharp-moves-panel\\\"><div class=\\\"title\\\"><h2>Резкие движения</h2></div><div id=\\\"moves\\\" class=\\\"moves\\\"></div></div></section> <section class=\\\"panel\\\" style=\\\"margin-top:14px\\\"><div class=\\\"title\\\"><div><h2>Позиции портфеля</h2><span class=\\\"muted\\\">Количество и средняя цена покупки</span></div></div><div class=\\\"table\\\"><table><thead><tr><th>Актив</th><th>Цена</th><th>Капитализация</th><th>Количество</th><th id=\\\"avgHeader\\\">Средняя покупка (USD)</th><th>Вложено</th><th>Сейчас</th><th>P/L</th></tr></thead><tbody id=\\\"rows\\\"></tbody></table></div></section> <section class=\\\"alerts\\\"><div class=\\\"panel\\\"><div class=\\\"title\\\"><h2>Новое оповещение</h2></div><div class=\\\"form\\\"><select id=\\\"aCoin\\\"><\",\"match\":\"<h2 id=\\\"chartTitle\\\">\",\"offset\":4985}],\"tvCharts\":[]},\"label\":\"installed_template_due_local_redirect\",\"markers\":{\"accordion_style\":1,\"chart_error_text\":1,\"details_creator\":1,\"loading_text\":2,\"no_data_text\":3,\"tvCharts\":3,\"v1_script\":0,\"v26_script\":1},\"no_data_contexts\":[{\"context\":\"tep=\\\\\\\"any\\\\\\\" value=\\\\\\\"\\\"+inputPrice(h.avg_buy)+\\\"\\\\\\\"></td><td>\\\"+money(cost,0)+\\\"</td><td>\\\"+money(value,2)+\\\"</td><td class=\\\\\\\"\\\"+(p>=0?\\\"good\\\":\\\"bad\\\")+\\\"\\\\\\\">\\\"+money(p,2)+\\\"</td></tr>\\\"}).join(\\\"\\\");document.querySelectorAll(\\\"#rows tr\\\").forEach((tr,i)=>{let id=ids[i];let td=tr.cells[0];td.classList.add(\\\"asset-cell\\\");let img=document.createElement(\\\"img\\\");img.className=\\\"coin-icon table-coin-icon\\\";img.src=\\\"/coin-icon/\\\"+id;img.alt=state.coins[id];img.loading=\\\"lazy\\\";td.prepend(img);let symbol=td.querySelector(\\\"b\\\");if(symbol)symbol.classList.add(\\\"asset-symbol\\\")});totalEl=total;document.getElementById(\\\"total\\\").textContent=money(total,0);invested.textContent=money(inv,0);let p=total-inv;pnl.textContent=money(p,0);pnl.className=p>=0?\\\"good\\\":\\\"bad\\\";pnlp.textContent=(inv?p/inv*100:0).toFixed(2)+\\\"%\\\";day.textContent=(daily>=0?\\\"+\\\":\\\"\\\")+money(daily,2)+\\\" за 24ч\\\";updated.textContent=state.market.updated_at?new Date(state.market.updated_at*1000).toLocaleTimeString(\\\"ru-RU\\\"):\\\"нет данных\\\";marketStatus.textContent=Object.keys(m).length===2?\\\"Рынок обновлён\\\":\\\"Нет данных\\\";marketStatus.className=\\\"status \\\"+(Object.keys(m).length===2?\\\"good\\\":\\\"bad\\\");bindInputs();draw();renderMoves();renderAlerts();aCoin.innerHTML=ids.map(id=>\\\"<option value=\\\\\\\"\\\"+id+\\\"\\\\\\\">\\\"+state.coins[id]+\\\"</option>\\\").join(\\\"\\\")}function bindInputs(){document.querySelectorAll(\\\".qty,.avg\\\").forEach(x=>x.onchange=async()=>{let id=x.dataset.id;let q=document.querySelector(\\\".qty[data-id=\\\\\\\"\\\"+id+\\\"\\\\\\\"]\\\").value;let a=document.querySelector(\\\".avg[data-id=\\\\\\\"\\\"+id+\\\"\\\\\\\"]\\\").value;let avg=currency===\\\"RUB\\\"?Number(a)/fx():Number(a);await fetch(\\\"/api/holding\\\",{method:\\\"POST\\\",headers:{\\\"Content-Type\\\":\\\"application/json\\\"},body:JSON.stringify({coin_id:id,quantity:Number(q),avg_buy:avg})});load()})}function draw(){let c=coin();chartTitle.textContent=(c.name||selected)+\\\" · \\\"+state.coins[selected]+\\\"/\\\"+currency;let a=c.sparkline_7d||[];if(a.length<2){chart.innerHTML=\\\"<text x=\\\\\\\"20\\\\\\\" y=\\\\\\\"40\\\\\\\" fill=\\\\\",\"match\":\"нет данных\",\"offset\":10457},{\"context\":\"d>\\\"+money(value,2)+\\\"</td><td class=\\\\\\\"\\\"+(p>=0?\\\"good\\\":\\\"bad\\\")+\\\"\\\\\\\">\\\"+money(p,2)+\\\"</td></tr>\\\"}).join(\\\"\\\");document.querySelectorAll(\\\"#rows tr\\\").forEach((tr,i)=>{let id=ids[i];let td=tr.cells[0];td.classList.add(\\\"asset-cell\\\");let img=document.createElement(\\\"img\\\");img.className=\\\"coin-icon table-coin-icon\\\";img.src=\\\"/coin-icon/\\\"+id;img.alt=state.coins[id];img.loading=\\\"lazy\\\";td.prepend(img);let symbol=td.querySelector(\\\"b\\\");if(symbol)symbol.classList.add(\\\"asset-symbol\\\")});totalEl=total;document.getElementById(\\\"total\\\").textContent=money(total,0);invested.textContent=money(inv,0);let p=total-inv;pnl.textContent=money(p,0);pnl.className=p>=0?\\\"good\\\":\\\"bad\\\";pnlp.textContent=(inv?p/inv*100:0).toFixed(2)+\\\"%\\\";day.textContent=(daily>=0?\\\"+\\\":\\\"\\\")+money(daily,2)+\\\" за 24ч\\\";updated.textContent=state.market.updated_at?new Date(state.market.updated_at*1000).toLocaleTimeString(\\\"ru-RU\\\"):\\\"нет данных\\\";marketStatus.textContent=Object.keys(m).length===2?\\\"Рынок обновлён\\\":\\\"Нет данных\\\";marketStatus.className=\\\"status \\\"+(Object.keys(m).length===2?\\\"good\\\":\\\"bad\\\");bindInputs();draw();renderMoves();renderAlerts();aCoin.innerHTML=ids.map(id=>\\\"<option value=\\\\\\\"\\\"+id+\\\"\\\\\\\">\\\"+state.coins[id]+\\\"</option>\\\").join(\\\"\\\")}function bindInputs(){document.querySelectorAll(\\\".qty,.avg\\\").forEach(x=>x.onchange=async()=>{let id=x.dataset.id;let q=document.querySelector(\\\".qty[data-id=\\\\\\\"\\\"+id+\\\"\\\\\\\"]\\\").value;let a=document.querySelector(\\\".avg[data-id=\\\\\\\"\\\"+id+\\\"\\\\\\\"]\\\").value;let avg=currency===\\\"RUB\\\"?Number(a)/fx():Number(a);await fetch(\\\"/api/holding\\\",{method:\\\"POST\\\",headers:{\\\"Content-Type\\\":\\\"application/json\\\"},body:JSON.stringify({coin_id:id,quantity:Number(q),avg_buy:avg})});load()})}function draw(){let c=coin();chartTitle.textContent=(c.name||selected)+\\\" · \\\"+state.coins[selected]+\\\"/\\\"+currency;let a=c.sparkline_7d||[];if(a.length<2){chart.innerHTML=\\\"<text x=\\\\\\\"20\\\\\\\" y=\\\\\\\"40\\\\\\\" fill=\\\\\\\"#8fa5b6\\\\\\\">История ещё загружается</text>\\\";return}let min=Math.min(...a),max=Math\",\"match\":\"Нет данных\",\"offset\":10538},{\"context\":\"ls=[\\\"HBAR\\\",\\\"POL\\\",\\\"AVAX\\\",\\\"LINK\\\"]; const charts=new Map(); const candleCache=new Map(); const afterLayout=()=>new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve))); const loadCandles=async symbol=>{ if(candleCache.has(symbol))return candleCache.get(symbol); const task=(async()=>{ const response=await fetch(\\\"/api/klines/\\\"+encodeURIComponent(symbol)+\\\"?limit=1000&interval=1M\\\",{cache:\\\"no-store\\\",credentials:\\\"same-origin\\\"}); if(response.status===401){location=\\\"/login\\\";throw new Error(\\\"Требуется повторный вход\\\")} if(!response.ok)throw new Error(\\\"HTTP \\\"+response.status); const data=await response.json(); const candles=Array.isArray(data.candles)?data.candles:(data.result&&Array.isArray(data.result.candles)?data.result.candles:[]); if(data.status&&data.status!==\\\"OK\\\")throw new Error(String(data.error||data.message||data.status)); if(!candles.length)throw new Error(\\\"Нет данных для \\\"+symbol); return candles; })(); candleCache.set(symbol,task); try{return await task}catch(error){candleCache.delete(symbol);throw error} }; const widthOf=box=>Math.max(320,Math.floor(box.getBoundingClientRect().width||box.clientWidth||900)); const ensureChart=card=>{ if(card.dataset.chartState===\\\"ready\\\")return Promise.resolve(charts.get(card.dataset.symbol)); if(card.dataset.chartState===\\\"loading\\\"&&card._chartPromise)return card._chartPromise; const symbol=card.dataset.symbol; const box=card.querySelector(\\\".tv-widget-slot\\\"); const errorBox=card.querySelector(\\\".tv-chart-error\\\"); card.dataset.chartState=\\\"loading\\\"; errorBox.textContent=\\\"Загрузка...\\\"; card._chartPromise=(async()=>{ try{ const candles=await loadCandles(symbol); await afterLayout(); if(!card.open){card.dataset.chartState=\\\"idle\\\";return null} const api=LightweightCharts.createCha\",\"match\":\"Нет данных\",\"offset\":25676}],\"scripts\":[{\"async\":false,\"body_bytes\":7045,\"body_sha256\":\"e6afa17d7b94d9da5036d55bf6b8c623ec036ca05e9dea9c789432631c1b6ca7\",\"contains_v26\":false,\"defer\":false,\"id\":null,\"order\":0,\"src\":null},{\"async\":false,\"body_bytes\":4775,\"body_sha256\":\"f30846a0a26517bd6e8303d552aeba19887576c54b4a70678e234a5e1f6e7582\",\"contains_v26\":false,\"defer\":false,\"id\":\"ALERT_CREATE_OVERRIDE_V3\",\"order\":1,\"src\":null},{\"async\":false,\"body_bytes\":4605,\"body_sha256\":\"b6fedba59295891e784ab98cef7690efe81553715d35a9cf6a31c30d08efb5a9\",\"contains_v26\":false,\"defer\":false,\"id\":\"ALERT_LIST_RENDERER_V5\",\"order\":2,\"src\":null},{\"async\":false,\"body_bytes\":0,\"body_sha256\":null,\"contains_v26\":false,\"defer\":false,\"id\":null,\"order\":3,\"src\":\"/vendor/lightweight-charts.js\"},{\"async\":false,\"body_bytes\":6016,\"body_sha256\":\"084f5447f052e941f4eef6784b1431399a6dd17b28dba277fe310fca903224d9\",\"contains_v26\":true,\"defer\":false,\"id\":\"LOCAL_TRADINGVIEW_CHARTS_V2_LAZY_ACCORDION\",\"order\":4,\"src\":null},{\"async\":false,\"body_bytes\":642,\"body_sha256\":\"90a765a83fb8a5a0542dee1abf8c32af0f248aa411aa203848f73758bc3f9b40\",\"contains_v26\":false,\"defer\":false,\"id\":\"MOVE_CHARTS_TO_BOTTOM_ALLTIME_V1\",\"order\":5,\"src\":null}],\"sha256\":\"db9a1605bd37fca7d6ff651921dd3bc0ccac991f2e8f221179c4dddc3b2c3536\"},\"homepage_headers\":{\"cache-control\":null,\"content-type\":null,\"etag\":null,\"last-modified\":null,\"location\":\"/login\"},\"homepage_redirect\":{\"location\":\"/login\",\"status\":303},\"homepage_source\":\"installed_template_due_local_redirect\",\"homepage_status\":303},\"mutation_outcome\":\"NO_MUTATION\",\"previous_capsule_sha256\":\"568b0311741b93ae8ece3a3346c287f195c3f727ce6d04d4e28bdf87a5a4a88b\",\"previous_command_id\":\"GRAM1-SITE-CHARTS-LAZY-ACCORDION-V26-ATOMIC-FIX-20260801T093500Z\",\"production_site_changed\":false,\"public_hosts\":[\"gram1.ru\"],\"public_runtime\":[{\"bytes\":1088,\"final_url\":\"https://gram1.ru/login\",\"headers\":{\"age\":null,\"cache-control\":null,\"cf-cache-status\":null,\"content-type\":\"text/html; charset=utf-8\",\"etag\":null,\"last-modified\":null,\"server\":null},\"host\":\"gram1.ru\",\"markers\":{\"accordion_style\":0,\"no_data\":0,\"tvCharts\":0,\"v26_script\":0},\"sha256\":\"66b06909949879d84757d0f96be74eebf1db40f35de22b625ea67e85e1bbdae2\",\"status\":200}],\"rc\":0,\"rollback_restored\":null,\"rollback_started\":false,\"schema\":1,\"status\":\"OK\",\"template\":{\"bytes\":32738,\"id_contexts\":{\"chart\":[{\"context\":\"y=\\\"RUB\\\">RUB</button></div><form method=\\\"post\\\" action=\\\"/logout\\\"><button class=\\\"btn ghost\\\">Выйти</button></form></div></div> <section class=\\\"grid4\\\"><div class=\\\"card\\\"><span class=\\\"muted\\\">Стоимость портфеля</span><strong id=\\\"total\\\">—</strong><span id=\\\"day\\\" class=\\\"muted\\\">—</span></div><div class=\\\"card\\\"><span class=\\\"muted\\\">Вложено</span><strong id=\\\"invested\\\">—</strong></div><div class=\\\"card\\\"><span class=\\\"muted\\\">Прибыль / убыток</span><strong id=\\\"pnl\\\">—</strong><span id=\\\"pnlp\\\" class=\\\"muted\\\">0%</span></div><div class=\\\"card\\\"><span class=\\\"muted\\\">Обновление рынка</span><strong id=\\\"updated\\\">—</strong><span class=\\\"muted\\\">каждые 5 минут</span></div></section> <section id=\\\"coins\\\" class=\\\"coins\\\"></section><section class=\\\"layout\\\"><div class=\\\"panel\\\"><div class=\\\"title\\\"><div><h2 id=\\\"chartTitle\\\">График цены</h2><span class=\\\"muted\\\">7 дней</span></div><span id=\\\"marketStatus\\\" class=\\\"status\\\">Загрузка</span></div><svg id=\\\"chart\\\" class=\\\"chart\\\" viewBox=\\\"0 0 900 270\\\" preserveAspectRatio=\\\"none\\\"></svg></div><div class=\\\"panel sharp-moves-panel\\\"><div class=\\\"title\\\"><h2>Резкие движения</h2></div><div id=\\\"moves\\\" class=\\\"moves\\\"></div></div></section> <section class=\\\"panel\\\" style=\\\"margin-top:14px\\\"><div class=\\\"title\\\"><div><h2>Позиции портфеля</h2><span class=\\\"muted\\\">Количество и средняя цена покупки</span></div></div><div class=\\\"table\\\"><table><thead><tr><th>Актив</th><th>Цена</th><th>Капитализация</th><th>Количество</th><th id=\\\"avgHeader\\\">Средняя покупка (USD)</th><th>Вложено</th><th>Сейчас</th><th>P/L</th></tr></thead><tbody id=\\\"rows\\\"></tbody></table></div></section> <section class=\\\"alerts\\\"><div class=\\\"panel\\\"><div class=\\\"title\\\"><h2>Новое оповещение</h2></div><div class=\\\"form\\\"><select id=\\\"aCoin\\\"></select><select id=\\\"aKind\\\"><option value=\\\"price_above\\\">Цена выше</option><option value=\\\"price_below\\\">Цена ниже</option><option value=\\\"cap_above\\\">Капитализация выше</option><option value=\\\"cap_bel\",\"match\":\"<svg id=\\\"chart\\\" class=\\\"chart\\\" viewBox=\\\"0 0 900 270\\\" preserveAspectRatio=\\\"none\\\">\",\"offset\":5120}],\"chartTitle\":[{\"context\":\"tch\\\"><button type=\\\"button\\\" class=\\\"currency-btn\\\" data-currency=\\\"USD\\\">USD</button><button type=\\\"button\\\" class=\\\"currency-btn\\\" data-currency=\\\"RUB\\\">RUB</button></div><form method=\\\"post\\\" action=\\\"/logout\\\"><button class=\\\"btn ghost\\\">Выйти</button></form></div></div> <section class=\\\"grid4\\\"><div class=\\\"card\\\"><span class=\\\"muted\\\">Стоимость портфеля</span><strong id=\\\"total\\\">—</strong><span id=\\\"day\\\" class=\\\"muted\\\">—</span></div><div class=\\\"card\\\"><span class=\\\"muted\\\">Вложено</span><strong id=\\\"invested\\\">—</strong></div><div class=\\\"card\\\"><span class=\\\"muted\\\">Прибыль / убыток</span><strong id=\\\"pnl\\\">—</strong><span id=\\\"pnlp\\\" class=\\\"muted\\\">0%</span></div><div class=\\\"card\\\"><span class=\\\"muted\\\">Обновление рынка</span><strong id=\\\"updated\\\">—</strong><span class=\\\"muted\\\">каждые 5 минут</span></div></section> <section id=\\\"coins\\\" class=\\\"coins\\\"></section><section class=\\\"layout\\\"><div class=\\\"panel\\\"><div class=\\\"title\\\"><div><h2 id=\\\"chartTitle\\\">График цены</h2><span class=\\\"muted\\\">7 дней</span></div><span id=\\\"marketStatus\\\" class=\\\"status\\\">Загрузка</span></div><svg id=\\\"chart\\\" class=\\\"chart\\\" viewBox=\\\"0 0 900 270\\\" preserveAspectRatio=\\\"none\\\"></svg></div><div class=\\\"panel sharp-moves-panel\\\"><div class=\\\"title\\\"><h2>Резкие движения</h2></div><div id=\\\"moves\\\" class=\\\"moves\\\"></div></div></section> <section class=\\\"panel\\\" style=\\\"margin-top:14px\\\"><div class=\\\"title\\\"><div><h2>Позиции портфеля</h2><span class=\\\"muted\\\">Количество и средняя цена покупки</span></div></div><div class=\\\"table\\\"><table><thead><tr><th>Актив</th><th>Цена</th><th>Капитализация</th><th>Количество</th><th id=\\\"avgHeader\\\">Средняя покупка (USD)</th><th>Вложено</th><th>Сейчас</th><th>P/L</th></tr></thead><tbody id=\\\"rows\\\"></tbody></table></div></section> <section class=\\\"alerts\\\"><div class=\\\"panel\\\"><div class=\\\"title\\\"><h2>Новое оповещение</h2></div><div class=\\\"form\\\"><select id=\\\"aCoin\\\"><\",\"match\":\"<h2 id=\\\"chartTitle\\\">\",\"offset\":4985}],\"tvCharts\":[]},\"label\":\"production_template\",\"markers\":{\"accordion_style\":1,\"chart_error_text\":1,\"details_creator\":1,\"loading_text\":2,\"no_data_text\":3,\"tvCharts\":3,\"v1_script\":0,\"v26_script\":1},\"no_data_contexts\":[{\"context\":\"tep=\\\\\\\"any\\\\\\\" value=\\\\\\\"\\\"+inputPrice(h.avg_buy)+\\\"\\\\\\\"></td><td>\\\"+money(cost,0)+\\\"</td><td>\\\"+money(value,2)+\\\"</td><td class=\\\\\\\"\\\"+(p>=0?\\\"good\\\":\\\"bad\\\")+\\\"\\\\\\\">\\\"+money(p,2)+\\\"</td></tr>\\\"}).join(\\\"\\\");document.querySelectorAll(\\\"#rows tr\\\").forEach((tr,i)=>{let id=ids[i];let td=tr.cells[0];td.classList.add(\\\"asset-cell\\\");let img=document.createElement(\\\"img\\\");img.className=\\\"coin-icon table-coin-icon\\\";img.src=\\\"/coin-icon/\\\"+id;img.alt=state.coins[id];img.loading=\\\"lazy\\\";td.prepend(img);let symbol=td.querySelector(\\\"b\\\");if(symbol)symbol.classList.add(\\\"asset-symbol\\\")});totalEl=total;document.getElementById(\\\"total\\\").textContent=money(total,0);invested.textContent=money(inv,0);let p=total-inv;pnl.textContent=money(p,0);pnl.className=p>=0?\\\"good\\\":\\\"bad\\\";pnlp.textContent=(inv?p/inv*100:0).toFixed(2)+\\\"%\\\";day.textContent=(daily>=0?\\\"+\\\":\\\"\\\")+money(daily,2)+\\\" за 24ч\\\";updated.textContent=state.market.updated_at?new Date(state.market.updated_at*1000).toLocaleTimeString(\\\"ru-RU\\\"):\\\"нет данных\\\";marketStatus.textContent=Object.keys(m).length===2?\\\"Рынок обновлён\\\":\\\"Нет данных\\\";marketStatus.className=\\\"status \\\"+(Object.keys(m).length===2?\\\"good\\\":\\\"bad\\\");bindInputs();draw();renderMoves();renderAlerts();aCoin.innerHTML=ids.map(id=>\\\"<option value=\\\\\\\"\\\"+id+\\\"\\\\\\\">\\\"+state.coins[id]+\\\"</option>\\\").join(\\\"\\\")}function bindInputs(){document.querySelectorAll(\\\".qty,.avg\\\").forEach(x=>x.onchange=async()=>{let id=x.dataset.id;let q=document.querySelector(\\\".qty[data-id=\\\\\\\"\\\"+id+\\\"\\\\\\\"]\\\").value;let a=document.querySelector(\\\".avg[data-id=\\\\\\\"\\\"+id+\\\"\\\\\\\"]\\\").value;let avg=currency===\\\"RUB\\\"?Number(a)/fx():Number(a);await fetch(\\\"/api/holding\\\",{method:\\\"POST\\\",headers:{\\\"Content-Type\\\":\\\"application/json\\\"},body:JSON.stringify({coin_id:id,quantity:Number(q),avg_buy:avg})});load()})}function draw(){let c=coin();chartTitle.textContent=(c.name||selected)+\\\" · \\\"+state.coins[selected]+\\\"/\\\"+currency;let a=c.sparkline_7d||[];if(a.length<2){chart.innerHTML=\\\"<text x=\\\\\\\"20\\\\\\\" y=\\\\\\\"40\\\\\\\" fill=\\\\\",\"match\":\"нет данных\",\"offset\":10457},{\"context\":\"d>\\\"+money(value,2)+\\\"</td><td class=\\\\\\\"\\\"+(p>=0?\\\"good\\\":\\\"bad\\\")+\\\"\\\\\\\">\\\"+money(p,2)+\\\"</td></tr>\\\"}).join(\\\"\\\");document.querySelectorAll(\\\"#rows tr\\\").forEach((tr,i)=>{let id=ids[i];let td=tr.cells[0];td.classList.add(\\\"asset-cell\\\");let img=document.createElement(\\\"img\\\");img.className=\\\"coin-icon table-coin-icon\\\";img.src=\\\"/coin-icon/\\\"+id;img.alt=state.coins[id];img.loading=\\\"lazy\\\";td.prepend(img);let symbol=td.querySelector(\\\"b\\\");if(symbol)symbol.classList.add(\\\"asset-symbol\\\")});totalEl=total;document.getElementById(\\\"total\\\").textContent=money(total,0);invested.textContent=money(inv,0);let p=total-inv;pnl.textContent=money(p,0);pnl.className=p>=0?\\\"good\\\":\\\"bad\\\";pnlp.textContent=(inv?p/inv*100:0).toFixed(2)+\\\"%\\\";day.textContent=(daily>=0?\\\"+\\\":\\\"\\\")+money(daily,2)+\\\" за 24ч\\\";updated.textContent=state.market.updated_at?new Date(state.market.updated_at*1000).toLocaleTimeString(\\\"ru-RU\\\"):\\\"нет данных\\\";marketStatus.textContent=Object.keys(m).length===2?\\\"Рынок обновлён\\\":\\\"Нет данных\\\";marketStatus.className=\\\"status \\\"+(Object.keys(m).length===2?\\\"good\\\":\\\"bad\\\");bindInputs();draw();renderMoves();renderAlerts();aCoin.innerHTML=ids.map(id=>\\\"<option value=\\\\\\\"\\\"+id+\\\"\\\\\\\">\\\"+state.coins[id]+\\\"</option>\\\").join(\\\"\\\")}function bindInputs(){document.querySelectorAll(\\\".qty,.avg\\\").forEach(x=>x.onchange=async()=>{let id=x.dataset.id;let q=document.querySelector(\\\".qty[data-id=\\\\\\\"\\\"+id+\\\"\\\\\\\"]\\\").value;let a=document.querySelector(\\\".avg[data-id=\\\\\\\"\\\"+id+\\\"\\\\\\\"]\\\").value;let avg=currency===\\\"RUB\\\"?Number(a)/fx():Number(a);await fetch(\\\"/api/holding\\\",{method:\\\"POST\\\",headers:{\\\"Content-Type\\\":\\\"application/json\\\"},body:JSON.stringify({coin_id:id,quantity:Number(q),avg_buy:avg})});load()})}function draw(){let c=coin();chartTitle.textContent=(c.name||selected)+\\\" · \\\"+state.coins[selected]+\\\"/\\\"+currency;let a=c.sparkline_7d||[];if(a.length<2){chart.innerHTML=\\\"<text x=\\\\\\\"20\\\\\\\" y=\\\\\\\"40\\\\\\\" fill=\\\\\\\"#8fa5b6\\\\\\\">История ещё загружается</text>\\\";return}let min=Math.min(...a),max=Math\",\"match\":\"Нет данных\",\"offset\":10538},{\"context\":\"ls=[\\\"HBAR\\\",\\\"POL\\\",\\\"AVAX\\\",\\\"LINK\\\"]; const charts=new Map(); const candleCache=new Map(); const afterLayout=()=>new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve))); const loadCandles=async symbol=>{ if(candleCache.has(symbol))return candleCache.get(symbol); const task=(async()=>{ const response=await fetch(\\\"/api/klines/\\\"+encodeURIComponent(symbol)+\\\"?limit=1000&interval=1M\\\",{cache:\\\"no-store\\\",credentials:\\\"same-origin\\\"}); if(response.status===401){location=\\\"/login\\\";throw new Error(\\\"Требуется повторный вход\\\")} if(!response.ok)throw new Error(\\\"HTTP \\\"+response.status); const data=await response.json(); const candles=Array.isArray(data.candles)?data.candles:(data.result&&Array.isArray(data.result.candles)?data.result.candles:[]); if(data.status&&data.status!==\\\"OK\\\")throw new Error(String(data.error||data.message||data.status)); if(!candles.length)throw new Error(\\\"Нет данных для \\\"+symbol); return candles; })(); candleCache.set(symbol,task); try{return await task}catch(error){candleCache.delete(symbol);throw error} }; const widthOf=box=>Math.max(320,Math.floor(box.getBoundingClientRect().width||box.clientWidth||900)); const ensureChart=card=>{ if(card.dataset.chartState===\\\"ready\\\")return Promise.resolve(charts.get(card.dataset.symbol)); if(card.dataset.chartState===\\\"loading\\\"&&card._chartPromise)return card._chartPromise; const symbol=card.dataset.symbol; const box=card.querySelector(\\\".tv-widget-slot\\\"); const errorBox=card.querySelector(\\\".tv-chart-error\\\"); card.dataset.chartState=\\\"loading\\\"; errorBox.textContent=\\\"Загрузка...\\\"; card._chartPromise=(async()=>{ try{ const candles=await loadCandles(symbol); await afterLayout(); if(!card.open){card.dataset.chartState=\\\"idle\\\";return null} const api=LightweightCharts.createCha\",\"match\":\"Нет данных\",\"offset\":25676}],\"scripts\":[{\"async\":false,\"body_bytes\":7045,\"body_sha256\":\"e6afa17d7b94d9da5036d55bf6b8c623ec036ca05e9dea9c789432631c1b6ca7\",\"contains_v26\":false,\"defer\":false,\"id\":null,\"order\":0,\"src\":null},{\"async\":false,\"body_bytes\":4775,\"body_sha256\":\"f30846a0a26517bd6e8303d552aeba19887576c54b4a70678e234a5e1f6e7582\",\"contains_v26\":false,\"defer\":false,\"id\":\"ALERT_CREATE_OVERRIDE_V3\",\"order\":1,\"src\":null},{\"async\":false,\"body_bytes\":4605,\"body_sha256\":\"b6fedba59295891e784ab98cef7690efe81553715d35a9cf6a31c30d08efb5a9\",\"contains_v26\":false,\"defer\":false,\"id\":\"ALERT_LIST_RENDERER_V5\",\"order\":2,\"src\":null},{\"async\":false,\"body_bytes\":0,\"body_sha256\":null,\"contains_v26\":false,\"defer\":false,\"id\":null,\"order\":3,\"src\":\"/vendor/lightweight-charts.js\"},{\"async\":false,\"body_bytes\":6016,\"body_sha256\":\"084f5447f052e941f4eef6784b1431399a6dd17b28dba277fe310fca903224d9\",\"contains_v26\":true,\"defer\":false,\"id\":\"LOCAL_TRADINGVIEW_CHARTS_V2_LAZY_ACCORDION\",\"order\":4,\"src\":null},{\"async\":false,\"body_bytes\":642,\"body_sha256\":\"90a765a83fb8a5a0542dee1abf8c32af0f248aa411aa203848f73758bc3f9b40\",\"contains_v26\":false,\"defer\":false,\"id\":\"MOVE_CHARTS_TO_BOTTOM_ALLTIME_V1\",\"order\":5,\"src\":null}],\"sha256\":\"db9a1605bd37fca7d6ff651921dd3bc0ccac991f2e8f221179c4dddc3b2c3536\"}},\"rollback_restored\":null,\"rollback_started\":false,\"schema\":1,\"selected_method\":\"ALIAS_PINNED_AS_DEBIAN\",\"ssh_method_probes\":[{\"duration_seconds\":0.132,\"identity_count\":null,\"identity_sudo_read_ok\":true,\"name\":\"ALIAS_PINNED_AS_DEBIAN\",\"rc\":0,\"stderr\":\"\"},{\"duration_seconds\":0.136,\"identity_count\":2,\"identity_sudo_read_ok\":true,\"name\":\"DIRECT_PINNED_AS_DEBIAN_NO_PROXY\",\"rc\":0,\"stderr\":\"\"}],\"status\":\"OK\"}\n"
}
