PageSourceSearch

https://e0f.cx/assets/AdminProxiesPage-BHxhvOGe.js

js e0f.cx collected 2026-09-24 14:59:21 UTC 117,513 bytes, 8 lines download raw bytes

1import{R as Ys,r as x,U as he,V as Ke,j as e,S as Y,G as g,B as D,e as Ie,l as T,f as c,_ as y,a5 as ze,a6 as we,Y as me,v as ds,s as d,ah as ce,ai as z,aT as qe,ax as Be,W as Q,ab as r,ac as Pe,$ as _e,n as Js,a as hs,a1 as Re,aC as Xe,aG as $s,d as ns,ad as Ve,aV as ws,aY as vt,aZ as Me,aX as St,aj as Pt,aJ as Qe,aa as os,aD as Gs,a2 as Se,h as Es,aE as Tt,P as Oe,o as Le,T as De,bF as Hs,aK as as,a8 as Bs,a9 as R,t as It,de as zt}from"./index-DJgzD7sm.js";import{D as Ye,S as Ct}from"./ServerNotifyModal-BZxmu4Wn.js";import{H as qs,a as cs}from"./HostingProviderSelect-ECYO7eiT.js";import{I as Ps}from"./IconReload-DdvPCfnl.js";import{C as se}from"./Code-Bus1hUjD.js";import{S as ne}from"./Switch-K_-dcS_2.js";import{I as Ts}from"./IconPlayerPlay-C36Bx94L.js";import{I as es}from"./IconActivity-B2x21SRd.js";import{I as Is}from"./IconFileText-xJxAW6Wx.js";import{I as Je}from"./IconTrash-Dw0tXwO_.js";import{S as J}from"./Select-DYUJAPNs.js";import{N as ae}from"./NumberInput-37uLQK8S.js";import{u as Zs}from"./useHostingProviders-BKCWd1vK.js";import{C as kt,f as Ss}from"./country-codes-CYX_4J-F.js";import{I as Ze}from"./IconEdit-BBcyIY8S.js";import{S as At}from"./SegmentedControl-C3Jsnrqb.js";import{I as $t}from"./IconSearch-BtX1OOjH.js";import{I as Xs}from"./IconDotsVertical-C9EwIjIC.js";import{I as Qs}from"./IconEye-q7UJ_CdM.js";import"./pick-calendar-levels-props-ns0wLwTs.js";import"./clamp-DTmYCdls.js";import"./IconUsersGroup-nKqQqdJD.js";import"./react-number-format.es-hx5beCWS.js";const Et=[["path",{d:"M3 14l11 -11",key:"svg-0"}],["path",{d:"M10 3h4v4",key:"svg-1"}],["path",{d:"M10 17v4h4",key:"svg-2"}],["path",{d:"M21 10l-11 11",key:"svg-3"}]],Os=Ys("outline","arrows-double-ne-sw","ArrowsDoubleNeSw",Et);const Ht=[["path",{d:"M5.633 5.64a9 9 0 1 0 12.735 12.72m1.674 -2.32a9 9 0 0 0 -12.082 -12.082",key:"svg-0"}],["path",{d:"M12 7v1",key:"svg-1"}],["path",{d:"M3 3l18 18",key:"svg-2"}]],rs=Ys("outline","clock-off","ClockOff",Ht),et=/^[0-9a-fA-F]+$/,Bt=/^[A-Za-z0-9_-]+$/;function Ot(a){if(!et.test(a)||a.length%2!==0)return null;const m=new Uint8Array(a.length/2);for(let S=0;S<m.length;S++)m[S]=parseInt(a.substr(S*2,2),16);return m}function Lt(a){const m=Ot(a);if(m)try{return new TextDecoder("utf-8",{fatal:!0}).decode(m)}catch{return}}function Dt(a){if(Bt.test(a))try{const m=a.replace(/-/g,"+").replace(/_/g,"/"),S="=".repeat((4-m.length%4)%4),C=atob(m+S);let E="";for(let v=0;v<C.length;v++)E+=C.charCodeAt(v).toString(16).padStart(2,"0");return E}catch{return}}function Mt(a){return/^[\x20-\x7e]+$/.test(a)&&/\./.test(a)&&a.length<=253}function Ls(a){if(!a)return null;const m=a.trim();if(!m)return null;let S,C,E=m;if(/^(tg:\/\/proxy|https?:\/\/t\.me\/proxy)/i.test(m)){let p=m;p.toLowerCase().startsWith("tg://")&&(p="https://"+p.slice(5));let B;try{B=new URL(p)}catch{return null}S=B.searchParams.get("server")?.trim()||void 0;const k=B.searchParams.get("port")?.trim();if(k&&/^\d+$/.test(k)){const F=Number(k);F>0&&F<65536&&(C=F)}const M=B.searchParams.get("secret")?.trim();if(!M)return null;E=M}let L;if(et.test(E))L=E.toLowerCase();else{const p=Dt(E);if(!p)return null;L=p.toLowerCase()}let W,H;if((L.startsWith("ee")||L.startsWith("dd"))&&L.length>=34){if(W=L.slice(2,34),L.length>34){const p=L.slice(34),B=Lt(p);B&&Mt(B)&&(H=B)}}else if(L.length===32)W=L;else return null;return/^[0-9a-f]{32}$/.test(W)?{secret:W,host:S,port:C,fakeTlsDomain:H}:null}const Nt=180*1e3,Ut=5*1e3,Wt=1500*1e3,Ft=3,Rt=a=>new Promise(m=>setTimeout(m,a)),Vt=a=>{const m=a.trim().split(`
2`).map(S=>S.trim()).filter(Boolean);return m.length?m[m.length-1].slice(0,300):""},Kt=a=>a.status==="pending"||a.status==="error"?!0:a.status==="installing"&&Date.now()-new Date(a.updatedAt).getTime()>Nt;function _t({token:a,exits:m}){const[S,C]=x.useState([]),[E,v]=x.useState(!0),[L,W]=x.useState(!1),[H,p]=x.useState(null),[B,k]=x.useState(null),[M,{open:F,close:te}]=he(!1),[ge,{open:ue,close:xe}]=he(!1),G=Ke({initialValues:{exitServerId:"",host:"",sshHost:"",sshUser:"root",sshPort:22,sshPassword:""},validate:{exitServerId:i=>i?null:"Выберите exit-сервер",host:i=>/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/i.test(i.trim())?null:"Укажите публичный DNS-хост фронта",sshHost:i=>
2i.trim()?null:"Введите SSH-хост",sshPassword:i=>i?null:"Введите SSH-пароль"}}),q=async i=>{try{const o=await i.json();return Array.isArray(o?.message)?o.message.join(", "):o?.message||`HTTP ${i.status}`}catch{return`HTTP ${i.status}`}},V=async()=>{if(a)try{v(!0);const i=await fetch(`${Q}/api/admin/webproxy/fronts`,{headers:{Authorization:`Bearer ${a}`}});if(!i.ok)throw new Error(await q(i));const o=await i.json(),j=Array.isArray(o)?o:[];C(j),k(h=>h&&j.find(A=>A.id===h.id)||null)}catch(i){r.show({title:"Ошибка",message:i?.message||"Не удалось загрузить фронты",color:"red"})}finally{v(!1)}};x.useEffect(()=>{V()},[a]);const je=m.map(i=>({value:i.id,label:`${i.name} · ${i.host}`})),pe=async i=>{try{W(!0);const o=await fetch(`${Q}/api/admin/webproxy/fronts`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({exitServerId:i.exitServerId,host:i.host.trim(),sshHost:i.sshHost.trim(),sshUser:i.sshUser.trim()||void 0,sshPort:Number(i.sshPort),sshPassword:i.sshPassword})});if(!o.ok)throw new Error(await q(o));xe(),G.reset(),r.show({title:"Фронт добавлен",message:"Теперь запустите установку через меню фронта",color:"green"}),await V()}catch(o){r.show({title:"Ошибка добавления",message:o?.message||"Не удалось добавить фронт",color:"red"})}finally{W(!1)}},_=async i=>{const o=Date.now()+Wt;let j=0;for(;Date.now()<o;){await Rt(Ut);try{const h=await fetch(`${Q}/api/admin/webproxy/fronts/${i}/install-status`,{headers:{Authorization:`Bearer ${a}`}});if(!h.ok)throw new Error(await q(h));const A=await h.json();if(A.status==="success"||A.status==="error")return{status:A.status,log:A.log||""};if(j=A.status==="unknown"?j+1:0,j>=Ft)return{status:"unknown",log:""}}catch{}}return{status:"unknown",log:""}},Z=async i=>{if(!H)try{p({frontId:i.id,action:"install"});const o=await fetch(`${Q}/api/admin/webproxy/fronts/${i.id}/install`,{method:"POST",headers:{Authorization:`Bearer ${a}`}});if(!o.ok)throw new Error(await q(o));r.show({title:"Установка запущена",message:"Ставим nginx stream :443 → exit:443. Обычно 3–8 минут. Можно не держать страницу открытой.",color:"blue",autoClose:8e3}),await V();const j=await _(i.id);j.status==="success"?r.show({title:"Фронт установлен",message:"Проверьте DNS фронта и включите тумблер «Активен», чтобы клиенты пошли через double-hop.",color:"green",autoClose:12e3}):j.status==="error"?r.show({title:"Установка не удалась",message:Vt(j.log)||"Подробности в логе фронта",color:"red",autoClose:15e3}):r.show({title:"Статус установки неизвестен",message:"Обновите список — установка могла продолжиться на сервере",color:"yellow"})}catch(o){r.show({title:"Ошибка",message:o?.message||"Операция не выполнена",color:"red"})}finally{await V(),p(o=>o?.frontId===i.id&&o.action==="install"?null:o)}},le=async i=>{if(!H)try{p({frontId:i.id,action:"check"});const o=await fetch(`${Q}/api/admin/webproxy/fronts/${i.id}/status`,{headers:{Authorization:`Bearer ${a}`}});if(!o.ok)throw new Error(await q(o));const j=await o.json();r.show({title:j.ok?"Фронт онлайн":"Фронт офлайн",message:j.lastCheckDetails||"Проверка завершена",color:j.ok?"green":"red"}),await V()}catch(o){r.show({title:"Ошибка проверки",message:o?.message||"Не удалось проверить фронт",color:"red"})}finally{p(o=>o?.frontId===i.id&&o.action==="check"?null:o)}},K=async(i,o)=>{if(!H)try{p({frontId:i.id,action:"toggle"});const j=await fetch(`${Q}/api/admin/webproxy/fronts/${i.id}`,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({isActive:o})});if(!j.ok)throw new Error(await q(j));r.show({title:o?"Фронт включён":"Фронт выключен",message:o?"Клиенты этого exit теперь идут через фронт (double-hop)":"Клиенты вернулись на прямой хост exit",color:o?"green":"gray"}),await V()}catch(j){r.show({title:"Ошибка",message:j?.message||"Не удалось изменить статус",color:"red"}),await V()}finally{p(j=>j?.frontId===i.id&&j.action==="toggle"?null:j)}},fe=async i=>{if(!H&&window.confirm(`Удалить фронт «${i.host}» из панели? Удаление записи не удаляет nginx с VPS.`))try{p({frontId:i.id,action:"delete"});const o=await fetch(`${Q}/api/admin/webproxy/fronts/${i.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${a}`}});if(!o.ok)throw new Error(await q(o));r.show({title:"Удалено",message:"Фронт удалён из панели",color:"green"}),await V()}catch(o){r.show({title:"Ошибка удаления",message:o?.message||"Не удалось удалить фронт",color:"red"})}finally{p(o=>o?.frontId===i.id&&o.action==="delete"?null:o)}},X=i=>i.status==="active"&&i.isActive?e.jsx(T,{color:"green",leftSection:e.jsx(Pe,{size:12}),children:"Активен (double-hop)"}):i.status==="active"?e.jsx(T,{color:"teal",variant:"light",leftSection:e.jsx(Pe,{size:12}),children:"Установлен (выключен)"}):i.status==="installing"?e.jsx(T,{color:"blue",leftSection:e.jsx(_e,{size:12}),children:"Установка"}):i.status==="error"?e.jsx(T,{color:"red",leftSection:e.jsx(Be,{size:12}),children:"Ошибка"}):e.jsx(T,{color:"yellow",leftSection:e.jsx(Js,{size:12}),children:"Ожидает установки"});return e.jsxs(Y,{gap:"md",children:[e.jsxs(g,{justify:"space-between",align:"flex-start",children:[e.jsxs(D,{children:[e.jsxs(g,{gap:"sm",children:[e.jsx(Os,{size:24}),e.jsx(Ie,{order:3,children:"Double-hop фронты"}),e.jsx(T,{color:"grape",variant:"filled",children:"FRONT"})]}),e.jsx(c,{c:"dimmed",size:"sm",mt:4,children:"Фронт — дешёвый RU-достижимый VPS с nginx stream :443 → exit:443. Прячет IP exit-ноды и даёт запасной вход. TLS не терминируется, secret не меняется."})]}),e.jsxs(g,{children:[e.jsx(y,{variant:"light",leftSection:e.jsx(Ps,{size:18}),loading:E,onClick:()=>void V(),children:"Обновить"}),e.jsx(y,{leftSection:e.jsx(ze,{size:18}),onClick:ue,disabled:m.length===0,children:"Добавить фронт"})]})]}),e.jsxs(we,{color:"grape",variant:"light",icon:e.jsx(Os,{size:18}),children:["Клиент → ",e.jsx("b",{children:"фронт"})," (L4 passthrough) → ",e.jsx("b",{children:"exit"})," (Caddy + MTProxy) → Telegram. Как только тумблер «Активен» включён, клиентам этого exit выдаётся хост фронта вместо прямого хоста exit. Включайте только после того, как DNS A-запись фронта начнёт резолвиться."]}
2),e.jsx(me,{withBorder:!0,children:E?e.jsx(c,{c:"dimmed",children:"Загрузка фронтов…"}):S.length===0?e.jsxs(Y,{align:"center",py:"lg",children:[e.jsx(ds,{size:40,color:"var(--mantine-color-dimmed)"}),e.jsx(c,{c:"dimmed",children:"Фронты ещё не добавлены"}),m.length===0?e.jsx(c,{c:"dimmed",size:"sm",children:"Сначала добавьте хотя бы один exit WEB-прокси выше."}):e.jsx(y,{variant:"light",leftSection:e.jsx(ze,{size:16}),onClick:ue,children:"Добавить первый фронт"})]}):e.jsx(D,{style:{overflowX:"auto"},children:e.jsxs(d,{striped:!0,highlightOnHover:!0,miw:1e3,children:[e.jsx(d.Thead,{children:e.jsxs(d.Tr,{children:[e.jsx(d.Th,{children:"Фронт"}),e.jsx(d.Th,{children:"Exit"}),e.jsx(d.Th,{children:"Состояние"}),e.jsx(d.Th,{children:"Активен"}),e.jsx(d.Th,{children:"Проверка"}),e.jsx(d.Th,{ta:"right",children:"Действия"})]})}),e.jsx(d.Tbody,{children:S.map(i=>e.jsxs(d.Tr,{children:[e.jsxs(d.Td,{children:[e.jsx(c,{fw:600,children:e.jsxs(se,{children:[i.host,":443"]})}),e.jsxs(c,{size:"xs",c:"dimmed",children:["SSH: ",i.sshUser,"@",i.sshHost,":",i.sshPort]})]}),e.jsx(d.Td,{children:i.exitServer?e.jsxs(D,{children:[e.jsx(c,{size:"sm",fw:500,children:i.exitServer.name}),e.jsxs(c,{size:"xs",c:"dimmed",children:["→ ",i.targetHost,":443"]})]}):e.jsx(c,{size:"xs",c:"dimmed",children:"exit удалён"})}),e.jsx(d.Td,{children:X(i)}),e.jsx(d.Td,{children:e.jsx(ne,{checked:i.isActive,disabled:i.status!=="active"||H!==null,onChange:o=>void K(i,o.currentTarget.checked)})}),e.jsxs(d.Td,{children:[e.jsx(c,{size:"xs",c:i.lastCheckDetails?void 0:"dimmed",children:i.lastCheckDetails||"Проверка ещё не выполнялась"}),i.lastCheckedAt&&e.jsx(c,{size:"xs",c:"dimmed",children:new Date(i.lastCheckedAt).toLocaleString("ru-RU")})]}),e.jsx(d.Td,{children:e.jsxs(g,{justify:"flex-end",gap:4,wrap:"nowrap",children:[Kt(i)&&e.jsx(y,{size:"compact-sm",color:"green",leftSection:e.jsx(Ts,{size:14}),loading:H?.frontId===i.id&&H.action==="install",disabled:H!==null,onClick:()=>void Z(i),children:i.status==="installing"?"Повторить":"Установить"}),e.jsx(y,{size:"compact-sm",variant:"subtle",leftSection:e.jsx(es,{size:14}),loading:H?.frontId===i.id&&H.action==="check",disabled:H!==null,onClick:()=>void le(i),children:"Проверить"}),e.jsx(y,{size:"compact-sm",variant:"subtle",leftSection:e.jsx(Is,{size:14}),onClick:()=>{k(i),F()},children:"Лог"}),e.jsx(y,{size:"compact-sm",color:"red",variant:"subtle",leftSection:e.jsx(Je,{size:14}),loading:H?.frontId===i.id&&H.action==="delete",disabled:H!==null,onClick:()=>void fe(i),children:"Удалить"})]})})]},i.id))})]})})}),e.jsx(ce,{opened:ge,onClose:xe,title:"Добавить double-hop фронт",size:"lg",children:e.jsx("form",{onSubmit:G.onSubmit(pe),children:e.jsxs(Y,{gap:"md",children:[e.jsx(J,{label:"Exit WEB-прокси",description:"Трафик фронта уходит на этот exit (:443)",data:je,searchable:!0,required:!0,...G.getInputProps("exitServerId")}),e.jsx(z,{label:"Публичный домен фронта",description:"A-запись домена должна указывать на IP фронта. Идёт в tg://webproxy-ссылку.",placeholder:"front-ru.example.com",required:!0,...G.getInputProps("host")}),e.jsx(Ie,{order:5,children:"SSH-доступ к фронту"}),e.jsxs(g,{grow:!0,children:[e.jsx(z,{label:"SSH хост",placeholder:"203.0.113.20",required:!0,...G.getInputProps("sshHost")}),e.jsx(ae,{label:"SSH порт",min:1,max:65535,...G.getInputProps("sshPort")})]}),e.jsxs(g,{grow:!0,children:[e.jsx(z,{label:"SSH пользователь",...G.getInputProps("sshUser")}),e.jsx(qe,{label:"SSH пароль",required:!0,...G.getInputProps("sshPassword")})]}),e.jsx(we,{color:"grape",variant:"light",icon:e.jsx(Be,{size:18}),children:"Установщик поднимет Docker + nginx (stream :443 → exit:443) и откроет порт 443. MTProxy/TLS на фронте НЕ ставится — это только L4-проброс."}),e.jsxs(g,{justify:"flex-end",children:[e.jsx(y,{variant:"subtle",onClick:xe,children:"Отмена"}),e.jsx(y,{type:"submit",loading:L,children:"Добавить"})]})]})})}),e.jsx(ce,{opened:M,onClose:te,title:`Лог фронта: ${B?.host||""}`,size:"xl",children:e.jsx(se,{block:!0,style:{maxHeight:500,overflow:"auto",whiteSpace:"pre-wrap"},children:B?.installLog||B?.errorMessage||"Логов пока нет"})})]})}const Yt=180*1e3,Jt=5*1e3,Gt=3e3*1e3,qt=3,Zt=a=>new Promise(m=>setTimeout(m,a)),Xt=a=>{const m=a.trim().split(`
3`).map(S=>
3S.trim()).filter(Boolean);return m.length?m[m.length-1].slice(0,300):""},Ds=a=>a.status==="pending"||a.status==="error"?!0:a.status==="installing"&&Date.now()-new Date(a.updatedAt).getTime()>Yt,Ms=a=>{if(!a)return null;const m=a instanceof Date?a:new Date(a);return Number.isNaN(m.getTime())?null:m.toISOString()},Ns=kt.map(({value:a,name:m})=>({value:a,label:m}));function Us({option:a}){return e.jsxs(g,{gap:"sm",wrap:"nowrap",children:[e.jsx(Me,{countryCode:a.value,flagEmoji:Ss(a.value).flag,size:18}),e.jsx(c,{size:"sm",children:a.label})]})}const Ws=[{value:"https",label:"HTTPS"},{value:"https-lanes",label:"HTTPS lanes"},{value:"websocket",label:"WebSocket"},{value:"websocket-lanes",label:"WebSocket lanes"}],Fs=[{value:"prime",label:"👥 Для Premium пользователей"},{value:"free",label:"🆓 Для Free пользователей"},{value:"all",label:"🌍 Для всех пользователей"}],Rs={total:0,active:0,pending:0,installing:0,error:0};function Qt(){const{token:a}=hs(),{hostingProviders:m,hostingProvidersLoading:S}=Zs(),[C,E]=x.useState([]),[v,L]=x.useState(Rs),[W,H]=x.useState(!0),[p,B]=x.useState(!1),[k,M]=x.useState(null),[F,te]=x.useState(null),[ge,{open:ue,close:xe}]=he(!1),[G,{open:q,close:V}]=he(!1),[je,{open:pe,close:_}]=he(!1),[Z,le]=x.useState(null),[K,fe]=x.useState(""),[X,i]=x.useState(""),[o,j]=x.useState(!1),[h,A]=x.useState(!1),[de,Ce]=x.useState(!1),[Te,ke]=x.useState("password"),[Ae,Ne]=x.useState(""),[be,Ue]=x.useState(""),N=Ke({initialValues:{name:"",countryCode:"NL",host:"",letsencryptEmail:"",siteUpstream:"http://127.0.0.1:8080",carrierMode:"https",access:"prime",sshHost:"",sshUser:"root",sshPort:22,sshPassword:"",skipInstall:!1,secret:"",expiresAt:null,hostingProviderId:""},validate:{name:t=>t.trim()?null:"Введите название",countryCode:t=>t?null:"Выберите страну",host:t=>/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/i.test(t.trim())?null:"Укажите публичный DNS-хост",letsencryptEmail:t=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t.trim())?null:"Укажите email для Let’s Encrypt",siteUpstream:(t,u)=>u.skipInstall||/^http:\/\/(?:127\.(?:\d{1,3}\.){2}\d{1,3}|\[::1\]):\d{1,5}$/.test(t.trim())?null:"Нужен loopback URL, например http://127.0.0.1:8080",sshHost:t=>t.trim()?null:"Введите SSH-хост",sshPassword:(t,u)=>u.skipInstall||t?null:"Введите SSH-пароль",secret:(t,u)=>u.skipInstall&&!/^[a-f0-9]{32}$/i.test(t.trim())?"Нужен secret из 32 hex-символов":null}}),ee=Ke({initialValues:{name:"",countryCode:"NL",host:"",letsencryptEmail:"",siteUpstream:"",carrierMode:"https",access:"prime",isActive:!1,expiresAt:null,hostingProviderId:""}}),oe=async t=>{try{const u=await t.json();return Array.isArray(u?.message)?u.message.join(", "):u?.message||`HTTP ${t.status}`}catch{return`HTTP ${t.status}`}},l=async()=>{if(a)try{H(!0);const t={Authorization:`Bearer ${a}`},[u,$]=await Promise.all([fetch(`${Q}/api/admin/webproxy/servers`,{headers:t}),fetch(`${Q}/api/admin/webproxy/stats`,{headers:t})]);if(!u.ok)throw new Error(await oe(u));const O=await u.json(),ie=Array.isArray(O)?O:[];E(ie),te(ve=>ve&&ie.find(Fe=>Fe.id===ve.id)||null),$.ok&&L({...Rs,...await $.json()})}catch(t){r.show({title:"Ошибка",message:t?.message||"Не удалось загрузить WEB Proxy",color:"red"})}finally{H(!1)}};x.useEffect(()=>{l()},[a]);const w=async t=>{if(!k&&window.confirm(`Перезапустить WEB Proxy «${t.name}»?`))try{M({serverId:t.id,action:"restart"});const u=await fetch(`${Q}/api/admin/webproxy/servers/${t.id}/restart`,{method:"POST",headers:{Authorization:`Bearer ${a}`}});if(!u.ok)throw new Error(await oe(u));r.show({title:"Сервис перезапущен",message:"Состояние WEB Proxy обновлено",color:"green"}),await l()}catch(u){r.show({title:"Ошибка",message:u?.message||"Операция не выполнена",color:"red"}),await l()}finally{M(u=>u?.serverId===t.id&&u.action==="restart"?null:u)}},P=async t=>{const u=Date.now()+Gt;let $=0;for(;Date.now()<u;){await Zt(Jt);try{const O=await fetch(`${Q}
3/api/admin/webproxy/servers/${t}/install-status`,{headers:{Authorization:`Bearer ${a}`}});if(!O.ok)throw new Error(await oe(O));const ie=await O.json();if(ie.status==="success"||ie.status==="error")return{status:ie.status,log:ie.log||""};if($=ie.status==="unknown"?$+1:0,$>=qt)return{status:"unknown",log:""}}catch{}}return{status:"unknown",log:""}},re=async(t,u)=>{if(!k)try{M({serverId:t.id,action:"install"});const $=await fetch(`${Q}/api/admin/webproxy/servers/${t.id}/install`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify(u)});if(!$.ok)throw new Error(await oe($));r.show({title:"Установка запущена",message:"Сборка на VPS обычно занимает 10–20 минут. Результат появится здесь, страницу можно не держать открытой.",color:"blue",autoClose:1e4}),await l();const O=await P(t.id);O.status==="success"?r.show({title:"Установка завершена",message:"WEB Proxy готов к работе",color:"green"}):O.status==="error"?r.show({title:"Установка не удалась",message:Xt(O.log)||"Подробности смотрите в логе сервера",color:"red",autoClose:15e3}):r.show({title:"Статус установки неизвестен",message:"Обновите список — установка могла продолжиться на сервере",color:"yellow"})}catch($){r.show({title:"Ошибка",message:$?.message||"Операция не выполнена",color:"red"})}finally{await l(),M($=>$?.serverId===t.id&&$.action==="install"?null:$)}},Ge=async t=>{if(!k)try{M({serverId:t.id,action:"check"});const u=await fetch(`${Q}/api/admin/webproxy/servers/${t.id}/status`,{headers:{Authorization:`Bearer ${a}`}});if(!u.ok)throw new Error(await oe(u));const $=await u.json();r.show({title:$.ok?"WEB Proxy онлайн":"WEB Proxy офлайн",message:$.details||"Проверка завершена",color:$.ok?"green":"red"}),await l()}catch(u){r.show({title:"Ошибка проверки",message:u?.message||"Не удалось проверить сервер",color:"red"})}finally{M(u=>u?.serverId===t.id&&u.action==="check"?null:u)}},us=async t=>{if(!k&&window.confirm(`Сгенерировать новый secret для «${t.name}»? Все ранее выданные пользователям WEB-ссылки перестанут работать.`))try{M({serverId:t.id,action:"rotate"});const u=await fetch(`${Q}/api/admin/webproxy/servers/${t.id}/rotate-secret`,{method:"POST",headers:{Authorization:`Bearer ${a}`}});if(!u.ok)throw new Error(await oe(u));r.show({title:"Secret заменён",message:"Новый secret применён на сервере, сервисы перезапущены",color:"green"}),await l()}catch(u){r.show({title:"Ошибка ротации",message:u?.message||"Не удалось заменить secret",color:"red"}),await l()}finally{M(u=>u?.serverId===t.id&&u.action==="rotate"?null:u)}},We=(t,u)=>{fe(""),i(""),j(!1),A(!1),ke("password"),Ne(""),Ue(""),le({server:t,mode:u})},xs=()=>{const t={harden:!0};if(Te==="key"){const $=Ae.trim();if(!/^-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY-----[\s\S]+-----END (?:[A-Z0-9]+ )*PRIVATE KEY-----\s*$/.test($))return r.show({title:"Некорректный SSH-ключ",message:"Вставь приватный ключ (-----BEGIN ... PRIVATE KEY-----)",color:"red"}),null;if(be&&!/^[\x20-\x7E]+$/.test(be))return r.show({title:"Некорректный passphrase",message:"Только печатные ASCII-символы",color:"red"}),null;if(!X)return r.show({title:"Нужен новый SSH-пароль",message:"При подключении по ключу укажите «Новый SSH-пароль root» — сервер перейдёт на него после hardening",color:"red"}),null;t.sshPrivateKey=$,be&&(t.sshKeyPassphrase=be)}const u=K.trim();if(u){const $=Number(u);if(!Number.isInteger($)||$<1024||$>65535)return r.show({title:"Некорректный SSH-порт",message:"Укажите число от 1024 до 65535",color:"red"}),null;t.sshPortNew=$}if(X){if(X.length<8||!/^[\x20-\x7E]+$/.test(X))return r.show({title:"Некорректный пароль",message:"Минимум 8 символов, только печатные ASCII-символы",color:"red"}),null;t.sshPasswordNew=X}return o&&(t.lockdownPorts=!0),h&&(t.frontLockdown=!0),t},ps=async()=>
3{if(!Z)return;const t=xs();if(!t)return;const{server:u,mode:$}=Z;Ce(!0);try{if($==="install"){le(null),i(""),await re(u,t);return}M({serverId:u.id,action:"harden"});const O=await fetch(`${Q}/api/admin/webproxy/servers/${u.id}/harden`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify(t)});if(!O.ok)throw new Error(await oe(O));const ie=await O.json();le(null),i(""),r.show({title:"Сервер защищён",message:`Исходящий SMTP заблокирован, fail2ban включён, SSH-порт: ${ie.sshPort}`,color:"green"}),await l()}catch(O){r.show({title:"Ошибка защиты сервера",message:O?.message||"Не удалось применить защиту",color:"red"}),await l()}finally{Ce(!1),M(O=>O?.serverId===u.id&&O.action==="harden"?null:O)}},ss=async t=>{try{B(!0);const u=Ss(t.countryCode).name,$={...t,country:u,name:t.name.trim(),host:t.host.trim(),letsencryptEmail:t.letsencryptEmail.trim(),sshHost:t.sshHost.trim(),sshUser:t.sshUser.trim()||void 0,sshPassword:t.sshPassword||void 0,sshPort:Number(t.sshPort),hostingProviderId:t.hostingProviderId||void 0,siteUpstream:t.siteUpstream.trim()||void 0,secret:t.skipInstall?t.secret.trim():void 0,expiresAt:Ms(t.expiresAt)},O=await fetch(`${Q}/api/admin/webproxy/servers`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify($)});if(!O.ok)throw new Error(await oe(O));V(),N.reset(),r.show({title:"Сервер добавлен",message:t.skipInstall?"WEB Proxy добавлен в ручном режиме":"Теперь запустите установку через меню сервера",color:"green"}),await l()}catch(u){r.show({title:"Ошибка добавления",message:u?.message||"Не удалось добавить WEB Proxy",color:"red"})}finally{B(!1)}},ms=t=>{te(t),ee.setValues({name:t.name,countryCode:t.countryCode==="OTHER"?"XX":t.countryCode,host:t.host,letsencryptEmail:t.letsencryptEmail,siteUpstream:t.siteUpstream||"",carrierMode:t.carrierMode,access:t.access,isActive:t.isActive,expiresAt:t.expiresAt?new Date(t.expiresAt):null,hostingProviderId:t.hostingProvider?.id||""}),pe()},gs=async t=>{if(F)try{B(!0);const u=Ss(t.countryCode).name,$=await fetch(`${Q}/api/admin/webproxy/servers/${F.id}`,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({...t,country:u,name:t.name.trim(),host:t.host.trim(),letsencryptEmail:t.letsencryptEmail.trim(),hostingProviderId:t.hostingProviderId||void 0,siteUpstream:t.siteUpstream.trim()||void 0,expiresAt:Ms(t.expiresAt)})});if(!$.ok)throw new Error(await oe($));_(),r.show({title:"Сохранено",message:"Настройки WEB Proxy обновлены",color:"green"}),await l()}catch(u){r.show({title:"Ошибка сохранения",message:u?.message||"Не удалось сохранить настройки",color:"red"})}finally{B(!1)}},js=async t=>{if(!k&&window.confirm(`Удалить WEB Proxy «${t.name}» из панели? Удаление записи не удаляет ПО с VPS.`))try{M({serverId:t.id,action:"delete"});const u=await fetch(`${Q}/api/admin/webproxy/servers/${t.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${a}`}});if(!u.ok)throw new Error(await oe(u));r.show({title:"Удалено",message:"WEB Proxy удалён из панели",color:"green"}),await l()}catch(u){r.show({title:"Ошибка удаления",message:u?.message||"Не удалось удалить WEB Proxy",color:"red"})}finally{M(u=>u?.serverId===t.id&&u.action==="delete"?null:u)}},f=async(t,u)=>{t&&(await navigator.clipboard.writeText(t),r.show({title:"Скопировано",message:u,color:"green"}))},ts=(t,u)=>t==="active"&&u?e.jsx(T,{color:"green",leftSection:e.jsx(Pe,{size:12}),children:"Активен"}):t==="installing"?e.jsx(T,{color:"blue",leftSection:e.jsx(_e,{size:12}),children:"Установка"}):t==="error"?e.jsx(T,{color:"red",leftSection:e.jsx(Be,{size:12}),children:"Ошибка"}):e.jsx(T,{color:"yellow",leftSection:e.jsx(Js,{size:12}),children:"Ожидает установки"});return e.jsxs(Y,{gap:"lg",children:[e.jsxs(g,{justify:"space-between",align:"flex-start",children:[e.jsxs(D,{children:[e.jsxs(g,{gap:"sm",children:[e.jsx(Re,{size:28}),e.jsx(Ie,{order:2,children:"WEB Proxy Telegram"}),e.jsx(T,{color:"teal",variant:"filled",children:"WEB"})]}),e.jsx(c,{c:"dimmed",size:"sm",mt:4,children:"Настоящий Telegram WEB Proxy: HTTPS/443 + WebView carrier + локальный MTProxy backend"})]}),e.jsxs(g,{children:[e.jsx(y,{variant:"light",leftSection:e.jsx(Ps,{size:18}),loading:W,onClick:()=>void l(),children:"Обновить"}),e.jsx(y,{leftSection:e.jsx(ze,{size:18}),onClick:q,children:"Добавить WEB сервер"})]})]}),e.jsxs(we,{color:"blue",icon:e.jsx(Xe,{size:20}),title:"Это не обычный MTProxy",children:["Пользователь получает ссылку ",e.jsx(se,{children:"tg://webproxy?server=...&secret=..."}
3),". Порт в ссылке не указывается: внешний вход работает через HTTPS/443. Для установки нужны DNS-запись на VPS и свободные порты 80/443."]}),e.jsxs(we,{color:"orange",icon:e.jsx($s,{size:20}),title:"Secret общий для всего сервера",children:["Блокировка пользователя закрывает панель и подписку, но не отзывает уже выданную WEB-ссылку: secret один на сервер, персональных ключей здесь нет. Чтобы отозвать доступ, нажмите ",e.jsx("b",{children:"«Сменить secret»"})," — тогда старые ссылки перестанут работать у всех пользователей этого сервера."]}),e.jsxs(ns,{cols:{base:2,sm:5},children:[e.jsxs(me,{withBorder:!0,children:[e.jsx(c,{size:"xs",c:"dimmed",children:"Всего"}),e.jsx(c,{fw:700,size:"xl",children:v.total})]}),e.jsxs(me,{withBorder:!0,children:[e.jsx(c,{size:"xs",c:"dimmed",children:"Активны"}),e.jsx(c,{fw:700,size:"xl",c:"green",children:v.active})]}),e.jsxs(me,{withBorder:!0,children:[e.jsx(c,{size:"xs",c:"dimmed",children:"Ожидают"}),e.jsx(c,{fw:700,size:"xl",c:"yellow",children:v.pending})]}),e.jsxs(me,{withBorder:!0,children:[e.jsx(c,{size:"xs",c:"dimmed",children:"Устанавливаются"}),e.jsx(c,{fw:700,size:"xl",c:"blue",children:v.installing})]}),e.jsxs(me,{withBorder:!0,children:[e.jsx(c,{size:"xs",c:"dimmed",children:"Ошибки"}),e.jsx(c,{fw:700,size:"xl",c:"red",children:v.error})]})]}),e.jsx(me,{withBorder:!0,children:W?e.jsx(c,{c:"dimmed",children:"Загрузка WEB Proxy серверов…"}):C.length===0?e.jsxs(Y,{align:"center",py:"xl",children:[e.jsx(ds,{size:44,color:"var(--mantine-color-dimmed)"}),e.jsx(c,{c:"dimmed",children:"WEB Proxy серверы ещё не добавлены"}),e.jsx(y,{variant:"light",leftSection:e.jsx(ze,{size:16}),onClick:q,children:"Добавить первый сервер"})]}):e.jsx(D,{style:{overflowX:"auto"},children:e.jsxs(d,{striped:!0,highlightOnHover:!0,miw:1100,children:[e.jsx(d.Thead,{children:e.jsxs(d.Tr,{children:[e.jsx(d.Th,{children:"Сервер"}),e.jsx(d.Th,{children:"WEB endpoint"}),e.jsx(d.Th,{children:"Режим / доступ"}),e.jsx(d.Th,{children:"Состояние"}),e.jsx(d.Th,{children:"Проверка"}),e.jsx(d.Th,{ta:"right",children:"Действия"})]})}),e.jsx(d.Tbody,{children:C.map(t=>e.jsxs(d.Tr,{children:[e.jsx(d.Td,{children:e.jsxs(g,{gap:"xs",wrap:"nowrap",children:[e.jsx(Xe,{size:18}),e.jsxs(D,{children:[e.jsx(c,{fw:600,children:t.name}),e.jsxs(c,{size:"xs",c:"dimmed",children:[t.country," · ",t.countryCode]}),e.jsx(qs,{providerId:t.hostingProvider?.id,providers:m})]})]})}),e.jsxs(d.Td,{children:[e.jsx(T,{color:"teal",variant:"light",children:"WEB"}),e.jsx(c,{size:"sm",mt:4,children:e.jsxs(se,{children:[t.host,":443"]})}),e.jsxs(g,{gap:4,mt:4,children:[e.jsx(y,{size:"compact-xs",variant:"subtle",leftSection:e.jsx(Ve,{size:12}),onClick:()=>void f(t.telegramLink,"WEB-ссылка Telegram"),children:"tg://webproxy"}),e.jsx(y,{size:"compact-xs",variant:"subtle",leftSection:e.jsx(Ve,{size:12}),onClick:()=>void f(t.secret,"Secret"),children:"secret"})]})]}),e.jsxs(d.Td,{children:[e.jsx(c,{size:"sm",children:t.carrierMode}),e.jsx(T,{size:"sm",variant:"light",mt:4,children:t.access==="prime"?"Premium":t.access==="free"?"Free":"Все"})]}),e.jsx(d.Td,{children:ts(t.status,t.isActive)}),e.jsxs(d.Td,{children:[e.jsx(c,{size:"xs",c:t.lastCheckDetails?void 0:"dimmed",children:t.lastCheckDetails||"Проверка ещё не выполнялась"}),t.lastCheckedAt&&e.jsx(c,{size:"xs",c:"dimmed",children:new Date(t.lastCheckedAt).toLocaleString("ru-RU")})]}),e.jsx(d.Td,{children:e.jsxs(g,{justify:"flex-end",gap:4,wrap:"nowrap",children:[Ds(t)&&e.jsx(y,{size:"compact-sm",color:"green",leftSection:e.jsx(Ts,{size:14}),loading:k?.serverId===t.id&&k.action==="install",disabled:k!==null,onClick:()=>We(t,"install"),children:t.status==="installing"?"Повторить установку":"Установить"}),t.status==="active"&&e.jsx(y,{size:"compact-sm",variant:"light",leftSection:e.jsx(_e,{size:14}),loading:k?.serverId===t.id&&k.action==="restart",disabled:k!==null,onClick:()=>void w(t),children:"Restart"}),e.jsx(y,{size:"compact-sm",variant:"subtle",leftSection:e.jsx(es,{size:14}),loading:k?.serverId===t.id&&k.action==="check",disabled:k!==null,onClick:()=>void Ge(t),children:"Проверить"}),t.sshPasswordConfigured&&(t.status!=="installing"||Ds(t))&&e.jsx(y,{size:"compact-sm",color:"orange",variant:"subtle",leftSection:e.jsx(ws,{size:14}),loading:k?.serverId===t.id&&k.action==="harden",disabled:k!==null,onClick:()=>We(t,"harden"),children:"Защитить"}),t.status==="active"&&e.jsx(y,{size:"compact-sm",color:"orange",variant:"subtle",leftSection:e.jsx($s,{size:14}),loading:k?.serverId===t.id&&k.action==="rotate",disabled:k!==null,onClick:()=>void us(t),children:"Сменить secret"}),e.jsx(y,{size:"compact-sm",variant:"subtle",leftSection:e.jsx(Is,{size:14}),onClick:()=>{te(t),ue()},children:"Лог"}),e.jsx(y,{size:"compact-sm",variant:"subtle",leftSection:e.jsx(Ze,{size:14}),onClick:()=>ms(t),children:"Изменить"}),e.jsx(y,{size:"compact-sm",color:"red",variant:"subtle",leftSection:e.jsx(Je,{size:14}),loading:k?.serverId===t.id&&k.action==="delete",disabled:k!==null,onClick:()=>void js(t),children:"Удалить"})]})})]},t.id))})]})})}),e.jsx(_t,{token:a,exits:C.map(t=>({id:t.id,name:t.name,country:t.country,countryCode:t.countryCode,host:t.host,status:t.status}))}),e.jsx(ce,{opened:G,onClose:V,title:"Добавить Telegram WEB Proxy",size:"lg",children:e.jsx("form",{onSubmit:N.onSubmit(ss),children:e.jsxs(Y,{gap:"md",children:[e.jsx(z,{label:"Название",placeholder:"WEB Proxy NL-1",required:!0,...
3N.getInputProps("name")}),e.jsxs(g,{grow:!0,children:[e.jsx(J,{label:"Страна",data:Ns,renderOption:Us,searchable:!0,required:!0,...N.getInputProps("countryCode")}),e.jsx(cs,{providers:m,loading:S,value:N.values.hostingProviderId,onChange:t=>N.setFieldValue("hostingProviderId",t||"")})]}),e.jsx(z,{label:"Публичный WEB домен",description:"A-запись домена должна указывать на тестовый VPS",placeholder:"web-nl.example.com",required:!0,...N.getInputProps("host")}),e.jsxs(g,{grow:!0,children:[e.jsx(z,{label:"Email Let’s Encrypt",placeholder:"[email protected]",required:!0,...N.getInputProps("letsencryptEmail")}),e.jsx(J,{label:"Carrier mode",data:Ws,...N.getInputProps("carrierMode")})]}),e.jsx(z,{label:"Site upstream",description:"Локальный HTTP-сервис, который Caddy отдаёт как обычный сайт",placeholder:"http://127.0.0.1:8080",disabled:N.values.skipInstall,...N.getInputProps("siteUpstream")}),e.jsx(J,{label:"Доступ",data:Fs,...N.getInputProps("access")}),e.jsx(ne,{label:"Сервер уже установлен вручную",description:"Не запускать установщик; профиль tproxy-server с этим secret должен уже работать",...N.getInputProps("skipInstall",{type:"checkbox"})}),N.values.skipInstall&&e.jsx(z,{label:"WEB Secret",description:"Только 32 hex-символа. Не используйте ee… — это формат обычного MTProxy.",placeholder:"0123456789abcdef0123456789abcdef",required:!0,...N.getInputProps("secret")}),e.jsx(Ie,{order:5,children:"SSH-доступ к VPS установщика"}),e.jsxs(g,{grow:!0,children:[e.jsx(z,{label:"SSH хост",placeholder:"203.0.113.10",required:!0,...N.getInputProps("sshHost")}),e.jsx(ae,{label:"SSH порт",min:1,max:65535,...N.getInputProps("sshPort")})]}),e.jsxs(g,{grow:!0,children:[e.jsx(z,{label:"SSH пользователь",...N.getInputProps("sshUser")}),e.jsx(qe,{label:N.values.skipInstall?"SSH пароль (для проверки, опционально)":"SSH пароль",...N.getInputProps("sshPassword")})]}),e.jsx(Ye,{label:"Дата окончания",placeholder:"Без срока",clearable:!0,minDate:new Date,...N.getInputProps("expiresAt")}),e.jsxs(we,{color:"yellow",icon:e.jsx(Be,{size:18}),children:["Установщик скачивает upstream ",e.jsx(se,{children:"tproxy-server"}),", настраивает Caddy, локальный MTProxy и использует порт 443. Запускайте его только на отдельном тестовом VPS."]}),e.jsxs(g,{justify:"flex-end",children:[e.jsx(y,{variant:"subtle",onClick:V,children:"Отмена"}),e.jsx(y,{type:"submit",loading:p,children:"Добавить"})]})]})})}),e.jsx(ce,{opened:je,onClose:_,title:"Настройки Telegram WEB Proxy",size:"lg",children:e.jsx("form",{onSubmit:ee.onSubmit(gs),children:e.jsxs(Y,{gap:"md",children:[e.jsx(z,{label:"Название",required:!0,...ee.getInputProps("name")}),e.jsxs(g,{grow:!0,children:[e.jsx(J,{label:"Страна",data:Ns,renderOption:Us,searchable:!0,required:!0,...ee.getInputProps("countryCode")}),e.jsx(cs,{providers:m,loading:S,value:ee.values.hostingProviderId,onChange:t=>ee.setFieldValue("hostingProviderId",t||"")})]}),e.jsx(z,{label:"Публичный WEB домен",description:"Изменение домена или carrier mode потребует повторной установки",required:!0,...ee.getInputProps("host")}),e.jsxs(g,{grow:!0,children:[e.jsx(z,{label:"Email Let’s Encrypt",required:!0,...ee.getInputProps("letsencryptEmail")}),e.jsx(J,{label:"Carrier mode",data:Ws,...ee.getInputProps("carrierMode")})]}),e.jsx(z,{label:"Site upstream",placeholder:"http://127.0.0.1:8080",...ee.getInputProps("siteUpstream")}),e.jsx(J,{label:"Доступ",data:Fs,...ee.getInputProps("access")}),e.jsx(Ye,{label:"Дата окончания",placeholder:"Без срока",clearable:!0,minDate:new Date,...ee.getInputProps("expiresAt")}),e.jsx(ne,{label:"Активен для пользователей",...ee.getInputProps("isActive",{type:"checkbox"})}),F&&e.jsxs(c,{size:"sm",c:"dimmed",children:["SSH: ",e.jsxs(se,{children:[F.sshUser,"@",F.sshHost,":",F.sshPort]}),". Пароль ",F.sshPasswordConfigured?"сохранён":"не сохранён","."]}),e.jsxs(g,{justify:"flex-end",children:[e.jsx(y,{variant:"subtle",onClick:_,children:"Отмена"}),e.jsx(y,{type:"submit",loading:p,children:"Сохранить"})]})]})})}),e.jsx(ce,{opened:Z!==null,onClose:()=>!de&&le(null),title:Z?.mode==="install"?"Защита сервера и установка WEB Proxy":"Защита сервера",size:"md",children:e.jsxs(Y,{gap:"md",children:[e.jsxs(we,{color:"blue",variant:"light",icon:e.jsx(ws,{size:18}),children:[Z?.mode==="install"?"Перед установкой на сервере будет заблокирован исходящий SMTP (порт 25) и включён fail2ban. Порты 80/443 должны быть свободны.":"На сервере будет заблокирован исходящий SMTP (порт 25) и включён fail2ban. WEB Proxy не переустанавливается."," ","Поля ниже необязательные — оставьте пустыми, чтобы не менять SSH-доступ."]}),e.jsx(At,{fullWidth:!0,value:Te,onChange:t=>ke(t),data:[{label:"SSH по паролю (текущий)",value:"password"},{label:"SSH по ключу",value:"key"}]}),Te==="key"&&e.jsxs(e.Fragment,{children:[e.jsx(vt,{label:"Приватный SSH-ключ (для первого подключения)",description:"PEM/OpenSSH. Только для этого подключения; после hardening сервер переходит на новый root-пароль. Не сохраняется.",placeholder:`-----BEGIN OPENSSH PRIVATE KEY-----
4...
5-----END OPENSSH PRIVATE KEY-----`,value:Ae,onChange:t=>Ne(t.currentTarget.value),minRows:4,autosize:!0,styles:{input:{fontFamily:"monospace",fontSize:12}}}),e.jsx(qe,{label:"Passphrase ключа (если есть)",description:"Оставьте пустым для незашифрованного ключа",value:be,onChange:t=>Ue(t.currentTarget.value)})]}),e.jsx(ae,{label:"Новый SSH-порт",description:"1024–65535. Пусто — оставить текущий.",placeholder:`текущий: ${Z?.server.sshPort??22}`,min:1024,max:65535,allowDecimal:!1,value:K===""?"":Number(K),onChange:t=>fe(t===""||t===void 0?"":String(t))}),e.jsx(qe,{label:"Новый SSH-пароль root",description:"Минимум 8 символов, печатный ASCII. Пусто — оставить текущий.",placeholder:"Оставьте пустым, чтобы не менять",value:X,onChange:t=>i(t.currentTarget.value)}),e.jsx(ne,{label:"Закрыть лишние порты (INPUT default-deny)",description:"Оставить только SSH, 80/443 и реально слушающие порты. FORWARD и NAT не затрагиваются.",checked:o,onChange:t=>j(t.currentTarget.checked)}
5),o&&e.jsx(we,{color:"yellow",variant:"light",icon:e.jsx(Be,{size:18}),children:"Включайте только на выделенном VPS под WEB Proxy: правила пишутся через iptables, а tproxy-server поднимает собственный firewall на nftables."}),Z?.mode==="harden"&&e.jsxs(e.Fragment,{children:[e.jsx(ne,{label:"Double-hop: ограничить :443 только IP активных фронтов",description:"Порт 443 примет трафик ТОЛЬКО с IP фронтов этого exit (+loopback). Порт 80 остаётся открыт для ACME. Включает lockdown.",checked:h,onChange:t=>A(t.currentTarget.checked)}),h&&e.jsx(we,{color:"grape",variant:"light",icon:e.jsx(Be,{size:18}),children:"Прямые клиенты на :443 будут отрезаны — применяйте ТОЛЬКО после того, как поднят и включён хотя бы один фронт этого exit, и клиенты уже идут через него. Если у exit нет активных фронтов с определяемым IP, хардеринг будет отклонён."})]}),e.jsx(we,{color:"orange",variant:"light",icon:e.jsx(Be,{size:18}),children:"Смена порта и пароля выполняется на живом sshd. Если соединение оборвётся до подтверждения, панель оставит прежние SSH-данные — тогда проверьте сервер через консоль хостера."}),e.jsxs(g,{justify:"flex-end",children:[e.jsx(y,{variant:"subtle",onClick:()=>le(null),disabled:de,children:"Отмена"}),e.jsx(y,{color:"orange",leftSection:e.jsx(ws,{size:16}),loading:de,onClick:()=>void ps(),children:Z?.mode==="install"?"Защитить и установить":"Защитить сервер"})]})]})}),e.jsx(ce,{opened:ge,onClose:xe,title:`Лог WEB Proxy: ${F?.name||""}`,size:"xl",children:e.jsx(se,{block:!0,style:{maxHeight:500,overflow:"auto",whiteSpace:"pre-wrap"},children:F?.installLog||F?.errorMessage||"Логов пока нет"})})]})}const el=[{value:"auto",label:"Авто (из фида)"},{value:"http",label:"HTTP"},{value:"https",label:"HTTPS"},{value:"socks4",label:"SOCKS4"},{value:"socks5",label:"SOCKS5"}],Vs=[{value:"prime-public",label:"Публичные (prime)"},{value:"free",label:"Бесплатные"},{value:"all",label:"Все пользователи"},{value:"private",label:"Приватные"}];function sl(){const a=hs(m=>m.token);return x.useMemo(()=>{async function m(S,C,E){const v=await fetch(`/api/admin/proxy-feeds${C}`,{method:S,headers:{Authorization:`Bearer ${a}`,"Content-Type":"application/json"},body:E?JSON.stringify(E):void 0});if(!v.ok){let L="";try{const W=await v.json();L=W?.message||W?.error||JSON.stringify(W)}catch{L=await v.text()}throw new Error(L||`HTTP ${v.status}`)}return v.status===204?null:v.json()}return{get:S=>m("GET",S),post:(S,C)=>m("POST",S,C),put:(S,C)=>m("PUT",S,C),del:S=>m("DELETE",S)}},[a])}function tl({opened:a,onClose:m,onChanged:S}){const C=sl(),[E,v]=x.useState([]),[L,W]=x.useState(!0),[H,p]=x.useState(null),[B,k]=x.useState(""),[M,F]=x.useState(""),[te,ge]=x.useState("auto"),[ue,xe]=x.useState("prime-public"),[G,q]=x.useState(""),[V,je]=x.useState(200),[pe,_]=x.useState(!0),[Z,le]=x.useState(!1),K=x.useCallback(async()=>{W(!0);try{const h=await C.get("/sources");v(h)}catch(h){r.show({color:"red",message:h.message||"Ошибка загрузки"})}finally{W(!1)}},[C]);x.useEffect(()=>{a&&K()},[a,K]);const fe=x.useCallback(async h=>{p(`refresh-${h}`);try{const A=await C.post(`/sources/${h}/refresh`);r.show({color:"green",message:`Импортировано ${A.total} (новых ${A.added}, обновлено ${A.updated}, удалено ${A.removed}; в фиде ${A.parsed})`}),await K(),S()}catch(A){r.show({color:"red",message:A.message||"Ошибка обновления"})}finally{p(null)}},[C,K,S]),X=async()=>{if(!M.trim()){r.show({color:"red",message:"Укажите URL источника"});return}le(!0);try{const h=await C.post("/sources",{name:B.trim(),url:M.trim(),protocol:te,access:ue,defaultCountry:G.trim()||void 0,maxImport:V,healthCheck:pe});k(""),F(""),ge("auto"),xe("prime-public"),q(""),je(200),_(!0),r.show({color:"green",message:"Источник добавлен, загружаю прокси…"}),await fe(h.id)}catch(h){r.show({color:"red",message:h.message||"Не удалось добавить источник"})}finally{le(!1)}},i=async h=>{p(`src-${h.id}`);try{await C.put(`/sources/${h.id}`,{enabled:!h.enabled}),await K()}catch(A){r.show({color:"red",message:A.message})}finally{p(null)}},o=async(h,A)=>{p(`src-${h.id}`);try{await C.put(`/sources/${h.id}`,{access:A}),await K(),S()}catch(de){r.show({color:"red",message:de.message})}finally{p(null)}},j=async h=>{if(window.confirm(`Удалить источник «${h.name}» и все импортированные из него прокси?`)){p(`src-${h.id}`);try{await C.del(`/sources/${h.id}`),await K(),S()}catch(A){r.show({color:"red",message:A.message})}finally{p(null)}}};return e.jsx(ce,{opened:a,onClose:m,title:"Импорт прокси из URL-фидов",size:"xl",children:e.jsxs(Y,{children:[e.jsxs(we,{color:"orange",icon:e.jsx(St,{size:16}),children:["Импорт из внешнего URL-списка HTTP/SOCKS-прокси. ",e.jsx("b",{children:"Публичные"})," прокси управляются неизвестными операторами — не гарантируют приватность и могут быть нестабильны. Импортированные прокси помечаются",e.jsx(c,{span:!0,ff:"monospace",children:" source=feed"})," и попадают в общий пул с выбранным уровнем доступа. Обновление — только вручную кнопкой."]}),e.jsxs(me,{withBorder:!0,padding:"md",children:[e.jsx(c,{fw:600,mb:"sm",children:"Добавить источник"}),e.jsxs(Y,{gap:"sm",children:[e.jsxs(g,{align:"flex-end",wrap:"wrap",children:[e.jsx(z,{label:"Название",placeholder:"Free Proxy List",value:B,onChange:h=>k(h.currentTarget.value),style:{minWidth:200}}),e.jsx(z,{label:"URL списка",placeholder:"https://.../proxies/socks5/data.json",value:M,onChange:h=>F(h.currentTarget.value),style:{flex:1,minWidth:300}})]}),e.jsxs(g,{align:"flex-end",wrap:"wrap",children:[e.jsx(J,{label:"Протокол",data:el,value:te,onChange:h=>ge(h||"auto"),allowDeselect:!1,style:{width:150}}),e.jsx(J,{label:"Доступ",data:Vs,value:ue,onChange:h=>xe(h||"prime-public"),allowDeselect:!1,style:{width:180}}),e.jsx(z,{label:"Страна по умолч. (ISO)",placeholder:"DE",value:G,onChange:h=>q(h.currentTarget.value.toUpperCase().slice(0,2)),style:{width:130}}),e.jsx(ae,{label:"Макс. импорт",value:V,onChange:h=>je(typeof h=="number"?h:200),min:1,max:2e3,style:{width:130}}),e.jsx(ne,{label:"Health-фильтр",checked:pe,onChange:h=>_(h.currentTarget.checked),mb:6}),e.jsx(y,{leftSection:e.jsx(ze,{size:16}),onClick:X,loading:Z,children:"Добавить и загрузить"})]})]})]}),L?e.jsx(g,{justify:"center",p:"xl",children:e.jsx(Pt,{})}):E.length===0?e.jsx(c,{c:"dimmed",ta:"center",p:"xl",children:"Источников пока нет. Добавьте URL выше."}):E.map(h=>e.jsxs(me,{withBorder:!0,padding:"md",children:[e.jsxs(g,{justify:"space-between",mb:"xs",wrap:"wrap",children:[e.jsxs(g,{gap:"xs",children:[e.jsx(ne,{checked:h.enabled,onChange:()=>i(h),disabled:H===`src-${h.id}`,label:e.jsx(c,{fw:600,children:h.name})}),h.lastStatus==="ok"&&e.jsx(T,{color:"green",variant:"light",children:"ok"}),h.lastStatus==="error"&&e.jsx(Qe,{label:h.lastError||"ошибка",children:e.jsx(T,{color:"red",variant:"light",children:"error"})}),e.jsxs(T,{variant:"light",children:[h.proxiesCount," прокси"]}),e.jsx(T,{variant:"outline",children:h.protocol}),h.healthCheck&&e.jsx(T,{color:"teal",variant:"light",children:"health"})]}),e.jsxs(g,{gap:"xs",children:[e.jsx(J,{size:"xs",data:Vs,value:h.access,onChange:A=>o(h,A||"prime-public"),allowDeselect:!1,disabled:H===`src-${h.id}`,style:{width:160}}),e.jsx(y,{size:"xs",variant:"light",leftSection:e.jsx(_e,{size:14}),onClick:()=>fe(h.id),loading:H===`refresh-${h.id}`,children:"Обновить"}),e.jsx(os,{color:"red",variant:"subtle",onClick:()=>j(h),disabled:H===`src-${h.id}`,children:e.jsx(Je,{size:16})})]})]}),e.jsx(c,{size:"xs",c:"dimmed",style:{wordBreak:"break-all"},children:h.url})]}
5,h.id))]})})}const ll="_statCard_cbh73_1",vs={statCard:ll},U="",al=/^(0|[1-9]\d{0,2})$/,rl=/^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i;function il(a){const m=a.trim();if(!m||m.length>253)return!1;const S=m.split(".");return S.length===4&&S.every(E=>al.test(E)&&Number(E)<=255)||rl.test(m)}const nl=[{value:"🌍",label:"🌍 Мир (по умолчанию)"},{value:"🇦🇩",label:"🇦🇩 Андорра"},{value:"🇦🇪",label:"🇦🇪 ОАЭ"},{value:"🇦🇫",label:"🇦🇫 Афганистан"},{value:"🇦🇱",label:"🇦🇱 Албания"},{value:"🇦🇲",label:"🇦🇲 Армения"},{value:"🇦🇴",label:"🇦🇴 Ангола"},{value:"🇦🇷",label:"🇦🇷 Аргентина"},{value:"🇦🇹",label:"🇦🇹 Австрия"},{value:"🇦🇺",label:"🇦🇺 Австралия"},{value:"🇦🇿",label:"🇦🇿 Азербайджан"},{value:"🇧🇦",label:"🇧🇦 Босния"},{value:"🇧🇩",label:"🇧🇩 Бангладеш"},{value:"🇧🇪",label:"🇧🇪 Бельгия"},{value:"🇧🇬",label:"🇧🇬 Болгария"},{value:"🇧🇭",label:"🇧🇭 Бахрейн"},{value:"🇧🇴",label:"🇧🇴 Боливия"},{value:"🇧🇷",label:"🇧🇷 Бразилия"},{value:"🇧🇾",label:"🇧🇾 Беларусь"},{value:"🇨🇦",label:"🇨🇦 Канада"},{value:"🇨🇭",label:"🇨🇭 Швейцария"},{value:"🇨🇱",label:"🇨🇱 Чили"},{value:"🇨🇳",label:"🇨🇳 Китай"},{value:"🇨🇴",label:"🇨🇴 Колумбия"},{value:"🇨🇺",label:"🇨🇺 Куба"},{value:"🇨🇾",label:"🇨🇾 Кипр"},{value:"🇨🇿",label:"🇨🇿 Чехия"},{value:"🇩🇪",label:"🇩🇪 Германия"},{value:"🇩🇰",label:"🇩🇰 Дания"},{value:"🇩🇿",label:"🇩🇿 Алжир"},{value:"🇪🇨",label:"🇪🇨 Эквадор"},{value:"🇪🇪",label:"🇪🇪 Эстония"},{value:"🇪🇬",label:"🇪🇬 Египет"},{value:"🇪🇸",label:"🇪🇸 Испания"},{value:"🇫🇮",label:"🇫🇮 Финляндия"},{value:"🇫🇷",label:"🇫🇷 Франция"},{value:"🇬🇧",label:"🇬🇧 Великобритания"},{value:"🇬🇪",label:"🇬🇪 Грузия"},{value:"🇬🇷",label:"🇬🇷 Греция"},{value:"🇭🇰",label:"🇭🇰 Гонконг"},{value:"🇭🇷",label:"🇭🇷 Хорватия"},{value:"🇭🇺",label:"🇭🇺 Венгрия"},{value:"🇮🇩",label:"🇮🇩 Индонезия"},{value:"🇮🇪",label:"🇮🇪 Ирландия"},{value:"🇮🇱",label:"🇮🇱 Израиль"},{value:"🇮🇳",label:"🇮🇳 Индия"},{value:"🇮🇶",label:"🇮🇶 Ирак"},{value:"🇮🇷",label:"🇮🇷 Иран"},{value:"🇮🇸",label:"🇮🇸 Исландия"},{value:"🇮🇹",label:"🇮🇹 Италия"},{value:"🇯🇴",label:"🇯🇴 Иордания"},{value:"🇯🇵",label:"🇯🇵 Япония"},{value:"🇰🇪",label:"🇰🇪 Кения"},{value:"🇰🇬",label:"🇰🇬 Киргизия"},{value:"🇰🇭",label:"🇰🇭 Камбоджа"},{value:"🇰🇷",label:"🇰🇷 Южная Корея"},{value:"🇰🇼",label:"🇰🇼 Кувейт"},{value:"🇰🇿",label:"🇰🇿 Казахстан"},{value:"🇱🇧",label:"🇱🇧 Ливан"},{value:"🇱🇰",label:"🇱🇰 Шри-Ланка"},{value:"🇱🇹",label:"🇱🇹 Литва"},{value:"🇱🇺",label:"🇱🇺 Люксембург"},{value:"🇱🇻",label:"🇱🇻 Латвия"},{value:"🇲🇦",label:"🇲🇦 Марокко"},{value:"🇲🇨",label:"🇲🇨 Монако"},{value:"🇲🇩",label:"🇲🇩 Молдова"},{value:"🇲🇪",label:"🇲🇪 Черногория"},{value:"🇲🇰",label:"🇲🇰 Северная Македония"},{value:"🇲🇳",label:"🇲🇳 Монголия"},{value:"🇲🇹",label:"🇲🇹 Мальта"},{value:"🇲🇻",label:"🇲🇻 Мальдивы"},{value:"🇲🇽",label:"🇲🇽 Мексика"},{value:"🇲🇾",label:"🇲🇾 Малайзия"},{value:"🇳🇬",label:"🇳🇬 Нигерия"}
5,{value:"🇳🇱",label:"🇳🇱 Нидерланды"},{value:"🇳🇴",label:"🇳🇴 Норвегия"},{value:"🇳🇵",label:"🇳🇵 Непал"},{value:"🇳🇿",label:"🇳🇿 Новая Зеландия"},{value:"🇴🇲",label:"🇴🇲 Оман"},{value:"🇵🇦",label:"🇵🇦 Панама"},{value:"🇵🇪",label:"🇵🇪 Перу"},{value:"🇵🇭",label:"🇵🇭 Филиппины"},{value:"🇵🇰",label:"🇵🇰 Пакистан"},{value:"🇵🇱",label:"🇵🇱 Польша"},{value:"🇵🇸",label:"🇵🇸 Палестина"},{value:"🇵🇹",label:"🇵🇹 Португалия"},{value:"🇶🇦",label:"🇶🇦 Катар"},{value:"🇷🇴",label:"🇷🇴 Румыния"},{value:"🇷🇸",label:"🇷🇸 Сербия"},{value:"🇷🇺",label:"🇷🇺 Россия"},{value:"🇸🇦",label:"🇸🇦 Саудовская Аравия"},{value:"🇸🇪",label:"🇸🇪 Швеция"},{value:"🇸🇬",label:"🇸🇬 Сингапур"},{value:"🇸🇮",label:"🇸🇮 Словения"},{value:"🇸🇰",label:"🇸🇰 Словакия"},{value:"🇸🇾",label:"🇸🇾 Сирия"},{value:"🇹🇭",label:"🇹🇭 Таиланд"},{value:"🇹🇯",label:"🇹🇯 Таджикистан"},{value:"🇹🇲",label:"🇹🇲 Туркменистан"},{value:"🇹🇳",label:"🇹🇳 Тунис"},{value:"🇹🇷",label:"🇹🇷 Турция"},{value:"🇹🇼",label:"🇹🇼 Тайвань"},{value:"🇺🇦",label:"🇺🇦 Украина"},{value:"🇺🇸",label:"🇺🇸 США"},{value:"🇺🇾",label:"🇺🇾 Уругвай"},{value:"🇺🇿",label:"🇺🇿 Узбекистан"},{value:"🇻🇪",label:"🇻🇪 Венесуэла"},{value:"🇻🇳",label:"🇻🇳 Вьетнам"},{value:"🇾🇪",label:"🇾🇪 Йемен"},{value:"🇿🇦",label:"🇿🇦 ЮАР"}],Ks=nl.map(a=>({...a,label:a.label.replace(/^\S+\s+/,"")}));function _s({option:a}){return e.jsxs(g,{gap:"sm",wrap:"nowrap",children:[e.jsx(Me,{flagEmoji:a.value,size:20}),e.jsx(c,{size:"sm",children:a.label})]})}const is=[{value:"AD",label:"🇦🇩 Андорра",country:"Андорра"},{value:"AE",label:"🇦🇪 ОАЭ",country:"ОАЭ"},{value:"AF",label:"🇦🇫 Афганистан",country:"Афганистан"},{value:"AL",label:"🇦🇱 Албания",country:"Албания"},{value:"AM",label:"🇦🇲 Армения",country:"Армения"},{value:"AO",label:"🇦🇴 Ангола",country:"Ангола"},{value:"AR",label:"🇦🇷 Аргентина",country:"Аргентина"},{value:"AT",label:"🇦🇹 Австрия",country:"Австрия"},{value:"AU",label:"🇦🇺 Австралия",country:"Австралия"},{value:"AZ",label:"🇦🇿 Азербайджан",country:"Азербайджан"},{value:"BA",label:"🇧🇦 Босния",country:"Босния"},{value:"BD",label:"🇧🇩 Бангладеш",country:"Бангладеш"},{value:"BE",label:"🇧🇪 Бельгия",country:"Бельгия"},{value:"BG",label:"🇧🇬 Болгария",country:"Болгария"},{value:"BH",label:"🇧🇭 Бахрейн",country:"Бахрейн"},{value:"BO",label:"🇧🇴 Боливия",country:"Боливия"},{value:"BR",label:"🇧🇷 Бразилия",country:"Бразилия"},{value:"BY",label:"🇧🇾 Беларусь",country:"Беларусь"},{value:"CA",label:"🇨🇦 Канада",country:"Канада"},{value:"CH",label:"🇨🇭 Швейцария",country:"Швейцария"},{value:"CL",label:"🇨🇱 Чили",country:"Чили"},{value:"CN",label:"🇨🇳 Китай",country:"Китай"},{value:"CO",label:"🇨🇴 Колумбия",country:"Колумбия"},{value:"CR",label:"🇨🇷 Коста-Рика",country:"Коста-Рика"},{value:"CU",label:"🇨🇺 Куба",country:"Куба"},{value:"CY",label:"🇨🇾 Кипр",country:"Кипр"},{value:"CZ",label:"🇨🇿 Чехия",country:"Чехия"},{value:"DE",label:"🇩🇪 Германия",country:"Германия"},{value:"DK",label:"🇩🇰 Дания",country:"Дания"},{value:"DZ",label:"🇩🇿 Алжир",country:"Алжир"},{value:"EC",label:"🇪🇨 Эквадор",country:"Эквадор"},{value:"EE",label:"🇪🇪 Эстония",country:"Эстония"},{value:"EG",label:"🇪🇬 Египет",country:"Египет"},{value:"ES",label:"🇪🇸 Испания",country:"Испания"},{value:"FI",label:"🇫🇮 Финляндия",country:"Финляндия"},{value:"FR",label:"🇫🇷 Франция",country:"Франция"},{value:"GB",label:"🇬🇧 Великобритания",country:"Великобритания"},{value:"GE",label:"🇬🇪 Грузия",country:"Грузия"},{value:"GR",label:"🇬🇷 Греция",country:"Греция"},{value:"GT",label:"🇬🇹 Гватемала",country:"Гватемала"},{value:"HK",label:"🇭🇰 Гонконг",country:"Гонконг"},{value:"HR",label:"🇭🇷 Хорватия",country:"Хорватия"},{value:"HU",label:"🇭🇺 Венгрия",country:"Венгрия"},{value:"ID",label:"🇮🇩 Индонезия",country:"Индонезия"},{value:"IE",label:"🇮🇪 Ирландия",country:"Ирландия"},{value:"IL",label:"🇮🇱 Израиль",country:"Израиль"},{value:"IN",label:"🇮🇳 Индия",country:"Индия"},{value:"IQ",label:"🇮🇶 Ирак",country:"Ирак"},{value:"IR",label:"🇮🇷 Иран",country:"Иран"},{value:"IS",label:"🇮🇸 Исландия",country:"Исландия"},{value:"IT",label:"🇮🇹 Италия",country:"Италия"},{value:"JO",label:"🇯🇴 Иордания",country:"Иордания"},{value:"JP",label:"🇯🇵 Япония",country:"Япония"},{value:"KE",label:"🇰🇪 Кения",country:"Кения"},{value:"KG",label:"🇰🇬 Киргизия",country:"Киргизия"},{value:"KH",label:"🇰🇭 Камбоджа",country:"Камбоджа"},{value:"KR",label:"🇰🇷 Южная Корея",country:"Южная Корея"},{value:"KW",label:"🇰🇼 Кувейт",country:"Кувейт"},{value:"KZ",label:"🇰🇿 Казахстан",country:"Казахстан"},{value:"LB",label:"🇱🇧 Ливан",country:"Ливан"},{value:"LK",label:"🇱🇰 Шри-Ланка",country:"Шри-Ланка"},{value:"LT",label:"🇱🇹 Литва",country:"Литва"},{value:"LU",label:"🇱🇺 Люксембург",country:"Люксембург"},{value:"LV",label:"🇱🇻 Латвия",country:"Латвия"},{value:"MA",label:"🇲🇦 Марокко",country:"Марокко"},{value:"MC",label:"🇲🇨 Монако",country:"Монако"},{value:"MD",label:"🇲🇩 Молдова",country:"Молдова"},{value:"ME",label:"🇲🇪 Черногория",country:"Черногория"},{value:"MK",label:"🇲🇰 Северная Македония",country:"Северная Македония"},{value:"MN",label:"🇲🇳 Монголия",country:"Монголия"},{value:"MT",label:"🇲🇹 Мальта",country:"Мальта"},{value:"MV",label:"🇲🇻 Мальдивы",country:"Мальдивы"},{value:"MX",label:"🇲🇽 Мексика",country:"Мексика"},{value:"MY",label:"🇲🇾 Малайзия",country:"Малайзия"},{value:"NG",label:"🇳🇬 Нигерия",country:"Нигерия"}
5,{value:"NL",label:"🇳🇱 Нидерланды",country:"Нидерланды"},{value:"NO",label:"🇳🇴 Норвегия",country:"Норвегия"},{value:"NP",label:"🇳🇵 Непал",country:"Непал"},{value:"NZ",label:"🇳🇿 Новая Зеландия",country:"Новая Зеландия"},{value:"OM",label:"🇴🇲 Оман",country:"Оман"},{value:"PA",label:"🇵🇦 Панама",country:"Панама"},{value:"PE",label:"🇵🇪 Перу",country:"Перу"},{value:"PH",label:"🇵🇭 Филиппины",country:"Филиппины"},{value:"PK",label:"🇵🇰 Пакистан",country:"Пакистан"},{value:"PL",label:"🇵🇱 Польша",country:"Польша"},{value:"PS",label:"🇵🇸 Палестина",country:"Палестина"},{value:"PT",label:"🇵🇹 Португалия",country:"Португалия"},{value:"QA",label:"🇶🇦 Катар",country:"Катар"},{value:"RO",label:"🇷🇴 Румыния",country:"Румыния"},{value:"RS",label:"🇷🇸 Сербия",country:"Сербия"},{value:"RU",label:"🇷🇺 Россия",country:"Россия"},{value:"SA",label:"🇸🇦 Саудовская Аравия",country:"Саудовская Аравия"},{value:"SE",label:"🇸🇪 Швеция",country:"Швеция"},{value:"SG",label:"🇸🇬 Сингапур",country:"Сингапур"},{value:"SI",label:"🇸🇮 Словения",country:"Словения"},{value:"SK",label:"🇸🇰 Словакия",country:"Словакия"},{value:"SY",label:"🇸🇾 Сирия",country:"Сирия"},{value:"TH",label:"🇹🇭 Таиланд",country:"Таиланд"},{value:"TJ",label:"🇹🇯 Таджикистан",country:"Таджикистан"},{value:"TM",label:"🇹🇲 Туркменистан",country:"Туркменистан"},{value:"TN",label:"🇹🇳 Тунис",country:"Тунис"},{value:"TR",label:"🇹🇷 Турция",country:"Турция"},{value:"TW",label:"🇹🇼 Тайвань",country:"Тайвань"},{value:"UA",label:"🇺🇦 Украина",country:"Украина"},{value:"US",label:"🇺🇸 США",country:"США"},{value:"UY",label:"🇺🇾 Уругвай",country:"Уругвай"},{value:"UZ",label:"🇺🇿 Узбекистан",country:"Узбекистан"},{value:"VE",label:"🇻🇪 Венесуэла",country:"Венесуэла"},{value:"VN",label:"🇻🇳 Вьетнам",country:"Вьетнам"},{value:"YE",label:"🇾🇪 Йемен",country:"Йемен"},{value:"ZA",label:"🇿🇦 ЮАР",country:"ЮАР"}];function ol(){const{token:a}=hs(),{hostingProviders:m,hostingProvidersLoading:S}=Zs(),[C,E]=x.useState([]),[v,L]=x.useState({totalServers:0,activeServers:0}),[W,H]=x.useState(!0),[p,B]=x.useState(!1),[k,M]=x.useState(null),[F,te]=x.useState(""),[ge,ue]=x.useState(null),[xe,G]=x.useState({}),[q,V]=x.useState(!1),[je,{open:pe,close:_}]=he(!1),[Z,{open:le,close:K}]=he(!1),[fe,{open:X,close:i}]=he(!1),o=Ke({initialValues:{name:"",countryCode:"",host:"",port:443,apiPort:9091,maxTcpConns:3,fakeTlsDomain:"google.com",sshHost:"",sshUser:"root",sshPort:22,sshPassword:"",access:"prime",expiresAt:null,skipInstall:!1,secret:"",apiHost:"",hostingProviderId:""},validate:{name:l=>l?null:"Введите название",countryCode:l=>l?null:"Выберите страну",host:l=>l?null:"Введите хост",port:l=>!l||l<1||l>65535?"Неверный порт":null,sshHost:(l,w)=>w.skipInstall||l?null:"Введите SSH хост",sshPassword:(l,w)=>w.skipInstall||l?null:"Введите SSH пароль",secret:(l,w)=>w.skipInstall?l?Ls(l)?null:"Не распознано. Принимаются: tg://proxy?…, ee…/dd… hex, или 32-символьный hex":"Укажите Secret или tg://proxy ссылку":null}}),j=Ke({initialValues:{name:"",countryCode:"",host:"",port:443,apiPort:9091,maxTcpConns:3,fakeTlsDomain:"google.com",sshHost:"",sshUser:"root",sshPort:22,sshPassword:"",isActive:!1,access:"prime",expiresAt:null,hostingProviderId:""}}),h={Authorization:`Bearer ${a}`,"Content-Type":"application/json"},A=async()=>{try{H(!0);const[l,w]=await Promise.all([fetch(`${U}/api/admin/mtproxy/servers`,{headers:h}),fetch(`${U}/api/admin/mtproxy/stats`,{headers:h})]);l.ok&&E(await l.json()),w.ok&&L(await w.json())}catch(l){console.error("Error fetching MTProxy data:",l)}finally{H(!1)}};x.useEffect(()=>{A()},[a]);const de=async(l=!1)=>{try{V(!0);const w=await fetch(`${U}/api/admin/monitoring/server-health${l?"?force=1":""}`,{headers:h});if(w.ok){const P=await w.json();G(P&&P.mtproxy||{})}}catch(w){console.error("Error fetching MTProxy health:",w)}finally{V(!1)}};x.useEffect(()=>{de();const l=setInterval(()=>de(),6e4);return()=>clearInterval(l)},[a]);const Ce=l=>{const w=xe[String(l.id)];if(!w)return e.jsx(Qe,{label:q?"Проверка…":"Нет данных",withArrow:!0,children:e.jsx(T,{color:"gray",variant:"light",size:"sm",children:"—"})});const P=w.online?`Отвечает${w.latencyMs!=null?` · ${w.latencyMs} мс`:""} · проверено ${Le(w.checkedAt).format("HH:mm:ss")}`:`Порт не отвечает · проверено ${Le(w.checkedAt).format("HH:mm:ss")}`;return e.jsx(Qe,{label:P,withArrow:!0,children:e.jsx(T,{color:w.online?"teal":"red",variant:"dot",size:"sm",children:w.online?"Онлайн":"Офлайн"})})},Te=async l=>{try{B(!0);const w=is.find(re=>re.value===l.countryCode),P=await fetch(`${U}/api/admin/mtproxy/servers`,{method:"POST",headers:h,body:JSON.stringify({...l,country:w?.country||l.countryCode})});if(P.ok)r.show({title:"Успех",message:"Сервер создан",color:"green"}
5),_(),o.reset(),A();else{const re=await P.json();r.show({title:"Ошибка",message:re.message||"Не удалось создать",color:"red"})}}catch{r.show({title:"Ошибка",message:"Сетевая ошибка",color:"red"})}finally{B(!1)}},ke=async l=>{if(k)try{B(!0);const w=is.find(Ge=>Ge.value===l.countryCode),P={...l};w&&(P.country=w.country),P.sshPassword||delete P.sshPassword,(await fetch(`${U}/api/admin/mtproxy/servers/${k.id}`,{method:"PUT",headers:h,body:JSON.stringify(P)})).ok&&(r.show({title:"Успех",message:"Сервер обновлён",color:"green"}),K(),A())}catch{r.show({title:"Ошибка",message:"Не удалось обновить",color:"red"})}finally{B(!1)}},Ae=async l=>{if(!confirm("Удалить сервер?"))return;const w=confirm(`Уведомить пользователей об удалении сервера?
6
7OK — отправить уведомление затронутым пользователям.
8Отмена — удалить без уведомления.`);try{await fetch(`${U}/api/admin/mtproxy/servers/${l}?notifyUsers=${w}`,{method:"DELETE",headers:h}),r.show({title:"Удалено",message:"Сервер удалён",color:"green"}),A()}catch{r.show({title:"Ошибка",message:"Не удалось удалить",color:"red"})}},Ne=async l=>{try{B(!0),r.show({title:"Установка",message:"Установка TeleMT начата...",color:"blue",loading:!0,autoClose:!1,id:`install-${l}`});const P=await(await fetch(`${U}/api/admin/mtproxy/servers/${l}/install`,{method:"POST",headers:h})).json();r.hide(`install-${l}`),P.success?r.show({title:"Успех",message:"TeleMT установлен!",color:"green"}):r.show({title:"Ошибка установки",message:"Проверьте логи",color:"red"}),te(P.log||""),X(),A()}catch{r.hide(`install-${l}`),r.show({title:"Ошибка",message:"Сетевая ошибка",color:"red"})}finally{B(!1)}},be=async l=>{try{const P=await(await fetch(`${U}/api/admin/mtproxy/servers/${l}/status`,{headers:h})).json();G(re=>({...re,[String(l)]:{online:!!P.online,latencyMs:null,checkedAt:new Date().toISOString()}})),r.show({title:P.online?"Онлайн":"Оффлайн",message:P.online?"TeleMT работает":"TeleMT не запущен",color:P.online?"green":"red"})}catch{r.show({title:"Ошибка",message:"Не удалось проверить",color:"red"})}},Ue=async l=>{try{const P=await(await fetch(`${U}/api/admin/mtproxy/servers/${l}/restart`,{method:"POST",headers:h})).json();r.show({title:P.success?"Перезапущен":"Ошибка",message:P.success?"TeleMT перезапущен":P.message,color:P.success?"green":"red"})}catch{r.show({title:"Ошибка",message:"Сетевая ошибка",color:"red"})}},N=l=>{M(l),j.setValues({name:l.name,countryCode:l.countryCode,host:l.host,port:l.port,apiPort:l.apiPort||9091,maxTcpConns:l.maxTcpConns||3,fakeTlsDomain:l.fakeTlsDomain,sshHost:l.sshHost,sshUser:l.sshUser,sshPort:l.sshPort,sshPassword:"",isActive:l.isActive,access:l.access||"prime",expiresAt:l.expiresAt?new Date(l.expiresAt):null,hostingProviderId:l.hostingProviderId||""}),le()},ee=(l,w)=>{const re={active:{color:"green",label:"Активен"},installing:{color:"blue",label:"Установка..."},pending:{color:"yellow",label:"Ожидание"},error:{color:"red",label:"Ошибка"}}[l]||{color:"gray",label:l};return e.jsx(T,{color:re.color,variant:"light",children:re.label})},oe=l=>{try{return String.fromCodePoint(...l.toUpperCase().split("").map(w=>127462+w.charCodeAt(0)-65))}catch{return"🌍"}};return W?e.jsx(Gs,{}):e.jsxs(Y,{gap:"lg",children:[e.jsxs(g,{justify:"space-between",align:"flex-start",children:[e.jsxs(D,{children:[e.jsx(Ie,{order:3,children:"MTProxy Серверы"}),e.jsx(c,{c:"dimmed",size:"sm",children:"Telegram прокси с маскировкой (TeleMT)"})]}),e.jsxs(g,{children:[e.jsx(y,{variant:"light",color:"teal",leftSection:e.jsx(es,{size:18}),loading:q,onClick:()=>de(!0),children:"Проверить все"}),e.jsx(y,{variant:"light",leftSection:e.jsx(_e,{size:18}),onClick:A,children:"Обновить"}),e.jsx(y,{leftSection:e.jsx(ze,{size:18}),onClick:pe,children:"Добавить"})]})]}),e.jsxs(ns,{cols:{base:2,sm:2},children:[e.jsxs(Oe,{withBorder:!0,p:"md",radius:"md",children:[e.jsx(De,{size:"lg",radiu
8s:"md",variant:"light",color:"blue",children:e.jsx(ds,{size:20})}),e.jsxs(D,{mt:"sm",children:[e.jsx(c,{size:"xl",fw:700,children:v.totalServers}),e.jsx(c,{size:"xs",c:"dimmed",children:"Всего серверов"})]})]}),e.jsxs(Oe,{withBorder:!0,p:"md",radius:"md",children:[e.jsx(De,{size:"lg",radius:"md",variant:"light",color:"green",children:e.jsx(Pe,{size:20})}),e.jsxs(D,{mt:"sm",children:[e.jsx(c,{size:"xl",fw:700,children:v.activeServers}),e.jsx(c,{size:"xs",c:"dimmed",children:"Активных"})]})]})]}),e.jsx(me,{withBorder:!0,padding:0,children:e.jsxs(d,{striped:!0,highlightOnHover:!0,children:[e.jsx(d.Thead,{children:e.jsxs(d.Tr,{children:[e.jsx(d.Th,{children:"Сервер"}),e.jsx(d.Th,{children:"Провайдер"}),e.jsx(d.Th,{children:"Хост / Порт"}),e.jsx(d.Th,{children:"Secret"}),e.jsx(d.Th,{children:"Сессии"}),e.jsx(d.Th,{children:"Статус"}),e.jsx(d.Th,{children:"Живой статус"}),e.jsx(d.Th,{children:"Истекает"}),e.jsx(d.Th,{children:"Действия"})]})}),e.jsx(d.Tbody,{children:C.length===0?e.jsx(d.Tr,{children:e.jsx(d.Td,{colSpan:9,children:e.jsx(It,{py:"xl",children:e.jsxs(Y,{align:"center",gap:"xs",children:[e.jsx(Xe,{size:48,stroke:1,color:"var(--mantine-color-dimmed)"}),e.jsx(c,{c:"dimmed",children:"Нет MTProxy серверов"}),e.jsx(y,{size:"xs",onClick:pe,children:"Добавить первый"})]})})})}):C.map(l=>e.jsxs(d.Tr,{children:[e.jsx(d.Td,{children:e.jsxs(g,{gap:"sm",children:[e.jsx(Me,{countryCode:l.countryCode,flagEmoji:oe(l.countryCode),size:24}),e.jsxs(D,{children:[e.jsx(c,{size:"sm",fw:500,children:l.name}),e.jsx(c,{size:"xs",c:"dimmed",children:l.country})]})]})}),e.jsx(d.Td,{children:e.jsx(qs,{providerId:l.hostingProviderId,providers:m})}),e.jsx(d.Td,{children:e.jsxs(se,{children:[l.host,":",l.port]})}),e.jsx(d.Td,{children:l.secret?e.jsxs(g,{gap:"xs",children:[e.jsxs(se,{style:{maxWidth:120,overflow:"hidden",textOverflow:"ellipsis"},children:[l.secret.substring(0,16),"..."]}),e.jsx(os,{size:"xs",variant:"subtle",onClick:()=>{navigator.clipboard.writeText(l.secret||""),r.show({title:"Скопировано",message:"Secret скопирован",color:"green"})},children:e.jsx(Ve,{size:12})})]}):e.jsx(c,{size:"sm",c:"dimmed",children:"—"})}),e.jsx(d.Td,{children:e.jsx(T,{variant:"light",color:"gray",size:"sm",children:l.maxTcpConns||3})}),e.jsx(d.Td,{children:ee(l.status,l.isActive)}),e.jsx(d.Td,{children:Ce(l)}),e.jsx(d.Td,{children:l.expiresAt?(()=>{const w=new Date(l.expiresAt),P=w.getTime()<Date.now();return e.jsx(T,{color:P?"red":"gray",variant:"light",size:"sm",children:P?"Истёк":w.toLocaleDateString("ru-RU")})})():e.jsx(c,{size:"sm",c:"dimmed",children:"—"})}),e.jsx(d.Td,{children:e.jsxs(R,{shadow:"md",children:[e.jsx(R.Target,{children:e.jsx(os,{variant:"subtle",children:e.jsx(Xs,{size:16})})}),e.jsxs(R.Dropdown,{children:[l.status==="pending"||l.status==="error"?e.jsx(R.Item,{leftSection:e.jsx(Ts,{size:14}),onClick:()=>Ne(l.id),disabled:p,children:"Установить TeleMT"}):null,e.jsx(R.Item,{leftSection:e.jsx(Qs,{size:14}),onClick:()=>be(l.id),children:"Проверить статус"}),l.status==="active"&&e.jsx(R.Item,{leftSection:e.jsx(Ps,{size:14}),onClick:()=>Ue(l.id),children:"Перезапустить"}),l.installLog&&e.jsx(R.Item,{leftSection:e.jsx(Is,{size:14}),onClick:()=>{te(l.installLog||""),X()},children:"Логи установки"}),e.jsx(R.Item,{leftSection:e.jsx(Ze,{size:14}),onClick:()=>N(l),children:"Редактировать"}),l.secret&&e.jsx(R.Item,{leftSection:e.jsx(Xe,{size:14}),onClick:()=>{const w=`tg://proxy?server=${l.host}&port=${l.port}&secret=${l.secret}`;navigator.clipboard.writeText(w),r.show({title:"Скопировано",message:"Telegram ссылка",color:"green"})},children:"Копировать TG ссылку"}),e.jsx(R.Item,{leftSection:e.jsx(zt,{size:14}),onClick:()=>ue({protocol:"mtproxy",serverId:String(l.id),serverNameHint:l.name}),children:"Уведомить пользователей"}),e.jsx(R.Divider,{}),e.jsx(R.Item,{color:"red",leftSection:e.jsx(Je,{size:14}),onClick:()=>Ae(l.id),children:"Удалить"})]})]})})]},l.id))})]})}),e.jsx(ce,{opened:je,onClose:_,title:"Добавить MTProxy сервер",size:"lg",children:e.jsx("form",{onSubmit:o.onSubmit(Te),children:e.jsxs(Y,{gap:"md",children:[e.jsx(z,{label:"Название",...o.getInputProps("name")}
8),e.jsx(J,{label:"Страна",placeholder:"Выберите страну",data:is,searchable:!0,...o.getInputProps("countryCode")}),e.jsx(cs,{providers:m,loading:S,value:o.values.hostingProviderId,onChange:l=>o.setFieldValue("hostingProviderId",l||"")}),e.jsxs(g,{grow:!0,children:[e.jsx(z,{label:"Хост (IP/домен)",placeholder:"1.2.3.4",...o.getInputProps("host")}),e.jsx(ae,{label:"Порт",min:1,max:65535,...o.getInputProps("port")})]}),e.jsxs(g,{grow:!0,children:[e.jsx(ae,{label:"API порт (TeleMT)",min:1,max:65535,...o.getInputProps("apiPort")}),e.jsx(z,{label:"Fake-TLS домен",placeholder:"google.com",...o.getInputProps("fakeTlsDomain")})]}),e.jsx(ae,{label:"Макс. сессий на пользователя",description:"Ограничение одновременных TCP-соединений (анти-шеринг)",min:1,max:100,...o.getInputProps("maxTcpConns")}),e.jsx(J,{label:"Доступ",data:[{value:"prime",label:"👥 Для премиум пользователей"},{value:"free",label:"🆓 Для Free пользователей"},{value:"all",label:"🌍 Для всех пользователей"}],...o.getInputProps("access")}),e.jsx(Ye,{label:"Дата окончания",placeholder:"Без срока — оставьте пустым",description:"После этой даты сервер пропадёт у пользователей",clearable:!0,minDate:new Date,...o.getInputProps("expiresAt")}),e.jsx(ne,{label:"Добавить вручную (без установки на сервер)",description:"TeleMT уже установлен, либо это сторонний MTProxy. Панель не будет подключаться по SSH — нужно указать Secret сервера.",checked:o.values.skipInstall,onChange:l=>o.setFieldValue("skipInstall",l.currentTarget.checked)}),o.values.skipInstall?e.jsxs(e.Fragment,{children:[e.jsx(z,{label:"Secret или tg-ссылка",placeholder:"tg://proxy?server=1.2.3.4&port=443&secret=ee...",description:"Вставьте полную ссылку из tg://proxy, fake-TLS секрет (ee…) или 32-символьный hex. Поля «Хост», «Порт» и «Fake-TLS домен» заполнятся автоматически, если вставлена ссылка.",...o.getInputProps("secret"),onChange:l=>{const w=l.currentTarget.value;o.setFieldValue("secret",w);const P=Ls(w);P&&(P.host&&!o.values.host&&o.setFieldValue("host",P.host),P.port&&(!o.values.port||o.values.port===443)&&o.setFieldValue("port",P.port),P.fakeTlsDomain&&(!o.values.fakeTlsDomain||o.values.fakeTlsDomain==="google.com")&&o.setFieldValue("fakeTlsDomain",P.fakeTlsDomain))}}),e.jsx(z,{label:"API host (опционально)",placeholder:"127.0.0.1",description:"Хост TeleMT API. Заполняется только если у вас есть прямой доступ к API (для индивидуальных ключей). Для купленных у стороннего сервиса прокси оставьте пустым — все пользователи получат общую ссылку.",...o.getInputProps("apiHost")})]}):e.jsxs(e.Fragment,{children:[e.jsx(Ie,{order:5,mt:"sm",children:"SSH доступ"}),e.jsxs(g,{grow:!0,children:[e.jsx(z,{label:"SSH хост",placeholder:"1.2.3.4",...o.getInputProps("sshHost")}),e.jsx(ae,{label:"SSH порт",min:1,max:65535,...o.getInputProps("sshPort")})]}),e.jsxs(g,{grow:!0,children:[e.jsx(z,{label:"SSH пользователь",...o.getInputProps("sshUser")}),e.jsx(z,{label:"SSH пароль",type:"password",...o.getInputProps("sshPassword")})]})]}),e.jsxs(g,{justify:"flex-end",children:[e.jsx(y,{variant:"light",onClick:_,children:"Отмена"}),e.jsx(y,{type:"submit",loading:p,children:"Создать"})]})]})})}),e.jsx(ce,{opened:Z,onClose:K,title:"Редактировать MTProxy сервер",size:"lg",children:e.jsx("form",{onSubmit:j.onSubmit(ke),children:e.jsxs(Y,{gap:"md",children:[e.jsx(z,{label:"Название",...j.getInputProps("name")}),e.jsx(J,{label:"Страна",data:is,searchable:!0,...j.getInputProps("countryCode")}),e.jsx(cs,{providers:m,loading:S,value:j.values.hostingProviderId,onChange:l=>j.setFieldValue("hostingProviderId",l||"")}),e.jsxs(g,{grow:!0,children:[e.jsx(z,{label:"Хост (IP/домен)",...j.getInputProps("host")}),e.jsx(ae,{label:"Порт",min:1,max:65535,...j.getInputProps("port")})]}),e.jsxs(g,{grow:!0,children:[e.jsx(ae,{label:"API порт (TeleMT)",min:1,max:65535,...j.getInputProps("apiPort")}),e.jsx(z,{label:"Fake-TLS домен",...j.getInputProps("fakeTlsDomain")})]}),e.jsx(ae,{label:"Макс. сессий на пользователя",description:"Ограничение одновременных TCP-соединений (анти-шеринг)",min:1,max:100,...j.getInputProps("maxTcpConns")}),e.jsx(J,{label:"Доступ",data:[{value:"prime",label:"👥 Дл
8я премиум пользователей"},{value:"free",label:"🆓 Для Free пользователей"},{value:"all",label:"🌍 Для всех пользователей"}],...j.getInputProps("access")}),e.jsx(Ye,{label:"Дата окончания",placeholder:"Без срока — оставьте пустым",description:"После этой даты сервер пропадёт у пользователей",clearable:!0,...j.getInputProps("expiresAt")}),e.jsx(ne,{label:"Активен",...j.getInputProps("isActive",{type:"checkbox"})}),e.jsx(Ie,{order:5,mt:"sm",children:"SSH доступ"}),e.jsxs(g,{grow:!0,children:[e.jsx(z,{label:"SSH хост",...j.getInputProps("sshHost")}),e.jsx(ae,{label:"SSH порт",min:1,max:65535,...j.getInputProps("sshPort")})]}),e.jsxs(g,{grow:!0,children:[e.jsx(z,{label:"SSH пользователь",...j.getInputProps("sshUser")}),e.jsx(z,{label:"SSH пароль (оставьте пустым чтобы не менять)",type:"password",...j.getInputProps("sshPassword")})]}),e.jsxs(g,{justify:"flex-end",children:[e.jsx(y,{variant:"light",onClick:K,children:"Отмена"}),e.jsx(y,{type:"submit",loading:p,children:"Сохранить"})]})]})})}),e.jsx(ce,{opened:fe,onClose:i,title:"Логи установки",size:"xl",children:e.jsx(se,{block:!0,style:{maxHeight:400,overflow:"auto",whiteSpace:"pre-wrap",fontSize:12},children:F||"Нет логов"})}),e.jsx(Ct,{target:ge,onClose:()=>ue(null)})]})}function Hl(){const{token:a}=hs(),[m,S]=x.useState("proxies"),[C,E]=x.useState([]),[v,L]=x.useState({total:0,active:0,countriesCount:0}),[W,H]=x.useState(""),[p,B]=x.useState(null),[k,M]=x.useState(!0),[F,te]=x.useState(!1),[ge,ue]=x.useState("active"),[xe,G]=x.useState({}),[q,V]=x.useState(!1),[je,pe]=x.useState(null),[_,Z]=x.useState(new Set),[le,K]=x.useState(!1),[fe,X]=x.useState(!1),[i,o]=x.useState(!1),[j,h]=x.useState(null),[A,de]=x.useState(""),[Ce,Te]=x.useState("EOFVPN"),[ke,Ae]=x.useState(!1),[Ne,be]=x.useState(!1),[Ue,N]=x.useState(!1),[ee,{open:oe,close:l}]=he(!1),[w,{open:P,close:re}]=he(!1),[Ge,{open:us,close:We}]=he(!1),[xs,{open:ps,close:ss}]=he(!1),[ms,{open:gs,close:js}]=he(!1),f=Ke({initialValues:{userId:"",country:"",ip:"",httpPort:1506,socksPort:11506,login:"",password:"",access:"prime-public",expiresAt:null,isActive:!0,forWhatsApp:!1},validate:{country:s=>s?null:"Выберите страну",ip:s=>!s||!s.trim()?"Введите IP-адрес или домен":il(s)?null:"Неверный формат: нужен IP-адрес или домен",httpPort:s=>!s||s<1||s>65535?"Неверный порт":null,socksPort:s=>!s||s<1||s>65535?"Неверный порт":null,login:(s,n)=>!n.forWhatsApp&&!s?"Введите логин":null,password:(s,n)=>!n.forWhatsApp&&!s?"Введите пароль":null,userId:(s,n)=>n.access==="personal"&&!s?"Выберите пользователя":null}}),[ts,t]=x.useState([]),[u,$]=x.useState(!1),[O,ie]=x.useState("");x.useEffect(()=>{const s=O.trim();if(s.length<2){t(I=>I.filter(ye=>ye.value===f.values.userId));return}let n=!1;$(!0);const b=setTimeout(async()=>{try{const I=await fetch(`${U}/api/admin/users?limit=20&search=${encodeURIComponent(s)}`,{headers:{Authorization:`Bearer ${a}`}});if(!I.ok)throw new Error("search failed");const ye=await I.json();if(n)return;const yt=(Array.isArray(ye?.users)?ye.users:[]).map(He=>{const ys=He.email||"",ls=He.username||He.login||"",As=He.telegramUsername?` · @${He.telegramUsername}`:"",wt=ys?`${ys}${ls?` (${ls})`:""}${As}`:`${ls||`#${He.id}`}${As}`;return{value:String(He.id),label:wt,email:ys,username:ls}});t(yt)}catch{n||t([])}finally{n||$(!1)}},250);return()=>{n=!0,clearTimeout(b)}},[O,a]);const ve=async()=>{try{M(!0);const s={Authorization:`Bearer ${a}`},[n,b]=await Promise.all([fetch(`${U}/api/admin/proxies?limit=100`,{headers:s}),fetch(`${U}/api/admin/proxies/stats`,{headers:s})]);if(n.ok){const I=await n.json();E(I.proxies||[])}if(b.ok){const I=await b.json();L(I)}}catch(s){console.error("Error fetching proxies:",s),r.show({title:"Ошибка",message:"Не удалось загрузить данные",color:"red"})}finally{M(!1)}};x.useEffect(()=>{ve()},[a]);const Fe=async(s=!1)=>{try{V(!0);const n=await fetch(`${U}/api/admin/proxies/health${s?"?force=1":""}`,{headers:{Authorization:`Bearer ${a}`}});if(n.ok){const b=await n.json();G(b||{})}}catch(n){console.error("Error fetching proxy health:",n)}finally{V(!1)}};x.useEffect(()=>{if(m!=="proxies")return;Fe();const s=setInterval(()=>Fe(),6e4);return()=>clearInterval(s)},[a,m]);const st=async s=>{try{pe(s.id);const n=await fetch(`${U}/api/admin/proxies/${s.id}/health-check`,{method:"POST",headers:{Authorization:`Bearer ${a}`}});if(n.ok){const b=await n.json();G(I=>({...I,[s.id]:b})),r.show({title:b.online?"Онлайн":"Офлайн",message:b.online?`Прокси отвечает${b.latencyMs!=null?` · ${b.latencyMs} мс`:""}`:"Прокси не отвечает — стоит заменить",color:b.online?"green":"red"})}else throw new Error("check failed")}catch{r.show({title:"Ошибка",message:"Не удалось проверить прокси",color:"red"})}finally{pe(null)}};x.useEffect(()=>{m==="proxies"&&(async()=>{try{const s=await fetch(`${U}/api/admin/proxies/settings`,{headers:{Authorization:`Bearer ${a}`}});if(s.ok){const n=await s.json();X(!!n.hideOfflineFromUsers)}}catch{}})()},[a,m]);const tt=async s=>{o(!0),X(s);try{const n=await fetch(`${U}/api/admin/proxies/settings`,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({hideOfflineFromUsers:s})});if(!n.ok)throw new Error("save failed");const b=await n.json();X(!!b.hideOfflineFromUsers),r.show({title:"Сохранено",message:s?"Оффлайн-прокси скрыты от пользователей":"Пользователи снова видят все прокси",color:"green"})}catch{X(!s),r.show({title:"Ошибка",message:"Не удалось сохранить настройку",color:"red"})}finally{o(!1)}},lt=async()=>{try{const s=await fetch(`${U}/api/admin/proxies/proxyline`,{headers:{Authorization:`Bearer ${a}`}});if(s.ok){const n=await s.json();h(n),Te(n.tag||"EOFVPN"),Ae(n.enabled)}}catch{}},at=()=>{de(""),ps(),lt()},zs=async()=>{const s={enabled:ke,tag:Ce};A.trim()&&(s.apiKey=A.trim());const n=await fetch(`${U}/api/admin/proxies/proxyline`,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify(s)});if(!n.ok)return!1;const b=await n.json();return h(b),de(""),!0},rt=async()=>{be(!0);try{if(!await zs())throw new Error("save failed");r.show({title:"Сохранено",message:"Настройки ProxyLine обновлены",color:"green"})}catch{r.show({title:"Ошибка",message:"Не удалось сохранить настройки",color:"red"})}finally{be(!1)}},it=async()=>{N(!0);try{await zs();const s=await fetch(`${U}/api/admin/proxies/proxyline/sync`,{method:"POST",headers:{Authorization:`Bearer ${a}`}}),n=await s.json();if(!s.ok)throw new Error(n?.message||"sync failed");h(n.status);const b=n.result?.proxies,I=n.result?.mtproxy;r.show({title:"Синхронизация завершена",message:`Прокси: найдено ${b?.matched??0} (+${b?.imported??0} ~${b?.updated??0} -${b?.deactivated??0}). MTProxy: активно ${I?.active??0} (+${I?.imported??0} ~${I?.updated??0} -${I?.deactivated??0}).`,color:"green"}),ve(),Fe(!0)}catch(s){r.show({title:"Ошибка синхронизации",message:s?.message||"Не удалось синхронизировать",color:"red"})}finally{N(!1)}},nt=s=>{Z(n=>{const b=new Set(n);return b.has(s)?b.delete(s):b.add(s),b})},fs=()=>Z(new Set),ot=async()=>{const s=Array.from(_);if(s.length!==0){K(!0);try{const n=await fetch(`${U}/api/admin/proxies/bulk/disable`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({ids:s})});if(!n.ok)throw new Error("bulk disable failed");const b=await n.json();r.show({title:"Готово",message:`Отключено прокси: ${b.count}`,color:"orange"}),fs(),await ve()}catch{r.show({title:"Ошибка",message:"Не удалось отключить выбранные",color:"red"})}finally{K(!1)}}},ct=async()=>{const s=Array.from(_);if(s.length!==0&&confirm(`Удалить выбранные прокси (${s.length})? Действие необратимо.`)){K(!0);try{const n=await fetch(`${U}/api/admin/proxies/bulk/delete`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({ids:s})});if(!n.ok)throw new Error("bulk delete failed");const b=await n.json();r.show({title:"Удалено",message:`Удалено прокси: ${b.count}`,color:"red"}),fs(),await ve()}catch{r.show({title:"Ошибка",message:"Не удалось удалить выбранные",color:"red"})}finally{K(!1)}}},dt=s=>{if(s.forWhatsApp)return e.jsx(c,{size:"sm",c:"dimmed",children:"—"});const n=xe[s.id];if(!n)return e.jsx(Qe,{label:q?"Проверка…":"Нет данных",withArrow:!0,children:e.jsx(T,{color:"gray",variant:"light",size:"sm",children:"—"})});const b=n.online?`Отвечает${n.latencyMs!=null?` · ${n.latencyMs} мс`:""} · проверено ${Le(n.checkedAt).format("HH:mm:ss")}`:`Не отвечает · проверено ${Le(n.checkedAt).format("HH:mm:ss")}`;return e.jsx(Qe,{label:b,withArrow:!0,children:e.jsx(T,{color:n.online?"teal":"red",variant:"dot",size:"sm",children:n.online?"Онлайн":"Офлайн"})})},$e=s=>s.expiresAt?new Date(s.expiresAt)<new Date:!1,Cs=C.filter($e).length,Ee=C.filter(s=>{const n=s.ip.includes(W)||s.country.toLowerCase().includes(W.toLowerCase())||s.login.toLowerCase().includes(W.toLowerCase())||s.username&&s.username.toLowerCase().includes(W.toLowerCase());return ge==="expired"?n&&$e(s):n&&!$e(s)}),bs=Ee.length>0&&Ee.every(s=>_.has(s.id)),ht=Ee.some(s=>_.has(s.id)),ut=()=>{Z(s=>{const n=new Set(s);return bs?Ee.forEach(b=>
8n.delete(b.id)):Ee.forEach(b=>n.add(b.id)),n})},xt=async s=>{te(!0);try{let n=null;if(s.expiresAt)try{const I=s.expiresAt instanceof Date?s.expiresAt:new Date(s.expiresAt);isNaN(I.getTime())||(n=I.toISOString())}catch{n=null}const b=await fetch(`${U}/api/admin/proxies`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({country:s.country,ip:s.ip.trim(),httpPort:s.httpPort,socksPort:s.socksPort,login:s.login,password:s.password,access:s.access,userId:s.userId||void 0,expiresAt:n,isActive:s.isActive,forWhatsApp:s.forWhatsApp})});if(b.ok){const I=await b.json();E([I,...C]),L({...v,total:v.total+1,active:s.isActive?v.active+1:v.active}),r.show({title:"Прокси добавлен",message:`${I.country} - ${I.ip}`,color:"green",icon:e.jsx(Pe,{size:16})}),f.reset(),l()}else{const I=await b.json().catch(()=>({}));throw new Error(I.message||"Failed to create proxy")}}catch(n){r.show({title:"Ошибка",message:n?.message||"Не удалось добавить прокси",color:"red"})}finally{te(!1)}},pt=async s=>{if(p){te(!0);try{let n=null;if(s.expiresAt)try{const I=s.expiresAt instanceof Date?s.expiresAt:new Date(s.expiresAt);isNaN(I.getTime())||(n=I.toISOString())}catch{n=null}const b=await fetch(`${U}/api/admin/proxies/${p.id}`,{method:"PATCH",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({country:s.country,ip:s.ip.trim(),httpPort:s.httpPort,socksPort:s.socksPort,login:s.login,password:s.password,access:s.access,userId:s.userId||null,expiresAt:n,isActive:s.isActive,forWhatsApp:s.forWhatsApp})});if(b.ok){const I=await b.json();E(C.map(ye=>ye.id===p.id?I:ye)),r.show({title:"Прокси обновлён",message:`${I.country} - ${I.ip}`,color:"green",icon:e.jsx(Pe,{size:16})}),re()}else{const I=await b.json().catch(()=>({}));throw new Error(I.message||"Failed to update proxy")}}catch(n){r.show({title:"Ошибка",message:n.message||"Не удалось обновить прокси",color:"red"})}finally{te(!1)}}},mt=async s=>{try{(await fetch(`${U}/api/admin/proxies/${s.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${a}`}})).ok&&(E(C.filter(b=>b.id!==s.id)),L({...v,total:v.total-1,active:s.isActive?v.active-1:v.active}),r.show({title:"Прокси удалён",message:`${s.country} - ${s.ip}`,color:"red"}))}catch{r.show({title:"Ошибка",message:"Не удалось удалить прокси",color:"red"})}},gt=async s=>{try{const n=await fetch(`${U}/api/admin/proxies/${s.id}/toggle`,{method:"PATCH",headers:{Authorization:`Bearer ${a}`}});if(n.ok){const b=await n.json();E(C.map(I=>I.id===s.id?b:I)),L({...v,active:b.isActive?v.active+1:v.active-1}),r.show({title:b.isActive?"Прокси включён":"Прокси отключён",message:`${s.country} - ${s.ip}`,color:b.isActive?"green":"orange"})}}catch{r.show({title:"Ошибка",message:"Не удалось изменить статус",color:"red"})}},jt=async s=>{try{const n=new Date;n.setDate(n.getDate()-1);const b=await fetch(`${U}/api/admin/proxies/${s.id}`,{method:"PATCH",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({expiresAt:n.toISOString(),isActive:!1})});if(b.ok){const I=await b.json();E(C.map(ye=>ye.id===s.id?I:ye)),L({...v,active:s.isActive?v.active-1:v.active}),r.show({title:"Прокси помечен как истёкший",message:`${s.country} - ${s.ip}`,color:"orange",icon:e.jsx(rs,{size:16})})}}catch{r.show({title:"Ошибка",message:"Не удалось пометить как истёкший",color:"red"})}},ks=s=>{if(B(s),f.setValues({userId:s.userId||"",country:s.countryFlag,ip:s.ip,httpPort:s.httpPort,socksPort:s.socksPort,login:s.login,password:s.password,access:s.access,expiresAt:s.expiresAt?new Date(s.expiresAt):null,isActive:s.isActive,forWhatsApp:s.forWhatsApp||!1}),s.userId){const n=s.username||"",b=s.userEmail||"",I=b?`${b}${n?` (${n})`:""}`:n||`#${s.userId}`;t([{value:s.userId,label:I,email:b,username:n}])}else t([]);ie(""),P()},ft=s=>{B(s),us()},bt=()=>{f.reset(),t([]),ie(""),oe()};return k&&m==="proxies"?e.jsx(Gs,{}):e.jsxs(Y,{gap:"lg",children:[e.jsx(Se,{value:m,onChange:S,children:e.jsxs(Se.List,{children:[e.jsx(Se.Tab,{value:"proxies",leftSection:e.jsx(Re,{size:16}),children:"Приватные прокси"}),e.jsx(Se.Tab,{value:"mtproxy",leftSection:e.jsx(Xe,{size:16}),children:"MTProxy Telegram"}),e.jsx(Se.Tab,{value:"webproxy",leftSection:e.jsx(Re,{size:16}),children:"WEB Proxy Telegram"})]})}),m==="mtproxy"&&e.jsx(ol,{}),m==="webproxy"&&e.jsx(Qt,{}),m==="proxies"&&e.jsxs(e.Fragment,{children:[e.jsxs(g,{justify:"space-between",align:"flex-start",children:[e.jsxs(D,{children:[e.jsx(Ie,{order:2,children:"Приватные прокси"}),e.jsx(c,{c:"dimmed",size:"sm",children:"Управление прокси-серверами пользователей"})]}),e.jsxs(g,{children:[e.jsx(y,{variant:"light",color:"teal",leftSection:e.jsx(es,{size:18}),loading:q,onClick:()=>Fe(!0),children:"Проверить все"}),e.jsx(y,{variant:"light",leftSection:e.jsx(_e,{size:18}),onClick:ve,children:"Обновить"}),e.jsx(y,{variant:"light",color:"grape",leftSection:e.jsx(Es,{size:18}),onClick:at,children:"ProxyLine"}),e.jsx(y,{variant:"light",color:"cyan",leftSection:e.jsx(Tt,{size:18}),onClick:gs,children:"Фиды"}),e.jsx(y,{leftSection:e.jsx(ze,{size:18}),onClick:bt,children:"Добавить"})]})]}),e.jsx(tl,{opened:ms,onClose:js,onChanged:ve}),e.jsx(ce,{opened:xs,onClose:ss,title:"Синхронизация с ProxyLine",size:"lg",children:e.jsxs(Y,{children:[e.jsx(c,{size:"sm",c:"dimmed",children:"Автоматически подтягивает прокси из панели Proxyline и показывает их пользователям. HTTP/SOCKS-прокси импортируются только с указанным тегом; MTProxy импортируются все активные (у них в API нет тегов). Прокси, добавленные вручную, синхронизация не трогает."}),e.jsx(ne,{checked:ke,onChange:s=>Ae(s.currentTarget.checked),label:"Автосинхронизация (раз в час)",description:"Каждый час обновляет список из Proxyline автоматически."}),e.jsx(z,{label:"Тег в Proxyline",description:"Импортируются только прокси, помеченные этим тегом в панели Proxyline.",placeholder:"EOFVPN",value:Ce,onChange:s=>Te(s.currentTarget.value)}),e.jsx(qe,{label:"API-ключ Proxyline",description:j?.hasApiKey?"Ключ сохранён. Оставьте поле пустым, чтобы не менять его.":"Ключ ещё не задан. Вставьте API-ключ из панели Proxyline.",placeholder:j?.hasApiKey?"•••••••• (сохранён)":"Вставьте API-ключ",value:A,onChange:s=>de(s.currentTarget.value)}
8),j&&e.jsxs(Oe,{withBorder:!0,p:"sm",radius:"md",children:[e.jsxs(g,{justify:"space-between",children:[e.jsx(c,{size:"sm",children:"Прокси (активных / всего)"}),e.jsxs(T,{color:"grape",variant:"light",children:[j.active," / ",j.total]})]}),e.jsxs(g,{justify:"space-between",mt:6,children:[e.jsx(c,{size:"sm",children:"MTProxy (активных / всего)"}),e.jsxs(T,{color:"grape",variant:"light",children:[j.mtActive," / ",j.mtTotal]})]}),e.jsxs(g,{justify:"space-between",mt:6,children:[e.jsx(c,{size:"sm",children:"Последняя синхронизация"}),e.jsx(c,{size:"sm",c:"dimmed",children:j.lastSyncAt?Le(j.lastSyncAt).format("DD.MM.YYYY HH:mm"):"—"})]}),j.lastResult&&e.jsx(c,{size:"xs",c:"dimmed",mt:6,children:j.lastResult})]}),e.jsxs(g,{justify:"space-between",mt:"md",children:[e.jsx(y,{variant:"light",color:"teal",leftSection:e.jsx(Es,{size:16}),loading:Ue,disabled:!j?.hasApiKey&&!A.trim(),onClick:it,children:"Синхронизировать сейчас"}),e.jsxs(g,{children:[e.jsx(y,{variant:"default",onClick:ss,children:"Закрыть"}),e.jsx(y,{loading:Ne,onClick:rt,children:"Сохранить"})]})]})]})}),e.jsxs(ns,{cols:{base:2,sm:3},children:[e.jsxs(Oe,{withBorder:!0,p:"md",radius:"md",className:vs.statCard,children:[e.jsx(De,{size:"lg",radius:"md",variant:"light",color:"blue",children:e.jsx(Re,{size:20})}),e.jsxs(D,{mt:"sm",children:[e.jsx(c,{size:"xl",fw:700,children:v.total}),e.jsx(c,{size:"xs",c:"dimmed",children:"Всего прокси"})]})]}),e.jsxs(Oe,{withBorder:!0,p:"md",radius:"md",className:vs.statCard,children:[e.jsx(De,{size:"lg",radius:"md",variant:"light",color:"green",children:e.jsx(Pe,{size:20})}),e.jsxs(D,{mt:"sm",children:[e.jsx(c,{size:"xl",fw:700,children:v.active}),e.jsx(c,{size:"xs",c:"dimmed",children:"Активных"})]})]}),e.jsxs(Oe,{withBorder:!0,p:"md",radius:"md",className:vs.statCard,children:[e.jsx(De,{size:"lg",radius:"md",variant:"light",color:"violet",children:e.jsx(ds,{size:20})}),e.jsxs(D,{mt:"sm",children:[e.jsx(c,{size:"xl",fw:700,children:v.countriesCount}),e.jsx(c,{size:"xs",c:"dimmed",children:"Стран"})]})]})]}),e.jsx(me,{withBorder:!0,padding:"md",children:e.jsxs(Y,{gap:"md",children:[e.jsx(Se,{value:ge,onChange:ue,children:e.jsxs(Se.List,{children:[e.jsxs(Se.Tab,{value:"active",leftSection:e.jsx(Pe,{size:14}),children:["Активные (",v.total-Cs,")"]}),e.jsxs(Se.Tab,{value:"expired",leftSection:e.jsx(rs,{size:14}),color:"red",children:["Истекшие (",Cs,")"]})]})}),e.jsx(z,{placeholder:"Поиск по IP, стране, логину...",leftSection:e.jsx($t,{size:16}),value:W,onChange:s=>H(s.target.value)}),e.jsx(ne,{checked:fe,disabled:i,onChange:s=>tt(s.currentTarget.checked),label:"Скрывать оффлайн-прокси от пользователей",description:"Пользователи на /proxies и в расширении будут видеть только прокси, которые сейчас отвечают. Статус берётся из живой проверки."})]})}),_.size>
80&&e.jsx(Oe,{withBorder:!0,p:"sm",radius:"md",children:e.jsxs(g,{justify:"space-between",children:[e.jsxs(c,{size:"sm",fw:500,children:["Выбрано: ",_.size]}),e.jsxs(g,{gap:"xs",children:[e.jsx(y,{size:"xs",variant:"light",color:"orange",leftSection:e.jsx(rs,{size:14}),loading:le,onClick:ot,children:"Отключить выбранные"}),e.jsx(y,{size:"xs",variant:"light",color:"red",leftSection:e.jsx(Je,{size:14}),loading:le,onClick:ct,children:"Удалить выбранные"}),e.jsx(y,{size:"xs",variant:"subtle",onClick:fs,disabled:le,children:"Снять выделение"})]})]})}),e.jsxs(me,{withBorder:!0,padding:0,children:[e.jsxs(d,{striped:!0,highlightOnHover:!0,children:[e.jsx(d.Thead,{children:e.jsxs(d.Tr,{children:[e.jsx(d.Th,{style:{width:40},children:e.jsx(Hs,{"aria-label":"Выбрать все",checked:bs,indeterminate:!bs&&ht,onChange:ut})}),e.jsx(d.Th,{children:"Страна"}),e.jsx(d.Th,{children:"IP / Порты"}),e.jsx(d.Th,{children:"Пользователь"}),e.jsx(d.Th,{children:"Логин"}),e.jsx(d.Th,{children:"Истекает"}),e.jsx(d.Th,{children:"Статус"}),e.jsx(d.Th,{children:"Живой статус"}),e.jsx(d.Th,{})]})}),e.jsx(d.Tbody,{children:Ee.map(s=>e.jsxs(d.Tr,{style:{opacity:s.isActive?1:.6},children:[e.jsx(d.Td,{children:e.jsx(Hs,{"aria-label":"Выбрать прокси",checked:_.has(s.id),onChange:()=>nt(s.id)})}),e.jsx(d.Td,{children:e.jsxs(g,{gap:"xs",children:[e.jsx(Me,{flagEmoji:s.countryFlag,size:24}),e.jsx(c,{size:"sm",children:s.country}),s.forWhatsApp&&e.jsx(T,{size:"xs",color:"green",variant:"light",children:"WhatsApp"}),s.source==="proxyline"&&e.jsx(T,{size:"xs",color:"grape",variant:"light",children:"Proxyline"})]})}),e.jsx(d.Td,{children:e.jsxs(D,{children:[e.jsx(se,{children:s.ip}),e.jsxs(c,{size:"xs",c:"dimmed",children:["HTTP: ",s.httpPort," | SOCKS5: ",s.socksPort]})]})}),e.jsx(d.Td,{children:e.jsx(g,{gap:"xs",children:s.access==="personal"&&s.userId?e.jsxs(e.Fragment,{children:[e.jsx(T,{size:"sm",color:"grape",variant:"light",leftSection:e.jsx(as,{size:10}),children:"Персональный"}),e.jsx(c,{size:"xs",c:"dimmed",children:s.userEmail||s.username||`#${s.userId}`})]}):s.access==="private"&&s.userId?e.jsxs(e.Fragment,{children:[e.jsx(Bs,{size:"xs",radius:"xl",color:"brand",children:(s.username||"U")[0].toUpperCase()}),e.jsx(c,{size:"sm",children:s.username||"Unknown"})]}):s.access==="private"?e.jsx(T,{size:"sm",color:"gray",variant:"light",children:"🔒 Приватный"}):s.access==="free"?e.jsx(T,{size:"sm",color:"teal",variant:"light",children:"🆓 Free"}):s.access==="all"?e.jsx(T,{size:"sm",color:"blue",variant:"light",children:"🌍 Все"}):e.jsx(T,{size:"sm",color:"violet",variant:"light",children:"👥 Premium"})})}),e.jsx(d.Td,{children:e.jsx(se,{children:s.login})}),e.jsx(d.Td,{children:e.jsx(c,{size:"sm",c:$e(s)?"red":void 0,fw:$e(s)?500:void 0,children:s.expiresAt?Le(s.expiresAt).format("DD.MM.YYYY"):"—"})}),e.jsx(d.Td,{children:$e(s)?e.jsx(T,{color:"red",variant:"light",children:"Истёк"}):e.jsx(T,{color:s.isActive?"green":"gray",variant:"light",children:s.isActive?"Активен":"Отключён"})}),e.jsx(d.Td,{children:dt(s)}),e.jsx(d.Td,{children:e.jsxs(R,{position:"bottom-end",withArrow:!0,children:[e.jsx(R.Target,{children:e.jsx(os,{variant:"subtle",color:"gray",children:e.jsx(Xs,{size:16})})}),e.jsxs(R.Dropdown,{children:[e.jsx(R.Item,{leftSection:e.jsx(Qs,{size:14}),onClick:()=>ft(s),children:"Просмотр"}),!s.forWhatsApp&&e.jsx(R.Item,{leftSection:e.jsx(es,{size:14}),onClick:()=>st(s),disabled:je===s.id,children:je===s.id?"Проверяю…":"Проверить сейчас"}),e.jsx(R.Item,{leftSection:e.jsx(Ve,{size:14}),onClick:()=>{navigator.clipboard.writeText(`${s.ip}:${s.httpPort}:${s.login}:${s.password}`),r.show({title:"Скопировано",message:"HTTP прокси",color:"green"})},children:"Копировать HTTP"}),e.jsx(R.Item,{leftSection:e.jsx(Ze,{size:14}),onClick:()=>ks(s),children:"Редактировать"}),e.jsx(R.Item,{leftSection:s.isActive?e.jsx(Re,{size:14}):e.jsx(Pe,{size:14}),color:s.isActive?"orange":"green",onClick:()=>gt(s),children:s.isActive?"Отключить":"Включить"}),!$e(s)&&e.jsx(R.Item,{leftSection:e.jsx(rs,{size:14}),color:"orange",onClick:()=>jt(s),children:"Пометить истёкшим"}),e.jsx(R.Divider,{}),e.jsx(R.Item,{leftSection:e.jsx(Je,{size:14}),color:"red",onClick:()=>mt(s),children:"Удалить"})]})]})})]},s.id))})]}),Ee.length===0&&e.jsxs(D,{p:"xl",ta:"center",children:[e.jsx(Re,{size:48,stroke:1,color:"var(--mantine-color-dimmed)"}),e.jsx(c,{c:"dimmed",mt:"md",children:"Прокси не найдены"})]})]}),e.jsx(ce,{opened:ee,onClose:l,title:e.jsxs(g,{children:[e.jsx(De,{size:"lg",radiu
8s:"md",color:"brand",children:e.jsx(ze,{size:20})}),e.jsx(c,{fw:600,children:"Новый прокси"})]}),size:"lg",children:e.jsx("form",{onSubmit:f.onSubmit(xt),children:e.jsxs(Y,{gap:"md",children:[e.jsx(ne,{label:"Для WhatsApp",description:"Прокси для WhatsApp (другой формат данных)",...f.getInputProps("forWhatsApp",{type:"checkbox"})}),e.jsx(J,{label:"Страна",placeholder:"Выберите страну",data:Ks,leftSection:e.jsx(Me,{flagEmoji:f.values.country,size:20}),renderOption:_s,searchable:!0,...f.getInputProps("country")}),e.jsx(z,{label:"IP или домен",placeholder:"45.139.30.109 или proxy.example.com",...f.getInputProps("ip")}),e.jsxs(g,{grow:!0,children:[e.jsx(ae,{label:f.values.forWhatsApp?"Порт для чата":"Порт HTTP(S)",placeholder:f.values.forWhatsApp?"5222":"1506",min:1,max:65535,...f.getInputProps("httpPort")}),e.jsx(ae,{label:f.values.forWhatsApp?"Порт для медиа":"Порт SOCKS5",placeholder:f.values.forWhatsApp?"443":"11506",min:1,max:65535,...f.getInputProps("socksPort")})]}),!f.values.forWhatsApp&&e.jsxs(g,{grow:!0,children:[e.jsx(z,{label:"Логин",placeholder:"user298608",...f.getInputProps("login")}),e.jsx(z,{label:"Пароль",placeholder:"td5hax",...f.getInputProps("password")})]}),e.jsx(J,{label:"Доступ",data:[{value:"prime-public",label:"👥 Для Premium пользователей"},{value:"free",label:"🆓 Для Free пользователей"},{value:"all",label:"🌍 Для всех пользователей"},{value:"personal",label:"👤 Персональный (для конкретного пользователя)"},{value:"private",label:"🔒 Приватный (скрыт от всех)"}],...f.getInputProps("access"),onChange:s=>{f.setFieldValue("access",s||"prime-public"),s!=="personal"&&s!=="private"&&f.setFieldValue("userId","")}}),(f.values.access==="personal"||f.values.access==="private")&&e.jsx(J,{label:"Пользователь",description:f.values.access==="personal"?"Прокси будет показан только этому пользователю в его панели":"Опционально: привязать приватный прокси к конкретному пользователю",placeholder:"Начните вводить email, username или Telegram…",searchable:!0,clearable:!0,nothingFoundMessage:u?"Поиск…":O.length<2?"Введите минимум 2 символа":"Пользователи не найдены",data:ts,value:f.values.userId||null,onChange:s=>f.setFieldValue("userId",s||""),searchValue:O,onSearchChange:ie,leftSection:e.jsx(as,{size:16}),error:f.errors.userId,withAsterisk:f.values.access==="personal",filter:({options:s})=>s}),e.jsx(Ye,{label:"Срок действия",placeholder:"Выберите дату или оставьте пустым",clearable:!0,minDate:new Date,...f.getInputProps("expiresAt")}),e.jsx(ne,{label:"Активен",description:"Прокси доступен для использования",...f.getInputProps("isActive",{type:"checkbox"})}),e.jsxs(g,{justify:"flex-end",mt:"md",children:[e.jsx(y,{variant:"subtle",onClick:l,children:"Отмена"}),e.jsx(y,{type:"submit",loading:F,children:"Добавить"})]})]})})}),e.jsx(ce,{opened:w,onClose:re,title:e.jsxs(g,{children:[e.jsx(De,{size:"lg",radius:"md",color:"blue",children:e.jsx(Ze,{size:20})}),e.jsx(c,{fw:600,children:"Редактирование прокси"})]}),size:"lg",children:e.jsx("form",{onSubmit:f.onSubmit(pt),children:e.jsxs(Y,{gap:"md",children:[e.jsx(ne,{label:"Для WhatsApp",description:"Прокси для WhatsApp (другой формат данных)",...f.getInputProps("forWhatsApp",{type:"checkbox"})}),e.jsx(J,{label:"Страна",placeholder:"Выберите страну",data:Ks,leftSection:e.jsx(Me,{flagEmoji:f.values.country,size:20}),renderOption:_s,searchable:!0,...f.getInputProps("country")}),e.jsx(z,{label:"IP или домен",placeholder:"45.139.30.109 или proxy.example.com",...f.getInputProps("ip")}),e.jsxs(g,{grow:!0,children:[e.jsx(ae,{label:f.values.forWhatsApp?"Порт для чата":"Порт HTTP(S)",placeholder:f.values.forWhatsApp?"5222":"1506",min:1,max:65535,...f.getInputProps("httpPort")}),e.jsx(ae,{label:f.values.forWhatsApp?"Порт для медиа":"Порт SOCKS5",placeholder:f.values.forWhatsApp?"443":"11506",min:1,max:65535,...f.getInputProps("socksPort")})]}),!f.values.forWhatsApp&&e.jsxs(g,{grow:!0,children:[e.jsx(z,{label:"ЛогиÐ
8½",placeholder:"user298608",...f.getInputProps("login")}),e.jsx(z,{label:"Пароль",placeholder:"td5hax",...f.getInputProps("password")})]}),e.jsx(J,{label:"Доступ",data:[{value:"prime-public",label:"👥 Для Premium пользователей"},{value:"free",label:"🆓 Для Free пользователей"},{value:"all",label:"🌍 Для всех пользователей"},{value:"personal",label:"👤 Персональный (для конкретного пользователя)"},{value:"private",label:"🔒 Приватный (скрыт от всех)"}],...f.getInputProps("access"),onChange:s=>{f.setFieldValue("access",s||"prime-public"),s!=="personal"&&s!=="private"&&f.setFieldValue("userId","")}}),(f.values.access==="personal"||f.values.access==="private")&&e.jsx(J,{label:"Пользователь",description:f.values.access==="personal"?"Прокси будет показан только этому пользователю в его панели":"Опционально: привязать приватный прокси к конкретному пользователю",placeholder:"Начните вводить email, username или Telegram…",searchable:!0,clearable:!0,nothingFoundMessage:u?"Поиск…":O.length<2?"Введите минимум 2 символа":"Пользователи не найдены",data:ts,value:f.values.userId||null,onChange:s=>f.setFieldValue("userId",s||""),searchValue:O,onSearchChange:ie,leftSection:e.jsx(as,{size:16}),error:f.errors.userId,withAsterisk:f.values.access==="personal",filter:({options:s})=>s}),e.jsx(Ye,{label:"Срок действия",placeholder:"Выберите дату или оставьте пустым",clearable:!0,minDate:new Date,...f.getInputProps("expiresAt")}),e.jsx(ne,{label:"Активен",description:"Прокси доступен для использования",...f.getInputProps("isActive",{type:"checkbox"})}),e.jsxs(g,{justify:"flex-end",mt:"md",children:[e.jsx(y,{variant:"subtle",onClick:re,children:"Отмена"}),e.jsx(y,{type:"submit",loading:F,children:"Сохранить"})]})]})})}),e.jsx(ce,{opened:Ge,onClose:We,title:p&&e.jsxs(g,{children:[e.jsx(Me,{flagEmoji:p.countryFlag,size:28}),e.jsxs(D,{children:[e.jsx(c,{fw:600,children:p.country}),e.jsx(c,{size:"xs",c:"dimmed",children:p.username||"Для всех"})]})]}),size:"md",children:p&&e.jsxs(Y,{gap:"md",children:[e.jsxs(ns,{cols:2,children:[e.jsxs(D,{children:[e.jsx(c,{size:"xs",c:"dimmed",children:"IP адрес"}),e.jsx(se,{children:p.ip})]}),e.jsxs(D,{children:[e.jsx(c,{size:"xs",c:"dimmed",children:"Статус"}),e.jsx(T,{color:p.isActive?"green":"gray",variant:"light",children:p.isActive?"Активен":"Отключён"})]}),e.jsxs(D,{children:[e.jsx(c,{size:"xs",c:"dimmed",children:"HTTP(S) порт"}),e.jsx(se,{children:p.httpPort})]}),e.jsxs(D,{children:[e.jsx(c,{size:"xs",c:"dimmed",children:"SOCKS5 порт"}),e.jsx(se,{children:p.socksPort})]}),e.jsxs(D,{children:[e.jsx(c,{size:"xs",c:"dimmed",children:"Логин"}),e.jsx(se,{children:p.login})]}),e.jsxs(D,{children:[e.jsx(c,{size:"xs",c:"dimmed",children:"Пароль"}),e.jsx(se,{children:p.password})]}),e.jsxs(D,{children:[e.jsx(c,{size:"xs",c:"dimmed",children:"Доступ"}),p.access==="free"?e.jsx(T,{size:"sm",color:"teal",variant:"light",children:"🆓 Free"}):p.access==="all"?e.jsx(T,{size:"sm",color:"blue",variant:"light",children:"🌍 Все"}):p.access==="personal"?e.jsx(T,{size:"sm",color:"grape",variant:"light",leftSection:e.jsx(as,{size:10}),children:"Персональный"}):p.access==="private"?e.jsx(T,{size:"sm",color:"gray",variant:"light",children:"🔒 Приватный"}):e.jsx(T,{size:"sm",color:"violet",variant:"light",children:"👥 Premium"})]}),(p.access==="personal"||p.access==="private")&&p.userId&&e.jsxs(D,{style:{gridColumn:"1 / -1"},children:[e.jsx(c,{size:"xs",c:"dimmed",children:"Пользователь"}),e.jsxs(g,{gap:"xs",mt:4,children:[e.jsx(Bs,{size:"sm",radius:"xl",color:"brand",children:(p.username||p.userEmail||"U")[0].toUpperCase()}),e.jsxs(D,{children:[p.userEmail&&e.jsx(c,{size:"sm",fw:500,children:p.userEmail}),p.username&&e.jsxs(c,{size:"xs",c:"dimmed",children:["@",p.username]})]})]})]}),e.jsxs(D,{children:[e.jsx(c,{size:"xs",c:"dimmed",children:"Истекает"}),e.jsx(c,{size:"sm",children:p.expiresAt?Le(p.expiresAt).format("DD.MM.YYYY"):"Бессрочно"})]})]}),e.jsxs(g,{grow:!0,mt:"md",children:[e.jsx(y,{variant:"light",leftSection:e.jsx(Ve,{size:16}),onClick:()=>{navigator.clipboard.writeText(`${p.ip}:${p.httpPort}:${p.login}:${p.password}`),r.show({title:"Скопировано",message:"HTTP прокси",color:"green"})},children:"Копировать HTTP"}),e.jsx(y,{variant:"light",color:"violet",leftSection:e.jsx(Ve,{size:16}),onClick:()=>{navigator.clipboard.writeText(`${p.ip}:${p.socksPort}:${p.login}:${p.password}`),r.show({title:"Скопировано",message:"SOCKS5 прокси",color:"green"})},children:"Копировать SOCKS5"})]}),e.jsxs(g,{justify:"flex-end",children:[e.jsx(y,{variant:"light",onClick:We,children:"Закрыть"}),e.jsx(y,{leftSection:e.jsx(Ze,{size:16}),onClick:()=>{We(),ks(p)},children:"Редактировать"})]})]})})]})]})}export{Hl as AdminProxiesPage};

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.