PageSourceSearch

https://snich.ai/assets/SettingsIntegrationsPage-44c81599.js

js snich.ai collected 2026-10-05 15:07:54 UTC 59,836 bytes, 1 lines download raw bytes

1import{r as g,j as e,b as qe,u as Qe,a as Ze,k as es}from"./vendor-react-72e4a099.js";import{u as ae,a as Ne,B as j,S as Be,i as ge,F as ee,P as we,I as xe,d as ss,C as ts}from"./Toast-43ee7788.js";import{b as ns,C as De,a as Oe,T as as}from"./Display-48adcc24.js";import{r as ve}from"./buildLifecycle-a44b0e05.js";import{useWorkspaceRole as is,SettingsHead as rs}from"./SettingsLayout-14516db3.js";import{a as $,e as X}from"./api-d8a57490.js";import{S as W,N as A,I as ue,a as K,H as ye,f as Ge,L as ls}from"./ui-99f3cce6.js";import{C as se,S as os,a as cs}from"./Controls-5a18fd37.js";import{T as q}from"./triangle-alert-cdc85d96.js";import{P as Se}from"./plus-39793e81.js";import{E as Te}from"./external-link-63c5a0ce.js";import{L as He}from"./lock-73755c1f.js";import{T as ds}from"./trash-2-7a739e24.js";import{R as fe}from"./refresh-cw-98362cea.js";import{C as ze}from"./circle-check-63b159d4.js";import{K as us}from"./key-round-327e86e8.js";import{S as ms}from"./send-ef7332d3.js";import{C as hs}from"./check-083433f7.js";import"./createLucideIcon-581cd729.js";import"./eye-5711d0dd.js";import"./x-b9f3d093.js";import"./circle-alert-67975319.js";import"./CabinetContext-31fc5321.js";/* empty css               *//* empty css                 */import"./user-2d8f405f.js";import"./video-a08888e9.js";import"./palette-a7aa7952.js";import"./layout-template-dfe5c440.js";import"./plug-1e9df63e.js";import"./credit-card-a31de76a.js";import"./chevron-down-888c3b30.js";import"./copy-34f4069e.js";const Ve=[{key:"youtube",name:"YouTube",glyph:"YT",category:"platforms",categoryLabel:"Площадка",description:"Трансляция на канал: эфир на YouTube создаётся сам при выходе в эфир",cta:"Подключить"},{key:"vk",name:"VK Видео",glyph:"VK",category:"platforms",categoryLabel:"Площадка",description:"Трансляция в сообщество или профиль по RTMP-ключу",cta:"Подключить"},{key:"telegram",name:"Telegram",glyph:"TG",category:"platforms",categoryLabel:"Площадка",description:"Трансляция в канал или группу по RTMP-ключу",cta:"Подключить"},{key:"rtmp",name:"Свой сервер",glyph:"RTMP",category:"platforms",categoryLabel:"Площадка · RTMP",description:"Rutube, Twitch или любой сервер по адресу и ключу",cta:"Добавить"},{key:"google_calendar",name:"Google Календарь",glyph:"GC",category:"calendars",categoryLabel:"Календарь",description:"Эфиры и встречи пространства в Google Календаре по ссылке-подписке",cta:"Подключить"},{key:"yandex_calendar",name:"Яндекс Календарь",glyph:"ЯК",category:"calendars",categoryLabel:"Календарь",description:"Эфиры и встречи в календаре команды",cta:"Подключить"},{key:"bitrix24",name:"Битрикс24",glyph:"Б24",category:"data",categoryLabel:"Выгрузка регистраций",description:"Зарегистрированные зрители попадают в CRM как лиды",cta:"Подключить"},{key:"amocrm",name:"amoCRM",glyph:"AMO",category:"data",categoryLabel:"Выгрузка регистраций",description:"Регистрации и посещения — в сделки воронки",cta:"Подключить"},{key:"webhook",name:"Webhooks",glyph:"WH",category:"data",categoryLabel:"Для разработчиков",description:"Регистрация, начало и конец эфира, итоги готовы",cta:"Добавить"}];function ps(s){return Ve.find(a=>a.key===s)}const xs=["youtube","vk","telegram","rtmp"];function me(s,a){switch(s){case"youtube":return a.filter(n=>n.provider==="youtube");case"vk":return a.filter(n=>n.provider==="vk_video");case"telegram":return a.filter(n=>{var t;return n.provider==="custom_rtmp"&&((t=n.providerConfig)==null?void 0:t.platform)==="telegram"});case"rtmp":return a.filter(n=>{var t;return n.provider!=="youtube"&&n.provider!=="vk_video"&&n.provider!=="snich_live"&&!(n.provider==="custom_rtmp"&&((t=n.providerConfig)==null?void 0:t.platform)==="telegram")});default:return[]}}const ke=new Map;function gs(s){const a=s?ke.get(s):void 0,[n,t]=g.useState(a||null),[d,c]=g.useState(null),h=g.useCallback(async()=>
1{if(s){c(null);try{const[u,x,i]=await Promise.all([$.get("/api/v1/streams/destinations",{query:{workspaceId:s}}),$.get(`/workspaces/${s}/integrations`),$.get("/api/v1/streams/integrations/youtube/status").catch(()=>({configured:!1}))]),m={destinations:Array.isArray(u)?u:[],connections:Array.isArray(x)?x:[],youtube:i||{configured:!1}};ke.set(s,m),t(m)}catch(u){c(X(u,"Не удалось загрузить интеграции"))}}},[s]);return g.useEffect(()=>{t(s&&ke.get(s)||null),h()},[h,s]),{destinations:(n==null?void 0:n.destinations)||null,connections:(n==null?void 0:n.connections)||null,youtube:(n==null?void 0:n.youtube)||null,error:d,reload:h,loading:!n}}function ys(s,a,n){var c,h,u,x,i;if(xs.includes(s.key)){const m=me(s.key,a);if(!m.length)return{connected:!1,tone:"gray",label:"Не подключено",detail:null};const o=m.find(C=>C.enabled)||m[0];let l;return s.key==="youtube"&&((c=o.providerConfig)!=null&&c.nativeApiConnected)?l=((h=o.providerConfig)==null?void 0:h.youtubeAutoCreate)===!1?`Канал «${((u=o.providerConfig)==null?void 0:u.channelTitle)||"YouTube"}» · эфир создаёте в YouTube Studio`:`Канал «${((x=o.providerConfig)==null?void 0:x.channelTitle)||"YouTube"}» · эфир создаётся сам при выходе в эфир`:s.key==="rtmp"?l=m.length===1?`${o.displayName} · по адресу и ключу`:`${m.length} ${m.length<5?"сервера":"серверов"}: ${m.map(C=>C.displayName).join(", ")}`:l=`${o.displayName} · по ключу трансляции`,m.every(C=>!C.enabled)?{connected:!0,tone:"gray",label:"Выключено",detail:l}:{connected:!0,tone:"green",label:"Подключено",detail:l}}const t=n.find(m=>m.provider===s.key);if(!t)return{connected:!1,tone:"gray",label:"Не подключено",detail:null};if(t.status==="error")return{connected:!0,tone:"red",label:"Ошибка",detail:pe(t.lastError)};if(t.status!=="connected")return{connected:!0,tone:"gray",label:"Отключено",detail:null};let d=null;return(s.key==="google_calendar"||s.key==="yandex_calendar")&&(d="Эфиры и встречи появляются в календаре по ссылке-подписке"),s.key==="bitrix24"&&(d=t.enabledEvents.includes("registration.synced")?"Новые регистрации уходят в CRM как лиды":"Подключено — выгрузка регистраций выключена"),s.key==="amocrm"&&(d=t.enabledEvents.includes("registration.synced")?"Новые регистрации уходят в воронку":"Подключено — выгрузка регистраций выключена"),s.key==="webhook"&&(d=`${((i=t.maskedConfig)==null?void 0:i.url)||"Адрес сохранён"} · ${t.enabledEvents.length} ${t.enabledEvents.length===1?"событие":t.enabledEvents.length<5?"события":"событий"}`),{connected:!0,tone:"green",label:"Подключено",detail:d}}function te(s){const a=Array.isArray(s==null?void 0:s.warnings)?s.warnings:[],n={destination_endpoint_missing:"Не указан адрес сервера",destination_stream_key_missing:"Не указан ключ трансляции",unsupported_output_profile:"Площадка не принимает такой формат видео"};return a.filter(t=>t.severity==="error").map(t=>n[t.code]||"Площадка может не принять поток — проверьте адрес и ключ")}function B(s,a="Не удалось сохранить"){const n=s==null?void 0:s.status,t=X(s,a),d=[[/RTMP\/RTMPS server is required/i,"Укажите адрес сервера"],[/Stream key is required/i,"Укажите ключ трансляции"],[/Invalid destination endpoint URL/i,"Адрес сервера выглядит неверно: он начинается с rtmp:// или rtmps://"],[/Only RTMP\/RTMPS/i,"Нужен адрес вида rtmp://… или rtmps://…"],[/Local publish endpoints|Private network|private network address/i,"Адрес ведёт во внутреннюю сеть — укажите публичный сервер площадки"],[/cannot be resolved/i,"Сервер с таким адресом не найден — проверьте адрес"],[/does not match endpoint scheme/i,"Протокол не совпадает с адресом сервера"]];for(const[c,h]of d)if(c.test(t))return h;return n===403?"Недостаточно прав: интеграции настраивают владелец и администраторы пространства":/^[\x20-\x7E]+$/.test(t)?a:t}function pe(s){const a=(s||"").trim();return a?/fetch failed|ENOTFOUND|EAI_AGAIN|ECONNREFUSED|ETIMEDOUT|timeout|aborted/i.test(a)?"Сервис не ответил — проверьте адрес и доступность":a:"Не удалось связаться с сервисом"}const $e={youtube:{provider:"youtube",name:"YouTube",endpoint:"rtmp://a.rtmp.youtube.com/live2",placeholder:"rtmp://a.rtmp.youtube.com/live2",howtoTitle:"Возьмите ключ в YouTube Studio",howto:[e.jsxs(e.Fragment,{children:["Откройте ",e.jsx("b",{children:"YouTube Studio"})," → ",e.jsx("b",{children:"Создать"})," → ",e.jsx("b",{children:"Начать трансляцию"}),"."]}),e.jsxs(e.Fragment,{children:["На вкладке «Трансляция» скопируйте ",e.jsx("b",{children:"Ключ трансляции"}),". Адрес сервера мы уже подставили."]})],link:{href:"https://studio.youtube.com/",label:"Открыть YouTube Studio"}},vk:{provider:"vk_video",name:"VK Видео",endpoint:"",placeholder:"rtmp://ovsu.okcdn.ru/input/",howtoTitle:"Возьмите адрес и ключ в VK Видео",howto:[e.jsxs(e.Fragment,{children:["В VK Видео откройте сообщество или профиль → ",e.jsx("b",{children:"Создать"})," → ",e.jsx("b",{children:"Трансляция"}),"."]}),e.jsxs(e.Fragment,{children:["Выберите «Программа для трансляций» и скопируйте ",e.jsx("b",{children:"URL"})," и ",e.jsx("b",{children:"Ключ потока"}),"."]})],link:{href:"https://vkvideo.ru/",label:"Открыть VK Видео"}},telegram:{provider:"custom_rtmp",name:"Telegram",endpoint:"",placeholder:"rtmps://dc4-1.rtmp.t.me/s/",providerConfig:{platform:"telegram"},howtoTitle:"Возьмите адрес и ключ в Telegram",howto:[e.jsxs(e.Fragment,{children:["В канале или группе откройте ",e.jsx("b",{children:"Трансляция"})," (видеочат) → ",e.jsx("b",{children:"Начать с помощью…"})]}),e.jsxs(e.Fragment,{children:["Скопируйте ",e.jsx("b",{children:"URL сервера"})," и ",e.jsx("b",{children:"Ключ трансляции"}),". Сам эфир в Telegram начнётся, когда SNICH выйдет в эфир."]})]},rutube:{provider:"rutube",name:"RUTUBE",endpoint:"",placeholder:"rtmp://…/live_push",howtoTitle:"Возьмите адрес и ключ в RUTUBE Студии",howto:[e.jsxs(e.Fragment,{children:["В ",e.jsx("b",{children:"RUTUBE Студии"})," откройте ",e.jsx("b",{children:"Трансляции"})," → ",e.jsx("b",{children:"Создать трансляцию"}),"."]}),e.jsxs(e.Fragment,{children:["Скопируйте ",e.jsx("b",{children:"URL-адрес сервера"})," и ",e.jsx("b",{children:"Ключ трансляции"}),"."]})],link:{href:"https://studio.rutube.ru/",label:"Открыть RUTUBE Студию"}}
1,twitch:{provider:"twitch",name:"Twitch",endpoint:"rtmp://live.twitch.tv/app",placeholder:"rtmp://live.twitch.tv/app",howtoTitle:"Возьмите ключ в Twitch",howto:[e.jsxs(e.Fragment,{children:["Откройте ",e.jsx("b",{children:"Панель управления автора"})," → ",e.jsx("b",{children:"Настройки"})," → ",e.jsx("b",{children:"Трансляция"}),"."]}),e.jsxs(e.Fragment,{children:["Скопируйте ",e.jsx("b",{children:"Основной ключ трансляции"}),". Адрес сервера мы уже подставили."]})],link:{href:"https://dashboard.twitch.tv/settings/stream",label:"Открыть настройки Twitch"}},custom:{provider:"custom_rtmp",name:"Свой сервер",endpoint:"",placeholder:"rtmp://stream.example.com/live",howtoTitle:"Адрес и ключ сервера",howto:[e.jsx(e.Fragment,{children:"Подойдёт любой сервер, который принимает RTMP или RTMPS: адрес и ключ даёт ваш сервис трансляций."})]}};function ne(s,a){return{displayName:(s==null?void 0:s.displayName)||a.name,endpointUrl:s?"":a.endpoint,streamKey:"",enabled:(s==null?void 0:s.enabled)??!0}}function We(s,a){return a?s.displayName.trim()!==a.displayName||!!s.endpointUrl.trim()||!!s.streamKey.trim()||s.enabled!==a.enabled:!!(s.endpointUrl.trim()||s.streamKey.trim())}async function Ee(s,a,n,t){let d;if(!a)d=await $.post("/api/v1/streams/destinations",{workspaceId:s,provider:n.provider,displayName:t.displayName.trim()||n.name,endpointUrl:t.endpointUrl.trim(),streamKey:t.streamKey.trim(),providerConfig:n.providerConfig||{},enabled:!0});else{const h={};t.displayName.trim()&&t.displayName.trim()!==a.displayName&&(h.displayName=t.displayName.trim()),t.enabled!==a.enabled&&(h.enabled=t.enabled),t.endpointUrl.trim()&&(h.endpointUrl=t.endpointUrl.trim()),t.streamKey.trim()&&(h.streamKey=t.streamKey.trim()),d=Object.keys(h).length?await $.patch(`/api/v1/streams/destinations/${a.id}`,h):a}let c=d.lastTestReport;return d.enabled&&(c=await $.post(`/api/v1/streams/destinations/${d.id}/test`,{}).catch(()=>d.lastTestReport)),{destination:d,report:c}}function Re({form:s,setForm:a,dest:n,platform:t,disabled:d,idPrefix:c,readOnly:h}){const u=h?"":" Впишите новый, чтобы заменить.";return e.jsxs(e.Fragment,{children:[e.jsx(ee,{label:"Название в SNICH",htmlFor:`${c}-name`,hint:"Так площадка будет называться при выборе в эфире",children:e.jsx(xe,{id:`${c}-name`,value:s.displayName,maxLength:120,disabled:d,onChange:x=>a(i=>({...i,displayName:x.target.value}))})}),e.jsx(ee,{label:"Адрес сервера",htmlFor:`${c}-url`,hint:n?e.jsxs(e.Fragment,{children:["Сохранён: ",e.jsx("span",{className:"sn-mono",children:n.endpointMasked||"—"}),".",u]}):"Начинается с rtmp:// или rtmps://",children:e.jsx(xe,{id:`${c}-url`,value:s.endpointUrl,placeholder:(n==null?void 0:n.endpointMasked)||t.placeholder,spellCheck:!1,autoComplete:"off",inputMode:"url",disabled:d,onChange:x=>a(i=>({...i,endpointUrl:x.target.value}))})}),e.jsx(ee,{label:"Ключ трансляции",htmlFor:`${c}-key`,hint:n!=null&&n.hasStreamKeySecret?`Ключ сохранён и зашифрован — мы его не показываем.${u}`:"Ключ хранится зашифрованным, его видит только сервер трансляций",children:e.jsx(we,{id:`${c}-key`,value:s.streamKey,placeholder:n!=null&&n.hasStreamKeySecret?"••••••••••••":"xxxx-xxxx-xxxx-xxxx",autoComplete:"off",spellCheck:!1,disabled:d,onChange:x=>a(i=>({...i,streamKey:x.target.value}))})})]})}function fs({dest:s,report:a,checking:n,onCheck:t,canManage:d}){if(!s)return e.jsx("p",{className:"sn-a-muted",children:"Проверим адрес, ключ и формат видео сразу после сохранения. Само соединение с площадкой начнётся при выходе в эфир."});if(!s.enabled)return e.jsx(A,{icon:e.jsx(Ne,{className:"sn-i sn-i-16"}),title:"Площадка выключена",children:"Её нельзя выбрать для эфира. Ключ и адрес сохранены."});const c=a?te(a):[],h=(a==null?void 0:a.checkedAt)||s.lastTestedAt;return e.jsxs(e.Fragment,{children:[!a&&e.jsx("p",{className:"sn-a-muted",children:"Проверки ещё не было."}),a&&c.length===0&&e.jsxs(A,{tone:"green",icon:e.jsx(ze,{className:"sn-i sn-i-16"}),title:"Готово к эфиру",children:["Адрес, ключ и формат видео подходят площадке.",h?` Проверено ${Ge(h)}.`:""]}),a&&c.length>0&&e.jsxs(A,{tone:"red",icon:e.jsx(q,{className:"sn-i sn-i-16"}),title:"Площадка не примет поток",children:[c.join(". "),"."]}),d&&e.jsx("div",{children:e.jsx(j,{variant:"quiet",size:"sm",icon:e.jsx(fe,{className:"sn-i sn-i-16"}),loading:n,onClick:t,children:"Проверить ещё раз"})})]})}function Fe({item:s,platformKey:a,workspaceId:n,canManage:t,list:d,onClose:c,reload:h,lead:u,subtitleOverride:x}){const i=ae(),m=$e[a],o=d[0],[l,b]=g.useState(()=>ne(o,m)),[C,v]=g.useState(!1),[I,k]=g.useState(!1),[r,f]=g.useState(null),[U,F]=g.useState((o==null?void 0:o.lastTestReport)||null),[Y,N]=g.useState(!1);g.useEffect(()=>{b(ne(o,m)),F((o==null?void 0:o.lastTestReport)||null)},[o==null?void 0:o.id]);const P=E=>{f(null),b(E)},w=We(l,o),_=async()=>
1{if(!o&&(!l.endpointUrl.trim()||!l.streamKey.trim())){f(l.endpointUrl.trim()?"Укажите ключ трансляции":"Укажите адрес сервера");return}v(!0),f(null);try{const E=await Ee(n,o,m,l);F(E.report),await h();const z=E.report?te(E.report):[];z.length?i.error(`${s.name}: ${z[0].toLowerCase()}`):i.success(o?"Изменения сохранены":`${s.name} подключён`),b(V=>({...V,endpointUrl:"",streamKey:""}))}catch(E){f(B(E))}finally{v(!1)}},J=async()=>{if(o){k(!0);try{F(await $.post(`/api/v1/streams/destinations/${o.id}/test`,{})),await h()}catch(E){i.error(B(E,"Не удалось проверить"))}finally{k(!1)}}},D=async()=>{if(o)try{await Promise.all(d.map(E=>$.del(`/api/v1/streams/destinations/${E.id}`))),await h(),i.success(`${s.name} отключён`),N(!1),c()}catch(E){i.error(B(E,"Не удалось отключить"))}},H=U?te(U):[],Q=o?o.enabled?H.length?{tone:"red",label:"Нужно исправить"}:{tone:"green",label:"Подключено"}:{tone:"gray",label:"Выключено"}:null,ie=t?e.jsxs(e.Fragment,{children:[o&&e.jsxs(j,{variant:"text",className:"sn-a-btn-danger-text",onClick:()=>N(!0),disabled:C,children:["Отключить ",s.name]}),e.jsx("span",{className:"sn-a-grow"}),e.jsx(j,{variant:"quiet",onClick:c,disabled:C,children:"Отмена"}),e.jsx(j,{variant:"primary",onClick:_,loading:C,disabled:!w&&!!o,children:o?"Сохранить":"Подключить"})]}):e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"sn-a-grow"}),e.jsx(j,{variant:"quiet",onClick:c,children:"Закрыть"})]});return e.jsxs(ue,{item:s,status:Q,subtitle:x||(o==null?void 0:o.displayName),onClose:c,footer:ie,busy:C,children:[u,!t&&e.jsx(he,{}),e.jsxs(K,{n:1,done:!!o,title:m.howtoTitle,children:[e.jsx(ye,{items:m.howto}),m.link&&e.jsx("div",{children:e.jsx(j,{variant:"quiet",size:"sm",href:m.link.href,target:"_blank",rel:"noreferrer",iconRight:e.jsx(Te,{className:"sn-i sn-i-16"}),children:m.link.label})})]}),e.jsxs(K,{n:2,done:!!o&&!w,title:"Вставьте их сюда",children:[e.jsx(Re,{form:l,setForm:P,dest:o,platform:m,disabled:!t||C,readOnly:!t,idPrefix:`dst-${s.key}`}),o&&e.jsx(ge,{title:"Площадка включена",description:"Выключенную площадку нельзя выбрать для эфира, ключ сохранится",checked:l.enabled,disabled:!t||C,onChange:E=>P(z=>({...z,enabled:E}))}),r&&e.jsx(A,{tone:"red",icon:e.jsx(q,{className:"sn-i sn-i-16"}),children:r})]}),e.jsx(K,{n:3,done:!!(o&&U&&!H.length&&o.enabled),title:"Проверка",children:e.jsx(fs,{dest:o,report:U,checking:I,onCheck:J,canManage:t})}),e.jsx(se,{open:Y,onClose:()=>N(!1),onConfirm:D,title:`Отключить ${s.name}?`,description:"Адрес и ключ удалятся из SNICH, площадку больше нельзя будет выбрать для эфира. Чтобы вернуть, подключите её заново.",confirmLabel:"Отключить"})]})}function he(){return e.jsx(W,{children:e.jsx(A,{icon:e.jsx(He,{className:"sn-i sn-i-16"}),title:"Только просмотр",children:"Подключать и менять интеграции могут владелец и администраторы пространства."})})}function js(s){return e.jsx(Fe,{item:s.item,platformKey:"vk",workspaceId:s.workspaceId,canManage:s.canManage,list:me("vk",s.destinations),onClose:s.onClose,reload:s.reload})}function bs(s){return e.jsx(Fe,{item:s.item,platformKey:"telegram",workspaceId:s.workspaceId,canManage:s.canManage,list:me("telegram",s.destinations),onClose:s.onClose,reload:s.reload})}const vs="rtmp://a.rtmp.youtube.com/live2",de=s=>{var a;return((a=s==null?void 0:s.providerConfig)==null?void 0:a.youtubeAutoCreate)!==!1},Ce=s=>{var a;return((a=s==null?void 0:s.providerConfig)==null?void 0:a.privacyStatus)==="public"?"public":"unlisted"};function ks({dest:s}){var d;const[a,n]=g.useState(!1),t=(d=s.providerConfig)==null?void 0:d.channelThumbn
1ailUrl;return t&&!a?e.jsx("img",{className:"sn-a-yt-thumb",src:t,alt:"",referrerPolicy:"no-referrer",onError:()=>n(!0)}):e.jsx("span",{className:"sn-a-glyph",style:{width:32,height:32,borderRadius:"50%",fontSize:11},"aria-hidden":"true",children:"YT"})}function Cs(s){var Ue,Ae,Pe,Ie,Ye,Ke,_e;const{item:a,workspaceId:n,canManage:t,youtube:d,onClose:c,reload:h}=s,u=ae(),[x,i]=qe(),m=me("youtube",s.destinations),o=m.filter(y=>{var L;return(L=y.providerConfig)==null?void 0:L.nativeApiConnected}).sort((y,L)=>String(y.createdAt||"").localeCompare(String(L.createdAt||""))),l=o.find(y=>y.enabled)||o[0],b=m.find(y=>{var L;return!((L=y.providerConfig)!=null&&L.nativeApiConnected)}),[C,v]=g.useState(()=>l||d.configured&&!b?"oauth":"key"),[I,k]=g.useState((l==null?void 0:l.id)||null),r=o.find(y=>y.id===I)||l,[f,U]=g.useState(Ce(r)),[F,Y]=g.useState(de(r)),[N,P]=g.useState(""),[w,_]=g.useState(null),[J,D]=g.useState(!1),[H,Q]=g.useState(!1),[ie,E]=g.useState(!1),[z,V]=g.useState(null);g.useEffect(()=>{k((l==null?void 0:l.id)||null)},[l==null?void 0:l.id]),g.useEffect(()=>{U(Ce(r)),Y(de(r)),P(""),_(null)},[r==null?void 0:r.id,(Ue=r==null?void 0:r.providerConfig)==null?void 0:Ue.privacyStatus,(Ae=r==null?void 0:r.providerConfig)==null?void 0:Ae.youtubeAutoCreate]),g.useEffect(()=>{const y=x.get("streamDestinationStatus");if(!y)return;y==="connected"?(u.success("YouTube подключён"),h()):V(x.get("streamDestinationMessage")||"Google не подтвердил вход");const L=new URLSearchParams(x);["streamDestinationStatus","streamDestinationProvider","streamDestinationId","streamDestinationMessage"].forEach(R=>L.delete(R)),i(L,{replace:!0})},[]);const re=async y=>{V(null),D(!0);const L=y?r==null?void 0:r.id:void 0;let R=null;try{R=window.open("","snich-youtube-oauth","width=520,height=700");const O=await $.post("/api/v1/streams/integrations/youtube/oauth/start",{workspaceId:n,privacyStatus:f,responseMode:R?"popup":"redirect",returnTo:"/settings/integrations/youtube",title:"YouTube"});if(!O.configured||!O.authUrl){R==null||R.close(),V("Вход через Google на этом сервере не настроен. Подключите канал по ключу трансляции."),v("key"),D(!1);return}if(!R){window.location.assign(O.authUrl);return}R.location.href=O.authUrl;const Z=async ce=>{const M=ce.data;ce.origin!==window.location.origin||(M==null?void 0:M.type)!=="snich-stream-destination-oauth"||(window.removeEventListener("message",Z),window.clearInterval(oe),D(!1),M.status==="connected"?(L&&M.destinationId&&M.destinationId!==L&&await $.del(`/api/v1/streams/destinations/${L}`).catch(()=>{}),await h(),M.destinationId&&k(M.destinationId),u.success("YouTube подключён")):V(M.message||"Google не подтвердил вход"))};window.addEventListener("message",Z);const oe=window.setInterval(()=>{R&&R.closed&&(window.clearInterval(oe),window.setTimeout(()=>{window.removeEventListener("message",Z),D(!1)},800))},400)}catch(O){R==null||R.close(),V(B(O,"Не удалось начать вход через Google")),D(!1)}},je=async()=>{var L;if(!r)return;const y=N.trim();if(!F&&de(r)&&!y){_("Вставьте ключ трансляции из YouTube Studio");return}Q(!0);try{const R=r.id!==(l==null?void 0:l.id)||!r.enabled;await $.patch(`/api/v1/streams/destinations/${r.id}`,{...R?{enabled:!0}:{},providerConfig:{privacyStatus:f,youtubeAutoCreate:F},...!F&&y?{protocol:"rtmp",endpointUrl:vs,streamKey:y}:{}}),R&&await Promise.all(o.filter(O=>O.id!==r.id&&O.enabled).map(O=>$.patch(`/api/v1/streams/destinations/${O.id}`,{enabled:!1}))),P(""),await h(),u.success(R?`Эфиры пойдут на канал «${((L=r.providerConfig)==null?void 0:L.channelTitle)||"YouTube"}»`:"Настройки YouTube сохранены")}catch(R){u.error(B(R))}finally{Q(!1)}},le=async()=>{try{await Promise.all(m.map(y=>$.del(`/api/v1/streams/destinations/${y.id}`))),await h(),u.success("YouTube отключён"),E(!1),c()}catch(y){u.error(B(y,"Не удалось отключить"))}};if(C==="key"&&!l){const y=d.configured?e.jsx(W,{children:e.jsxs("div",{className:"sn-a-inline-actions",children:[e.jsx("span",{className:"sn-a-muted",style:{flex:1},children:"Можно не возиться с ключом: войдите через Google, и эфир на YouTube будет создаваться сам."}),e.jsx(j,{variant:"text",size:"sm",onClick:()=>v("oauth"),children:"Войти через Google"})]})}):e.jsx(W,{children:e.jsx(A,{tone:"amber",icon:e.jsx(Ne,{className:"sn-i sn-i-16"}),title:"Вход через Google — требуется настройка администратором",children:"Пока администратор SNICH не подключил Google, канал добавляется по ключу трансляции. Эфир на YouTube тогда создаёте вы сами в YouTube Studio."})});return e.jsx(Fe,{item:a,platformKey:"youtube",workspaceId:n,canManage:t,list:b?[b]:[],onClose:c,reload:h,lead:y})}const be=((Pe=l==null?void 0:l.providerConfig)==null?void 0:Pe.channelTitle)||"YouTube",p=((Ie=r==null?void 0:r.providerConfig)==null?void 0:Ie.accountEmail)||((Ye=r==null?void 0:r.providerConfig)==null?void 0:Ye.accountLogin)||"",T=((Ke=r==null?void 0:r.providerConfig)==null?void 0:Ke.accountDisplayName)||((_e=r==null?void 0:r.providerConfig)==null?void 0:_e.channelTitle)||"YouTube",S=!!r&&((r==null?void 0:r.id)!==(l==null?void 0:l.id)||f!==Ce(r)||F!==de(r)||!!N.trim()),G=l?l.enabled?{tone:"green",label:"Подключено"}:{tone:"gray",label:"Выключено"}
1:null,Le=!!r&&!de(r),Xe=t?e.jsxs(e.Fragment,{children:[l&&e.jsx(j,{variant:"text",className:"sn-a-btn-danger-text",onClick:()=>E(!0),children:"Отключить YouTube"}),e.jsx("span",{className:"sn-a-grow"}),e.jsx(j,{variant:"quiet",onClick:c,children:l?"Отмена":"Закрыть"}),l&&e.jsx(j,{variant:"primary",onClick:je,loading:H,disabled:!S,children:"Сохранить"})]}):e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"sn-a-grow"}),e.jsx(j,{variant:"quiet",onClick:c,children:"Закрыть"})]});return e.jsxs(ue,{item:a,status:G,subtitle:l?`канал «${be}»`:void 0,onClose:c,footer:Xe,children:[!t&&e.jsx(he,{}),e.jsxs(K,{n:1,done:!!l,title:"Войти в аккаунт Google",children:[l?e.jsxs("div",{className:"sn-a-kv",children:[e.jsx(ns,{name:T,size:32}),e.jsxs("span",{className:"sn-a-kv-text",children:[e.jsx("b",{children:p||T}),e.jsx("span",{children:"Вход выполнен"})]}),t&&e.jsx(j,{variant:"text",size:"sm",onClick:()=>re(!0),loading:J,children:"Сменить"})]}):e.jsxs(e.Fragment,{children:[e.jsx("p",{className:"sn-a-muted",children:"Разрешите SNICH создавать эфиры на вашем канале. Пароль от Google мы не видим."}),t&&e.jsxs("div",{className:"sn-a-inline-actions",children:[e.jsx(j,{variant:"primary",onClick:()=>re(!1),loading:J,children:"Войти через Google"}),e.jsx(j,{variant:"text",onClick:()=>v("key"),children:"Подключить по ключу"})]})]}),z&&e.jsx(A,{tone:"red",icon:e.jsx(q,{className:"sn-i sn-i-16"}),children:z})]}),e.jsx(K,{n:2,done:!!l,title:"Выбрать канал",children:l?e.jsxs("div",{className:"sn-a-yt-channels",role:"radiogroup","aria-label":"Канал YouTube",children:[o.map(y=>{var Z,oe,ce,M;const L=y.id===(r==null?void 0:r.id),O=[(Z=y.providerConfig)!=null&&Z.accountLogin&&y.providerConfig.accountLogin!==((oe=y.providerConfig)==null?void 0:oe.accountEmail)?y.providerConfig.accountLogin:"",(ce=y.providerConfig)==null?void 0:ce.accountEmail].filter(Boolean).join(" · ")||"Канал аккаунта Google";return e.jsxs("label",{className:`sn-a-kv sn-a-kv-radio${L?" is-selected":""}`,children:[e.jsx("input",{type:"radio",name:"yt-channel",checked:L,disabled:!t,onChange:()=>k(y.id)}),e.jsx(ks,{dest:y}),e.jsxs("span",{className:"sn-a-kv-text",children:[e.jsx("b",{children:((M=y.providerConfig)==null?void 0:M.channelTitle)||"YouTube"}),e.jsx("span",{children:O})]})]},y.id)}),t&&e.jsxs("button",{type:"button",className:"sn-a-kv sn-a-kv-add",onClick:()=>re(!1),disabled:J,children:[e.jsx("span",{className:"sn-a-kv-add-icon","aria-hidden":"true",children:e.jsx(Se,{className:"sn-i sn-i-16"})}),e.jsxs("span",{className:"sn-a-kv-text",children:[e.jsx("b",{children:"Другой канал"}),e.jsx("span",{children:"Войдите в Google и выберите канал или бренд-аккаунт"})]})]})]}):e.jsx("p",{className:"sn-a-muted",children:"После входа Google предложит выбрать канал — личный или бренд-аккаунт."})}),e.jsxs(K,{n:3,title:"Настройки по умолчанию",children:[F&&e.jsxs("div",{className:"sn-field",children:[e.jsx("span",{className:"sn-label",id:"yt-access",children:"Доступ к эфиру на YouTube"}),e.jsx(Be,{ariaLabel:"Доступ к эфиру на YouTube",value:f,onChange:y=>U(y),options:[{value:"public",label:"Открытый"},{value:"unlisted",label:"По ссылке"}]})]}),e.jsx(ge,{title:"Создавать эфир на YouTube автоматически",description:F?"Название и описание возьмём из события":"Эфир создаёте сами в YouTube Studio — SNICH отправит видео на его ключ трансляции",checked:F,disabled:!t||!l,onChange:y=>{Y(y),_(null)}}),!F&&l&&e.jsx(ee,{label:"Ключ трансляции из YouTube Studio",htmlFor:"yt-studio-key",error:w,hint:Le?"Ключ сохранён и зашифрован. Вставьте новый, если сменили его в YouTube Studio.":"YouTube Studio → «Начать трансляцию» → «Ключ трансляции». Сервер подставим сами.",children:e.jsx(we,{id:"yt-studio-key",value:N,placeholder:Le?"••••••••••••":"xxxx-xxxx-xxxx-xxxx-xxxx",autoComplete:"off",spellCheck:!1,disabled:!t,onChange:y=>{P(y.target.value),_(null)}})})]}),e.jsx(se,{open:ie,onClose:()=>E(!1),onConfirm:le,title:"Отключить YouTube?",description:"SNICH забудет доступ к каналу и ключи трансляции. Уже прошедшие эфиры на YouTube останутся.",confirmLabel:"Отключить"})]})}const Ns=[{value:"rutube",label:"RUTUBE"},{value:"twitch",label:"Twitch"},{value:"custom",label:"Другой сервер (RTMP)"}];function ws(s){return s==="rutube"?"RT":s==="twitch"?"TW":"RTMP"}function Ss({dest:s,canManage:a,reload:n}){const t=ae(),d=$e[s.provider==="rutube"?"rutube":s.provider==="twitch"?"twitch":"custom"],[c,h]=g.useState(!1),[u,x]=g.useState(()=>ne(s,d)),[i,m]=g.useState(null),[o,l]=g.useState(null),[b,C]=g.useState(!1),v=s.lastTestReport?te(s.lastTestReport):[],I=N=>{l(null),x(N)},k=async()=>{m("save");try{await Ee(s.workspaceId||"",s,d,u),await n(),h(!1),t.success("Сервер сохранён")}catch(N){l(B(N))}finally{m(null)}},r=async()=>{m("check");try{const N=await $.post(`/api/v1/streams/destinations/${s.id}/test`,{});await n();const P=te(N);P.length?t.error(P[0]):t.success(`${s.displayName}: готово к эфиру`)}catch(N){t.error(B(N,"Не удалось проверить"))}finally{m(null)}},f=async N=>{m("toggle");try{await $.patch(`/api/v1/streams/destinations/${s.id}`,{enabled:N}),await n()}catch(P){t.error(B(P))}finally{m(null)}},U=async()=>{try{await $.del(`/api/v1/streams/destinations/${s.id}`),await n(),t.success(`${s.displayName} удалён`),C(!1)}catch(N){t.error(B(N,"Не удалось удалить"))}},F=s.enabled?v.length?"sn-a-dot sn-a-dot-red":"sn-a-dot sn-a-dot-green":"sn-a-dot sn-a-dot-gray",Y=s.enabled?v.length?v[0]:s.lastTestReport?"Готов к эфиру":"Ещё не проверяли":"Выключен";return e.jsxs("div",{className:"sn-a-srv",children:[e.jsxs("div",{className:"sn-a-srv-top",children:[e.jsx("span",{className:"sn-a-glyph",style:{width:36,height:36,borderRadius:10,fontSize:11},"aria-hidden":"true",children:ws(s.provider)}),e.jsxs("span",{className:"sn-a-kv-text",children:[e.jsx("b",{children:s.displayName}),e.jsx("span",{className:"sn-mono",title:s.endpointMasked||"",children:s.endpointMasked||"Адрес не указан"})]}),a&&e.jsx(ss,{size:"sm",variant:"ghost",label:`Удалить ${s.displayName}`,onClick:()=>C(!0),children:e.jsx(ds,{className:"sn-i"})})]}),e.jsxs("div",{className:"sn-a-inline-actions",children:[e.jsx("span",{className:F,"aria-hidden":"true"}),e.jsx("span",{className:"sn-a-muted",style:{flex:1},children:Y})]}),a&&!c&&e.jsxs("div",{className:"sn-a-srv-actions",children:[e.jsx(j,{variant:"quiet",size:"sm",onClick:()=>h(!0),children:"Изменить"}),e.jsx(j,{variant:"quiet",size:"sm",icon:e.jsx(fe,{className:"sn-i sn-i-16"}),loading:i==="check",disabled:!s.enabled,onClick:r,children:"Проверить"}),e.jsx("span",{className:"sn-a-grow",style:{flex:1}}),e.jsx(ge,{title:s.enabled?"Включён":"Выключен",checked:s.enabled,disabled:i!==null,onChange:f})]}),c&&e.jsxs(e.Fragment,{children:[e.jsx(Re,{form:u,setForm:I,dest:s,platform:d,disabled:i==="save",idPrefix:`srv-${s.id}`}),o&&e.jsx(A,{tone:"red",icon:e.jsx(q,{className:"sn-i sn-i-16"}),children:o}),e.jsxs("div",{className:"sn-a-inline-actions",children:[e.jsx(j,{variant:"primary",size:"sm",onClick:k,loading:i==="save",disabled:!We(u,s),children:"Сохранить"}),e.jsx(j,{variant:"quiet",size:"sm",onClick:()=>{h(!1),x(ne(s,d)),l(null)},disabled:i==="save",children:"Отмена"})]})]}),e.jsx(se,{open:b,onClose:()=>C(!1),onConfirm:U,title:`Удалить «${s.displayName}»?`,description:"Адрес и ключ удалятся из SNICH, сервер больше нельзя будет выбрать для эфира.",confirmLabel:"Удалить"})]})}function Ts(s){const{item:a,workspaceId:n,canManage:t,onClose:d,reload:c}=s,h=ae(),u=me("rtmp",s.destinations),[x,i]=g.useState("rutube"),m=$e[x],[o,l]=g.useState(u.length===0),[b,C]=g.useState(()=>ne(void 0,m)),[v,I]=g.useState(!1),[k,r]=g.useState(null);g.useEffect(()=>{C(w=>({...w,displayName:m.name,endpointUrl:m.endpoint}))},[x]);const f=w=>{r(null),C(w)},U=async()=>
1{if(!b.endpointUrl.trim()||!b.streamKey.trim()){r(b.endpointUrl.trim()?"Укажите ключ трансляции":"Укажите адрес сервера");return}I(!0);try{const w=await Ee(n,void 0,m,b);await c();const _=w.report?te(w.report):[];_.length?h.error(`${w.destination.displayName}: ${_[0].toLowerCase()}`):h.success(`${w.destination.displayName} добавлен`),l(!1),C(ne(void 0,m))}catch(w){r(B(w))}finally{I(!1)}},F=u.filter(w=>w.enabled).length,Y=u.length?{tone:F?"green":"gray",label:F?"Подключено":"Выключено"}:null,N=u.length?`${u.length} ${u.length===1?"сервер":u.length<5?"сервера":"серверов"}`:void 0,P=e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"sn-a-grow"}),t&&o&&u.length>0&&e.jsx(j,{variant:"quiet",onClick:()=>l(!1),disabled:v,children:"Отмена"}),t&&o?e.jsx(j,{variant:"primary",onClick:U,loading:v,children:"Добавить сервер"}):e.jsx(j,{variant:"quiet",onClick:d,children:"Готово"})]});return e.jsxs(ue,{item:a,status:Y,subtitle:N,onClose:d,footer:P,busy:v,children:[!t&&e.jsx(he,{}),u.length>0&&e.jsx(W,{title:"Серверы пространства",aside:t&&!o?e.jsx(j,{variant:"soft",size:"sm",icon:e.jsx(Se,{className:"sn-i sn-i-16"}),onClick:()=>l(!0),children:"Добавить"}):void 0,children:u.map(w=>e.jsx(Ss,{dest:w,canManage:t,reload:c},w.id))}),t&&o&&e.jsxs(e.Fragment,{children:[e.jsxs(K,{n:1,title:"Куда транслировать",children:[e.jsx(os,{ariaLabel:"Площадка",value:x,onChange:w=>i(w),options:Ns.map(w=>({value:w.value,label:w.label}))}),e.jsx(ye,{items:m.howto}),m.link&&e.jsx("div",{children:e.jsx(j,{variant:"quiet",size:"sm",href:m.link.href,target:"_blank",rel:"noreferrer",iconRight:e.jsx(Te,{className:"sn-i sn-i-16"}),children:m.link.label})})]}),e.jsxs(K,{n:2,title:"Адрес и ключ",children:[e.jsx(Re,{form:b,setForm:f,platform:m,disabled:v,idPrefix:"srv-new"}),k&&e.jsx(A,{tone:"red",icon:e.jsx(q,{className:"sn-i sn-i-16"}),children:k})]}),e.jsx(K,{n:3,title:"Проверка",children:e.jsx("p",{className:"sn-a-muted",children:"Проверим адрес, ключ и формат видео сразу после добавления. Соединение с сервером начнётся при выходе в эфир."})})]}),!t&&u.length===0&&e.jsx(W,{children:e.jsx("p",{className:"sn-a-muted",children:"Своих серверов пока нет."})})]})}function Je(s){return s?s.status==="connected"?{tone:"green",label:"Подключено"}:s.status==="error"?{tone:"red",label:"Ошибка"}:{tone:"gray",label:"Отключено"}:null}function $s(s,a,n){const[t,d]=g.useState(n?null:[]),c=g.useCallback(async()=>{if(!n){d([]);return}try{const h=await $.get(`/workspaces/${s}/integrations/${a}/logs`);d(Array.isArray(h)?h.slice(0,6):[])}catch{d([])}},[s,a,n]);return g.useEffect(()=>{c()},[c]),{entries:t,reloadLogs:c}}function Es(s){if(!s)return!1;try{const a=new URL(s).hostname;return a==="localhost"||a==="127.0.0.1"||a.endsWith(".local")}catch{return!1}}const Rs={google_calendar:{items:[e.jsx(e.Fragment,{children:"Откройте Google Календарь на компьютере."}),e.jsxs(e.Fragment,{children:["Слева у «Других календарей» нажмите ",e.jsx("b",{children:"+"})," → ",e.jsx("b",{children:"Добавить по URL"}),"."]}),e.jsxs(e.Fragment,{children:["Вставьте ссылку и нажмите ",e.jsx("b",{children:"Добавить календарь"}),"."]})],link:{href:"https://calendar.google.com/calendar/r/settings/addbyurl",label:"Открыть Google Календарь"},refresh:"Google обновляет подписки сам, обычно в течение нескольких часов."},yandex_calendar:{items:[e.jsx(e.Fragment,{children:"Откройте Яндекс Календарь."}),e.jsxs(e.Fragment,{children:["В списке календарей слева нажмите ",e.jsx("b",{children:"Добавить"})," → ",e.jsx("b",{children:"Подписаться на календарь"}),"."]}),e.jsxs(e.Fragment,{children:["Вставьте ссылку, задайте название и нажмите ",e.jsx("b",{children:"Создать"}),"."]})],link:{href:"https://calendar.yandex.ru/",label:"Открыть Яндекс Календарь"},refresh:"Яндекс обновляет подписку сам, обычно раз в час."}};
1function Fs({item:s,workspaceId:a,canManage:n,connections:t,onClose:d,reload:c}){const h=ae(),u=t.find(k=>k.provider===s.key),x=(u==null?void 0:u.status)==="connected"&&u.feedUrl||null,i=Rs[s.key],[m,o]=g.useState(null),[l,b]=g.useState(null),C=async(k=!1)=>{o(k?"rotate":"create");try{const r=await $.post(`/workspaces/${a}/integrations`,{provider:s.key,config:k?{rotate:"1"}:{},enabledEvents:[]});await c(),r.status==="connected"?h.success(k?"Новая ссылка готова, старая больше не работает":"Ссылка-подписка готова"):h.error(r.lastError||"Не удалось создать ссылку"),b(null)}catch(r){h.error(X(r,"Не удалось создать ссылку"))}finally{o(null)}},v=async()=>{try{await $.del(`/workspaces/${a}/integrations/${s.key}`),await c(),h.success(`${s.name} отключён — ссылка больше не работает`),b(null),d()}catch(k){h.error(X(k,"Не удалось отключить"))}},I=e.jsxs(e.Fragment,{children:[n&&u&&e.jsx(j,{variant:"text",className:"sn-a-btn-danger-text",onClick:()=>b("off"),children:"Отключить"}),e.jsx("span",{className:"sn-a-grow"}),e.jsx(j,{variant:x?"primary":"quiet",onClick:d,children:x?"Готово":"Закрыть"})]});return e.jsxs(ue,{item:s,status:Je(u),subtitle:x?"по ссылке-подписке":void 0,onClose:d,footer:I,busy:m!==null,children:[!n&&e.jsx(he,{}),e.jsx(K,{n:1,done:!!x,title:"Ссылка на календарь пространства",children:x?e.jsxs(e.Fragment,{children:[e.jsx(cs,{value:x,message:"Ссылка скопирована"}),e.jsx("p",{className:"sn-a-muted",children:"В календаре появятся запланированные эфиры и встречи пространства: за два месяца назад и на год вперёд, со ссылкой на вход."}),Es(x)&&e.jsx(A,{tone:"amber",icon:e.jsx(q,{className:"sn-i sn-i-16"}),title:"Сервер SNICH доступен только с этого компьютера",children:"Календарь в интернете не сможет забрать ссылку, пока сервер не опубликован."})]}):e.jsxs(e.Fragment,{children:[e.jsxs("p",{className:"sn-a-muted",children:["SNICH сделает секретную ссылку-подписку. Календарь сам будет забирать по ней эфиры и встречи — без доступа к вашему аккаунту ",s.key==="google_calendar"?"Google":"Яндекса","."]}),(u==null?void 0:u.status)==="error"&&u.lastError&&e.jsx(A,{tone:"red",icon:e.jsx(q,{className:"sn-i sn-i-16"}),children:u.lastError}),n&&e.jsx("div",{children:e.jsx(j,{variant:"primary",onClick:()=>C(!1),loading:m==="create",children:"Создать ссылку"})})]})}),e.jsxs(K,{n:2,title:`Добавьте её в ${s.key==="google_calendar"?"Google Календарь":"Яндекс Календарь"}`,children:[e.jsx(ye,{items:i.items}),e.jsx("div",{children:e.jsx(j,{variant:"quiet",size:"sm",href:i.link.href,target:"_blank",rel:"noreferrer",iconRight:e.jsx(Te,{className:"sn-i sn-i-16"}),children:i.link.label})}),e.jsx("p",{className:"sn-a-muted",children:i.refresh})]}),x&&e.jsxs(W,{title:"Безопасность",children:[e.jsx(A,{icon:e.jsx(us,{className:"sn-i sn-i-16"}),children:"По ссылке видны названия и время всех эфиров и встреч пространства. Если она попала не туда — смените её, старая сразу перестанет работать."}),n&&e.jsx("div",{children:e.jsx(j,{variant:"quiet",size:"sm",icon:e.jsx(fe,{className:"sn-i sn-i-16"}),onClick:()=>b("rotate"),children:"Сменить ссылку"})})]}),e.jsx(se,{open:l==="rotate",onClose:()=>b(null),onConfirm:()=>C(!0),title:"Сменить ссылку?",description:"Старая ссылка перестанет работать. Новую нужно будет заново добавить в календарь.",confirmLabel:"Сменить",tone:"primary"}),e.jsx(se,{open:l==="off",onClose:()=>b(null),onConfirm:v,title:`Отключить ${s.name}?`,description:"Ссылка-подписка перестанет работать, эфиры пропадут из календаря при следующем обновлении.",confirmLabel:"Отключить"})]})}const Ls={bitrix24:{howtoTitle:"Создайте входящий вебхук в Битрикс24",howto:[e.jsxs(e.Fragment,{children:["В Битрикс24 откройте ",e.jsx("b",{children:"Разработчикам"})," → ",e.jsx("b",{children:"Другое"})," → ",e.jsx("b",{children:"Входящий вебхук"}),"."]}),e.jsxs(e.Fragment,{children:["В правах доступа отметьте ",e.jsx("b",{children:"CRM (crm)"})," и сохраните."]}),e.jsxs(e.Fragment,{children:["Скопируйте ",e.jsx("b",{children:"Вебхук для вызова rest api"}),"."]})],fields:[{key:"webhookUrl",label:"Адрес вебхука",secret:!0,required:!0,placeholder:"https://portal.bitrix24.ru/rest/1/xxxxxxxx/"},{key:"responsibleId",label:"ID ответственного за лиды",secret:!1,required:!1,placeholder:"1",hint:"Необязательно. Без него ответственным станет владелец вебхука."}],syncTitle:"Новые регистрации → лиды",syncDescription:"Имя, email, эфир и UTM-метки зрителя появятся в CRM через минуту после регистрации"},amocrm:{howtoTitle:"Возьмите токен в amoCRM",howto:[e.jsxs(e.Fragment,{children:["В amoCRM откройте ",e.jsx("b",{children:"amoМаркет"})," → ",e.jsx("b",{children:"⋯"})," → ",e.jsx("b",{children:"Создать интеграцию"})," → ",e.jsx("b",{children:"Внешняя интеграция"}),"."]}),e.jsx(e.Fragment,{children:"Дайте доступ к сделкам и контактам, сохраните."}),e.jsxs(e.Fragment,{children:["На вкладке ",e.jsx("b",{children:"Ключи и доступы"})," сгенерируйте ",e.jsx("b",{children:"Долгосрочный токен"})," и скопируйте его."]})],fields:[{key:"baseUrl",label:"Адрес аккаунта",secret:!1,required:!0,placeholder:"https://company.amocrm.ru"},{key:"accessToken",label:"Долгосрочный токен",secret:!0,required:!0,placeholder:"eyJ0eXAiOiJKV1Qi…"},{key:"responsibleUserId",label:"ID ответственного",secret:!1,required:!1,placeholder:"1234567",hint:"Необязательно. Иначе сделку получит владелец токена."}],syncTitle:"Новые регистрации → сделки",syncDescription:"Зритель станет контактом, регистрация — сделкой в первой воронке"},webhook:{howtoTitle:"Куда отправлять",howto:[e.jsxs(e.Fragment,{children:["SNICH отправит ",e.jsx("b",{children:"POST"})," с JSON ",e.jsx("span",{className:"sn-mono",children:"{ id, event, occurredAt, data }"})," на ваш адрес."]}),e.jsx(e.Fragment,{children:"Ответ 2xx — событие доставлено. Каждое событие отправляется один раз, результат виден в журнале ниже."})],fields:[{key:"url",label:"Адрес (URL)",secret:!0,required:!0,placeholder:"https://example.com/snich-hook"}]}},Us=[{value:"registration.created",label:"Новая регистрация",description:"Зритель зарегистрировался на эфир"},{value:"event.started",label:"Эфир начался",description:"Эфир или встреча вышли в эфир"},{value:"event.ended",label:"Эфир закончился",description:"Эфир или встреча завершились"},{value:"summary.ready",label:"Итоги готовы",description:"Готов конспект и задачи по встрече"},{value:"recording.ready",label:"Запись готова",description:"Запись обработана и доступна"}];function As(){const s=new Uint8Array(24);return crypto.getRandomValues(s),Array.from(s,a=>a.toString(16).padStart(2,"0")).join("")}
1function Ps({item:s,workspaceId:a,canManage:n,connections:t,onClose:d,reload:c}){const h=ae(),u=Ls[s.key],x=s.key==="webhook",i=t.find(p=>p.provider===s.key),m=(i==null?void 0:i.maskedConfig)||{},o=g.useMemo(()=>{const p={};return u.fields.forEach(T=>{p[T.key]=T.secret?"":m[T.key]||""}),x&&(p.signingSecret=""),p},[i==null?void 0:i.id,i==null?void 0:i.updatedAt]),l=g.useMemo(()=>i?i.enabledEvents:x?["registration.created","event.started","event.ended"]:["registration.synced"],[i==null?void 0:i.id,i==null?void 0:i.updatedAt]),[b,C]=g.useState(o),[v,I]=g.useState(l),[k,r]=g.useState(!1),[f,U]=g.useState(!1),[F,Y]=g.useState(null),[N,P]=g.useState(null),[w,_]=g.useState(!1),{entries:J,reloadLogs:D}=$s(a,s.key,!!i);g.useEffect(()=>{C(o)},[o]),g.useEffect(()=>{I(l)},[l]);const H=(p,T)=>{Y(null),C(S=>({...S,[p]:T}))},Q=[...v].sort().join()!==[...l].sort().join(),ie=Object.keys(b).some(p=>b[p]!==o[p]),E=!i||Q||ie,z=async()=>{const p=u.fields.find(S=>{var G;return S.required&&!((G=b[S.key])!=null&&G.trim())&&!(i&&S.secret&&m[S.key])});if(p){Y(`Заполните поле «${p.label}»`);return}if(x&&!v.length){Y("Выберите хотя бы одно событие");return}const T={};u.fields.forEach(S=>{const G=(b[S.key]||"").trim();
1S.secret?G&&(T[S.key]=G):T[S.key]=G}),x&&b.signingSecret.trim()&&(T.signingSecret=b.signingSecret.trim()),r(!0),Y(null);try{const S=await $.post(`/workspaces/${a}/integrations`,{provider:s.key,displayName:s.name,config:T,enabledEvents:v});await c(),await D(),S.status==="connected"?(h.success(i?"Изменения сохранены":`${s.name} подключён`),P(null)):Y(pe(S.lastError))}catch(S){Y(X(S,"Не удалось сохранить"))}finally{r(!1)}},V=async()=>{U(!0);try{const p=await $.post(`/workspaces/${a}/integrations/${s.key}/test`,{});P(p),await c(),await D()}catch(p){P({success:!1,message:X(p,"Не удалось проверить")})}finally{U(!1)}},re=async()=>{try{await $.del(`/workspaces/${a}/integrations/${s.key}`),await c(),h.success(`${s.name} отключён`),_(!1),d()}catch(p){h.error(X(p,"Не удалось отключить"))}},je=n?e.jsxs(e.Fragment,{children:[i&&e.jsxs(j,{variant:"text",className:"sn-a-btn-danger-text",onClick:()=>_(!0),disabled:k,children:["Отключить ",s.name]}),e.jsx("span",{className:"sn-a-grow"}),e.jsx(j,{variant:"quiet",onClick:d,disabled:k,children:"Отмена"}),e.jsx(j,{variant:"primary",onClick:z,loading:k,disabled:!E,children:i?"Сохранить":"Подключить"})]}):e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"sn-a-grow"}),e.jsx(j,{variant:"quiet",onClick:d,children:"Закрыть"})]});let le=0;const be=x?i?m.url:void 0:(i==null?void 0:i.status)==="connected"?i.enabledEvents.includes("registration.synced")?"регистрации выгружаются":"выгрузка выключена":void 0;return e.jsxs(ue,{item:s,status:Je(i),subtitle:be,onClose:d,footer:je,busy:k,children:[!n&&e.jsx(he,{}),e.jsxs(K,{n:++le,done:(i==null?void 0:i.status)==="connected",title:u.howtoTitle,children:[e.jsx(ye,{items:u.howto}),u.fields.map(p=>{const T=!!(i&&p.secret&&m[p.key]);return e.jsx(ee,{label:p.label,htmlFor:`cfg-${p.key}`,hint:T?e.jsxs(e.Fragment,{children:["Сохранён: ",e.jsx("span",{className:"sn-mono",children:m[p.key]}),".",n?" Впишите новый, чтобы заменить.":""]}):p.hint,children:p.secret&&p.key!=="webhookUrl"&&p.key!=="url"?e.jsx(we,{id:`cfg-${p.key}`,value:b[p.key]||"",placeholder:T?"••••••••••••":p.placeholder,autoComplete:"off",spellCheck:!1,disabled:!n||k,onChange:S=>H(p.key,S.target.value)}):e.jsx(xe,{id:`cfg-${p.key}`,value:b[p.key]||"",placeholder:T?m[p.key]:p.placeholder,autoComplete:"off",spellCheck:!1,inputMode:/url/i.test(p.key)?"url":void 0,disabled:!n||k,onChange:S=>H(p.key,S.target.value)})},p.key)})]}),x&&e.jsxs(K,{n:++le,title:"Подпись запросов",children:[e.jsx(ee,{label:"Секрет подписи",htmlFor:"cfg-signingSecret",hint:i&&m.signingSecret?`Секрет сохранён.${n?" Впишите или сгенерируйте новый, чтобы заменить.":""}`:"Необязательно, но лучше задать: так вы отличите запросы SNICH от чужих.",children:e.jsxs("div",{className:"sn-a-inline-actions",style:{flexWrap:"nowrap"},children:[e.jsx(xe,{id:"cfg-signingSecret",className:"sn-mono",style:{flex:1,minWidth:0},value:b.signingSecret||"",placeholder:i&&m.signingSecret?"••••••••••••":"Секрет для HMAC-подписи",autoComplete:"off",spellCheck:!1,disabled:!n||k,onChange:p=>H("signingSecret",p.target.value)}),n&&e.jsx(j,{variant:"quiet",onClick:()=>H("signingSecret",As()),disabled:k,children:"Сгенерировать"})]})}),e.jsxs("p",{className:"sn-a-muted",children:["Заголовок ",e.jsx("span",{className:"sn-mono",children:"X-Snich-Signature: sha256=…"})," — это HMAC-SHA256 секрета от строки ",e.jsx("span",{className:"sn-mono",children:"«X-Snich-Timestamp.тело»"}),".",n?" Секрет виден только до сохранения — скопируйте его в свой сервис.":""]})]}),e.jsxs(K,{n:++le,title:x?"События":"Что выгружать",children:[x?e.jsx("div",{className:"sn-a-events",children:Us.map(p=>e.jsx(ts,{checked:v.includes(p.value),disabled:!n||k,onChange:T=>I(S=>T?[...S,p.value]:S.filter(G=>G!==p.value)),label:e.jsxs(e.Fragment,{children:[p.label," ",e.jsx("span",{className:"sn-mono sn-a-muted",children:p.value})]}),description:p.description},p.value))}
1):e.jsx(ge,{title:u.syncTitle,description:u.syncDescription,checked:v.includes("registration.synced"),disabled:!n||k,onChange:p=>I(T=>p?Array.from(new Set([...T,"registration.synced"])):T.filter(S=>S!=="registration.synced"))}),F&&e.jsx(A,{tone:"red",icon:e.jsx(q,{className:"sn-i sn-i-16"}),children:F})]}),i&&e.jsxs(W,{title:"Связь и журнал",aside:e.jsx(j,{variant:"quiet",size:"sm",icon:x?e.jsx(ms,{className:"sn-i sn-i-16"}):e.jsx(fe,{className:"sn-i sn-i-16"}),loading:f,onClick:V,children:x?"Тестовый запрос":"Проверить связь"}),children:[N?e.jsx(A,{tone:N.success?"green":"red",icon:N.success?e.jsx(ze,{className:"sn-i sn-i-16"}):e.jsx(q,{className:"sn-i sn-i-16"}),children:N.success?N.message:pe(N.message)}):i.status==="error"?e.jsx(A,{tone:"red",icon:e.jsx(q,{className:"sn-i sn-i-16"}),title:"Последняя проверка не прошла",children:pe(i.lastError)}):i.lastSyncAt?e.jsxs("p",{className:"sn-a-muted",children:["Последний успешный обмен: ",Ge(i.lastSyncAt),"."]}):null,e.jsx(ls,{entries:J,empty:"Событий пока не было."})]}),!i&&!x&&e.jsx(W,{children:e.jsx(A,{icon:e.jsx(Ne,{className:"sn-i sn-i-16"}),children:"Выгружаются только новые регистрации — те, что появятся после подключения."})}),e.jsx(se,{open:w,onClose:()=>_(!1),onConfirm:re,title:`Отключить ${s.name}?`,description:x?"SNICH перестанет отправлять события на этот адрес.":"Новые регистрации перестанут попадать в CRM. То, что уже выгружено, останется в CRM.",confirmLabel:"Отключить"})]})}const Me=[{value:"all",label:"Все"},{value:"platforms",label:"Площадки"},{value:"calendars",label:"Календари"},{value:"data",label:"Данные"}];function Is({item:s,destinations:a,connections:n,canManage:t,to:d}){const c=ys(s,a,n);return e.jsxs(De,{className:"sn-a-int-card",children:[e.jsxs("div",{className:"sn-a-int-top",children:[e.jsx("span",{className:"sn-a-glyph","aria-hidden":"true",children:s.glyph}),e.jsxs("span",{style:{minWidth:0},children:[e.jsx("b",{children:s.name}),e.jsx("small",{children:s.categoryLabel})]})]}),e.jsx("p",{className:c.tone==="red"?"sn-a-int-desc is-error":"sn-a-int-desc",children:c.detail||s.description}),e.jsxs("div",{className:"sn-a-int-foot",children:[c.connected?e.jsx(as,{tone:c.tone,icon:c.tone==="green"?e.jsx(hs,{className:"sn-i sn-i-14"}):c.tone==="red"?e.jsx(q,{className:"sn-i sn-i-14"}):void 0,children:c.label}):e.jsx("span",{className:"sn-a-muted",children:c.label}),e.jsx("span",{style:{flex:1}}),c.connected||!t?e.jsx(j,{variant:"quiet",size:"sm",to:d,"aria-label":`${t?"Настроить":"Открыть"} ${s.name}`,children:t?"Настроить":"Открыть"}):e.jsx(j,{variant:"soft",size:"sm",to:d,icon:e.jsx(Se,{className:"sn-i sn-i-16"}),"aria-label":`${s.cta} ${s.name}`,children:s.cta})]})]})}function Ys(){const{provider:s}=Qe(),a=Ze(),[n,t]=qe(),{workspace:d,canManage:c}=is(),{destinations:h,connections:u,youtube:x,error:i,reload:m,loading:o}=gs(d==null?void 0:d.id),l=Me.some(f=>f.value===n.get("type"))?n.get("type"):"all",b=l==="all"?"":`?type=${l}`,C=f=>{const U=new URLSearchParams(n);f==="all"?U.delete("type"):U.set("type",f),t(U,{replace:!0})},v=ps(s);if(s&&!v)return e.jsx(es,{to:ve.settingsIntegrations(),replace:!0});const I=()=>a(`${ve.settingsIntegrations()}${b}`),k=Ve.filter(f=>l==="all"||f.category===l);let r=null;if(v&&d&&h&&u&&x){const f={item:v,workspaceId:d.id,canManage:c,onClose:I,reload:m};switch(v.key){case"youtube":r=e.jsx(Cs,{...f,destinations:h,youtube:x},v.key);break;case"vk":r=e.jsx(js,{...f,destinations:h,youtube:x},v.key);break;case"telegram":r=e.jsx(bs,{...f,destinations:h,youtube:x},v.key);break;case"rtmp":r=e.jsx(Ts,{...f,destinations:h,youtube:x},v.key);break;case"google_calendar":case"yandex_calendar":r=e.jsx(Fs,{...f,connections:u},v.key);break;default:r=e.jsx(Ps,{...f,connections:u},v.key)}}return e.jsxs(e.Fragment,{children:[e.jsx(rs,{title:"Интеграции",subtitle:"Площадки для трансляций, календари и выгрузка регистраций",actions:e.jsx(Be,{ariaLabel:"Тип интеграции",value:l,onCh
1ange:C,options:Me})}),!c&&e.jsx(A,{icon:e.jsx(He,{className:"sn-i sn-i-16"}),children:"Подключать и менять интеграции могут владелец и администраторы пространства."}),i&&e.jsxs(A,{tone:"red",icon:e.jsx(q,{className:"sn-i sn-i-16"}),title:"Не удалось загрузить интеграции",children:[i," ",e.jsx(j,{variant:"text",size:"xs",onClick:()=>void m(),children:"Повторить"})]}),e.jsx("div",{className:"sn-a-int-grid",children:o||!h||!u?k.map(f=>e.jsxs(De,{className:"sn-a-int-card","aria-busy":"true",children:[e.jsxs("div",{className:"sn-a-int-top",children:[e.jsx("span",{className:"sn-a-glyph","aria-hidden":"true",children:f.glyph}),e.jsxs("span",{style:{minWidth:0},children:[e.jsx("b",{children:f.name}),e.jsx("small",{children:f.categoryLabel})]})]}),e.jsx(Oe,{height:14,width:"90%"}),e.jsx(Oe,{height:14,width:"60%"})]},f.key)):k.map(f=>e.jsx(Is,{item:f,destinations:h,connections:u,canManage:c,to:`${ve.settingsIntegration(f.key)}${b}`},f.key))}),r]})}function yt(){return e.jsx(Ys,{})}export{Ys as IntegrationsScreen,yt as default};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.