diff --git a/FRONT/BUILD/resources/app/.gitignore b/FRONT/BUILD/resources/app/.gitignore index b2d59d1..30bc162 100644 --- a/FRONT/BUILD/resources/app/.gitignore +++ b/FRONT/BUILD/resources/app/.gitignore @@ -1,2 +1 @@ -/node_modules -/dist \ No newline at end of file +/node_modules \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/dist/assets/icon-4fEsvohP.png b/FRONT/BUILD/resources/app/dist/assets/icon-4fEsvohP.png new file mode 100644 index 0000000..b39bc87 Binary files /dev/null and b/FRONT/BUILD/resources/app/dist/assets/icon-4fEsvohP.png differ diff --git a/FRONT/BUILD/resources/app/public/assets/images/back1.svg b/FRONT/BUILD/resources/app/dist/assets/images/back1.svg similarity index 100% rename from FRONT/BUILD/resources/app/public/assets/images/back1.svg rename to FRONT/BUILD/resources/app/dist/assets/images/back1.svg diff --git a/FRONT/BUILD/resources/app/public/assets/images/bigLogo.svg b/FRONT/BUILD/resources/app/dist/assets/images/bigLogo.svg similarity index 100% rename from FRONT/BUILD/resources/app/public/assets/images/bigLogo.svg rename to FRONT/BUILD/resources/app/dist/assets/images/bigLogo.svg diff --git a/FRONT/BUILD/resources/app/public/assets/images/bukvi.svg b/FRONT/BUILD/resources/app/dist/assets/images/bukvi.svg similarity index 100% rename from FRONT/BUILD/resources/app/public/assets/images/bukvi.svg rename to FRONT/BUILD/resources/app/dist/assets/images/bukvi.svg diff --git a/FRONT/BUILD/resources/app/public/assets/images/cancel.svg b/FRONT/BUILD/resources/app/dist/assets/images/cancel.svg similarity index 100% rename from FRONT/BUILD/resources/app/public/assets/images/cancel.svg rename to FRONT/BUILD/resources/app/dist/assets/images/cancel.svg diff --git a/FRONT/BUILD/resources/app/public/assets/images/check.svg b/FRONT/BUILD/resources/app/dist/assets/images/check.svg similarity index 100% rename from FRONT/BUILD/resources/app/public/assets/images/check.svg rename to FRONT/BUILD/resources/app/dist/assets/images/check.svg diff --git a/FRONT/BUILD/resources/app/public/assets/images/edit.svg b/FRONT/BUILD/resources/app/dist/assets/images/edit.svg similarity index 100% rename from FRONT/BUILD/resources/app/public/assets/images/edit.svg rename to FRONT/BUILD/resources/app/dist/assets/images/edit.svg diff --git a/FRONT/BUILD/resources/app/public/assets/images/ethernet.svg b/FRONT/BUILD/resources/app/dist/assets/images/ethernet.svg similarity index 100% rename from FRONT/BUILD/resources/app/public/assets/images/ethernet.svg rename to FRONT/BUILD/resources/app/dist/assets/images/ethernet.svg diff --git a/FRONT/BUILD/resources/app/dist/assets/images/gossys.png b/FRONT/BUILD/resources/app/dist/assets/images/gossys.png new file mode 100644 index 0000000..5b01b0e Binary files /dev/null and b/FRONT/BUILD/resources/app/dist/assets/images/gossys.png differ diff --git a/FRONT/BUILD/resources/app/public/assets/images/logo.svg b/FRONT/BUILD/resources/app/dist/assets/images/logo.svg similarity index 100% rename from FRONT/BUILD/resources/app/public/assets/images/logo.svg rename to FRONT/BUILD/resources/app/dist/assets/images/logo.svg diff --git a/FRONT/BUILD/resources/app/public/assets/images/signal.svg b/FRONT/BUILD/resources/app/dist/assets/images/signal.svg similarity index 100% rename from FRONT/BUILD/resources/app/public/assets/images/signal.svg rename to FRONT/BUILD/resources/app/dist/assets/images/signal.svg diff --git a/FRONT/BUILD/resources/app/public/assets/images/vite.svg b/FRONT/BUILD/resources/app/dist/assets/images/vite.svg similarity index 100% rename from FRONT/BUILD/resources/app/public/assets/images/vite.svg rename to FRONT/BUILD/resources/app/dist/assets/images/vite.svg diff --git a/FRONT/BUILD/resources/app/public/assets/images/vpn.svg b/FRONT/BUILD/resources/app/dist/assets/images/vpn.svg similarity index 100% rename from FRONT/BUILD/resources/app/public/assets/images/vpn.svg rename to FRONT/BUILD/resources/app/dist/assets/images/vpn.svg diff --git a/FRONT/BUILD/resources/app/public/assets/images/wifi.svg b/FRONT/BUILD/resources/app/dist/assets/images/wifi.svg similarity index 100% rename from FRONT/BUILD/resources/app/public/assets/images/wifi.svg rename to FRONT/BUILD/resources/app/dist/assets/images/wifi.svg diff --git a/FRONT/BUILD/resources/app/dist/assets/index-CAnb47c8.js b/FRONT/BUILD/resources/app/dist/assets/index-CAnb47c8.js new file mode 100644 index 0000000..2ee490f --- /dev/null +++ b/FRONT/BUILD/resources/app/dist/assets/index-CAnb47c8.js @@ -0,0 +1,913 @@ +(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))i(s);new MutationObserver(s=>{for(const a of s)if(a.type==="childList")for(const u of a.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&i(u)}).observe(document,{childList:!0,subtree:!0});function r(s){const a={};return s.integrity&&(a.integrity=s.integrity),s.referrerPolicy&&(a.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?a.credentials="include":s.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function i(s){if(s.ep)return;s.ep=!0;const a=r(s);fetch(s.href,a)}})();function rt(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Wf={exports:{}},ns={},qf={exports:{}},Re={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var mm;function KS(){if(mm)return Re;mm=1;var e=Symbol.for("react.element"),t=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),i=Symbol.for("react.strict_mode"),s=Symbol.for("react.profiler"),a=Symbol.for("react.provider"),u=Symbol.for("react.context"),f=Symbol.for("react.forward_ref"),d=Symbol.for("react.suspense"),h=Symbol.for("react.memo"),g=Symbol.for("react.lazy"),m=Symbol.iterator;function y(T){return T===null||typeof T!="object"?null:(T=m&&T[m]||T["@@iterator"],typeof T=="function"?T:null)}var v={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},S=Object.assign,E={};function k(T,H,Z){this.props=T,this.context=H,this.refs=E,this.updater=Z||v}k.prototype.isReactComponent={},k.prototype.setState=function(T,H){if(typeof T!="object"&&typeof T!="function"&&T!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,T,H,"setState")},k.prototype.forceUpdate=function(T){this.updater.enqueueForceUpdate(this,T,"forceUpdate")};function C(){}C.prototype=k.prototype;function R(T,H,Z){this.props=T,this.context=H,this.refs=E,this.updater=Z||v}var N=R.prototype=new C;N.constructor=R,S(N,k.prototype),N.isPureReactComponent=!0;var b=Array.isArray,M=Object.prototype.hasOwnProperty,L={current:null},j={key:!0,ref:!0,__self:!0,__source:!0};function G(T,H,Z){var te,ie={},le=null,de=null;if(H!=null)for(te in H.ref!==void 0&&(de=H.ref),H.key!==void 0&&(le=""+H.key),H)M.call(H,te)&&!j.hasOwnProperty(te)&&(ie[te]=H[te]);var re=arguments.length-2;if(re===1)ie.children=Z;else if(1>>1,H=A[T];if(0>>1;Ts(ie,z))les(de,ie)?(A[T]=de,A[le]=z,T=le):(A[T]=ie,A[te]=z,T=te);else if(les(de,z))A[T]=de,A[le]=z,T=le;else break e}}return O}function s(A,O){var z=A.sortIndex-O.sortIndex;return z!==0?z:A.id-O.id}if(typeof performance=="object"&&typeof performance.now=="function"){var a=performance;e.unstable_now=function(){return a.now()}}else{var u=Date,f=u.now();e.unstable_now=function(){return u.now()-f}}var d=[],h=[],g=1,m=null,y=3,v=!1,S=!1,E=!1,k=typeof setTimeout=="function"?setTimeout:null,C=typeof clearTimeout=="function"?clearTimeout:null,R=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function N(A){for(var O=r(h);O!==null;){if(O.callback===null)i(h);else if(O.startTime<=A)i(h),O.sortIndex=O.expirationTime,t(d,O);else break;O=r(h)}}function b(A){if(E=!1,N(A),!S)if(r(d)!==null)S=!0,F(M);else{var O=r(h);O!==null&&q(b,O.startTime-A)}}function M(A,O){S=!1,E&&(E=!1,C(G),G=-1),v=!0;var z=y;try{for(N(O),m=r(d);m!==null&&(!(m.expirationTime>O)||A&&!ee());){var T=m.callback;if(typeof T=="function"){m.callback=null,y=m.priorityLevel;var H=T(m.expirationTime<=O);O=e.unstable_now(),typeof H=="function"?m.callback=H:m===r(d)&&i(d),N(O)}else i(d);m=r(d)}if(m!==null)var Z=!0;else{var te=r(h);te!==null&&q(b,te.startTime-O),Z=!1}return Z}finally{m=null,y=z,v=!1}}var L=!1,j=null,G=-1,V=5,Y=-1;function ee(){return!(e.unstable_now()-YA||125T?(A.sortIndex=z,t(h,A),r(d)===null&&A===r(h)&&(E?(C(G),G=-1):E=!0,q(b,z-T))):(A.sortIndex=H,t(d,A),S||v||(S=!0,F(M))),A},e.unstable_shouldYield=ee,e.unstable_wrapCallback=function(A){var O=y;return function(){var z=y;y=O;try{return A.apply(this,arguments)}finally{y=z}}}}(Gf)),Gf}var Sm;function eE(){return Sm||(Sm=1,Xf.exports=ZS()),Xf.exports}/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Em;function tE(){if(Em)return Nt;Em=1;var e=As(),t=eE();function r(n){for(var o="https://reactjs.org/docs/error-decoder.html?invariant="+n,l=1;l"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),d=Object.prototype.hasOwnProperty,h=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,g={},m={};function y(n){return d.call(m,n)?!0:d.call(g,n)?!1:h.test(n)?m[n]=!0:(g[n]=!0,!1)}function v(n,o,l,c){if(l!==null&&l.type===0)return!1;switch(typeof o){case"function":case"symbol":return!0;case"boolean":return c?!1:l!==null?!l.acceptsBooleans:(n=n.toLowerCase().slice(0,5),n!=="data-"&&n!=="aria-");default:return!1}}function S(n,o,l,c){if(o===null||typeof o>"u"||v(n,o,l,c))return!0;if(c)return!1;if(l!==null)switch(l.type){case 3:return!o;case 4:return o===!1;case 5:return isNaN(o);case 6:return isNaN(o)||1>o}return!1}function E(n,o,l,c,p,x,_){this.acceptsBooleans=o===2||o===3||o===4,this.attributeName=c,this.attributeNamespace=p,this.mustUseProperty=l,this.propertyName=n,this.type=o,this.sanitizeURL=x,this.removeEmptyString=_}var k={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(n){k[n]=new E(n,0,!1,n,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(n){var o=n[0];k[o]=new E(o,1,!1,n[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(n){k[n]=new E(n,2,!1,n.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(n){k[n]=new E(n,2,!1,n,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(n){k[n]=new E(n,3,!1,n.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(n){k[n]=new E(n,3,!0,n,null,!1,!1)}),["capture","download"].forEach(function(n){k[n]=new E(n,4,!1,n,null,!1,!1)}),["cols","rows","size","span"].forEach(function(n){k[n]=new E(n,6,!1,n,null,!1,!1)}),["rowSpan","start"].forEach(function(n){k[n]=new E(n,5,!1,n.toLowerCase(),null,!1,!1)});var C=/[\-:]([a-z])/g;function R(n){return n[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(n){var o=n.replace(C,R);k[o]=new E(o,1,!1,n,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(n){var o=n.replace(C,R);k[o]=new E(o,1,!1,n,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(n){var o=n.replace(C,R);k[o]=new E(o,1,!1,n,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(n){k[n]=new E(n,1,!1,n.toLowerCase(),null,!1,!1)}),k.xlinkHref=new E("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(n){k[n]=new E(n,1,!1,n.toLowerCase(),null,!0,!0)});function N(n,o,l,c){var p=k.hasOwnProperty(o)?k[o]:null;(p!==null?p.type!==0:c||!(2$||p[_]!==x[$]){var D=` +`+p[_].replace(" at new "," at ");return n.displayName&&D.includes("")&&(D=D.replace("",n.displayName)),D}while(1<=_&&0<=$);break}}}finally{Z=!1,Error.prepareStackTrace=l}return(n=n?n.displayName||n.name:"")?H(n):""}function ie(n){switch(n.tag){case 5:return H(n.type);case 16:return H("Lazy");case 13:return H("Suspense");case 19:return H("SuspenseList");case 0:case 2:case 15:return n=te(n.type,!1),n;case 11:return n=te(n.type.render,!1),n;case 1:return n=te(n.type,!0),n;default:return""}}function le(n){if(n==null)return null;if(typeof n=="function")return n.displayName||n.name||null;if(typeof n=="string")return n;switch(n){case j:return"Fragment";case L:return"Portal";case V:return"Profiler";case G:return"StrictMode";case J:return"Suspense";case I:return"SuspenseList"}if(typeof n=="object")switch(n.$$typeof){case ee:return(n.displayName||"Context")+".Consumer";case Y:return(n._context.displayName||"Context")+".Provider";case ne:var o=n.render;return n=n.displayName,n||(n=o.displayName||o.name||"",n=n!==""?"ForwardRef("+n+")":"ForwardRef"),n;case U:return o=n.displayName||null,o!==null?o:le(n.type)||"Memo";case F:o=n._payload,n=n._init;try{return le(n(o))}catch{}}return null}function de(n){var o=n.type;switch(n.tag){case 24:return"Cache";case 9:return(o.displayName||"Context")+".Consumer";case 10:return(o._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return n=o.render,n=n.displayName||n.name||"",o.displayName||(n!==""?"ForwardRef("+n+")":"ForwardRef");case 7:return"Fragment";case 5:return o;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return le(o);case 8:return o===G?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof o=="function")return o.displayName||o.name||null;if(typeof o=="string")return o}return null}function re(n){switch(typeof n){case"boolean":case"number":case"string":case"undefined":return n;case"object":return n;default:return""}}function he(n){var o=n.type;return(n=n.nodeName)&&n.toLowerCase()==="input"&&(o==="checkbox"||o==="radio")}function Se(n){var o=he(n)?"checked":"value",l=Object.getOwnPropertyDescriptor(n.constructor.prototype,o),c=""+n[o];if(!n.hasOwnProperty(o)&&typeof l<"u"&&typeof l.get=="function"&&typeof l.set=="function"){var p=l.get,x=l.set;return Object.defineProperty(n,o,{configurable:!0,get:function(){return p.call(this)},set:function(_){c=""+_,x.call(this,_)}}),Object.defineProperty(n,o,{enumerable:l.enumerable}),{getValue:function(){return c},setValue:function(_){c=""+_},stopTracking:function(){n._valueTracker=null,delete n[o]}}}}function we(n){n._valueTracker||(n._valueTracker=Se(n))}function ge(n){if(!n)return!1;var o=n._valueTracker;if(!o)return!0;var l=o.getValue(),c="";return n&&(c=he(n)?n.checked?"true":"false":n.value),n=c,n!==l?(o.setValue(n),!0):!1}function Pe(n){if(n=n||(typeof document<"u"?document:void 0),typeof n>"u")return null;try{return n.activeElement||n.body}catch{return n.body}}function $e(n,o){var l=o.checked;return z({},o,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:l??n._wrapperState.initialChecked})}function Ke(n,o){var l=o.defaultValue==null?"":o.defaultValue,c=o.checked!=null?o.checked:o.defaultChecked;l=re(o.value!=null?o.value:l),n._wrapperState={initialChecked:c,initialValue:l,controlled:o.type==="checkbox"||o.type==="radio"?o.checked!=null:o.value!=null}}function Xt(n,o){o=o.checked,o!=null&&N(n,"checked",o,!1)}function Cr(n,o){Xt(n,o);var l=re(o.value),c=o.type;if(l!=null)c==="number"?(l===0&&n.value===""||n.value!=l)&&(n.value=""+l):n.value!==""+l&&(n.value=""+l);else if(c==="submit"||c==="reset"){n.removeAttribute("value");return}o.hasOwnProperty("value")?_r(n,o.type,l):o.hasOwnProperty("defaultValue")&&_r(n,o.type,re(o.defaultValue)),o.checked==null&&o.defaultChecked!=null&&(n.defaultChecked=!!o.defaultChecked)}function lo(n,o,l){if(o.hasOwnProperty("value")||o.hasOwnProperty("defaultValue")){var c=o.type;if(!(c!=="submit"&&c!=="reset"||o.value!==void 0&&o.value!==null))return;o=""+n._wrapperState.initialValue,l||o===n.value||(n.value=o),n.defaultValue=o}l=n.name,l!==""&&(n.name=""),n.defaultChecked=!!n._wrapperState.initialChecked,l!==""&&(n.name=l)}function _r(n,o,l){(o!=="number"||Pe(n.ownerDocument)!==n)&&(l==null?n.defaultValue=""+n._wrapperState.initialValue:n.defaultValue!==""+l&&(n.defaultValue=""+l))}var Gt=Array.isArray;function fn(n,o,l,c){if(n=n.options,o){o={};for(var p=0;p"+o.valueOf().toString()+"",o=uo.firstChild;n.firstChild;)n.removeChild(n.firstChild);for(;o.firstChild;)n.appendChild(o.firstChild)}});function Nr(n,o){if(o){var l=n.firstChild;if(l&&l===n.lastChild&&l.nodeType===3){l.nodeValue=o;return}}n.textContent=o}var br={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Ju=["Webkit","ms","Moz","O"];Object.keys(br).forEach(function(n){Ju.forEach(function(o){o=o+n.charAt(0).toUpperCase()+n.substring(1),br[o]=br[n]})});function qs(n,o,l){return o==null||typeof o=="boolean"||o===""?"":l||typeof o!="number"||o===0||br.hasOwnProperty(n)&&br[n]?(""+o).trim():o+"px"}function Ys(n,o){n=n.style;for(var l in o)if(o.hasOwnProperty(l)){var c=l.indexOf("--")===0,p=qs(l,o[l],c);l==="float"&&(l="cssFloat"),c?n.setProperty(l,p):n[l]=p}}var Zu=z({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function hi(n,o){if(o){if(Zu[n]&&(o.children!=null||o.dangerouslySetInnerHTML!=null))throw Error(r(137,n));if(o.dangerouslySetInnerHTML!=null){if(o.children!=null)throw Error(r(60));if(typeof o.dangerouslySetInnerHTML!="object"||!("__html"in o.dangerouslySetInnerHTML))throw Error(r(61))}if(o.style!=null&&typeof o.style!="object")throw Error(r(62))}}function pi(n,o){if(n.indexOf("-")===-1)return typeof o.is=="string";switch(n){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var gi=null;function mi(n){return n=n.target||n.srcElement||window,n.correspondingUseElement&&(n=n.correspondingUseElement),n.nodeType===3?n.parentNode:n}var yi=null,Gn=null,Kn=null;function Xs(n){if(n=Bi(n)){if(typeof yi!="function")throw Error(r(280));var o=n.stateNode;o&&(o=kl(o),yi(n.stateNode,n.type,o))}}function xi(n){Gn?Kn?Kn.push(n):Kn=[n]:Gn=n}function Gs(){if(Gn){var n=Gn,o=Kn;if(Kn=Gn=null,Xs(n),o)for(n=0;n>>=0,n===0?32:31-(ac(n)/uc|0)|0}var mo=64,yo=4194304;function kn(n){switch(n&-n){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return n&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return n&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return n}}function xo(n,o){var l=n.pendingLanes;if(l===0)return 0;var c=0,p=n.suspendedLanes,x=n.pingedLanes,_=l&268435455;if(_!==0){var $=_&~p;$!==0?c=kn($):(x&=_,x!==0&&(c=kn(x)))}else _=l&~p,_!==0?c=kn(_):x!==0&&(c=kn(x));if(c===0)return 0;if(o!==0&&o!==c&&(o&p)===0&&(p=c&-c,x=o&-o,p>=x||p===16&&(x&4194240)!==0))return o;if((c&4)!==0&&(c|=l&16),o=n.entangledLanes,o!==0)for(n=n.entanglements,o&=c;0l;l++)o.push(n);return o}function Ci(n,o,l){n.pendingLanes|=o,o!==536870912&&(n.suspendedLanes=0,n.pingedLanes=0),n=n.eventTimes,o=31-Tt(o),n[o]=l}function yw(n,o){var l=n.pendingLanes&~o;n.pendingLanes=o,n.suspendedLanes=0,n.pingedLanes=0,n.expiredLanes&=o,n.mutableReadLanes&=o,n.entangledLanes&=o,o=n.entanglements;var c=n.eventTimes;for(n=n.expirationTimes;0=ji),up=" ",cp=!1;function fp(n,o){switch(n){case"keyup":return Ww.indexOf(o.keyCode)!==-1;case"keydown":return o.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function dp(n){return n=n.detail,typeof n=="object"&&"data"in n?n.data:null}var So=!1;function Yw(n,o){switch(n){case"compositionend":return dp(o);case"keypress":return o.which!==32?null:(cp=!0,up);case"textInput":return n=o.data,n===up&&cp?null:n;default:return null}}function Xw(n,o){if(So)return n==="compositionend"||!Cc&&fp(n,o)?(n=rp(),dl=xc=tr=null,So=!1,n):null;switch(n){case"paste":return null;case"keypress":if(!(o.ctrlKey||o.altKey||o.metaKey)||o.ctrlKey&&o.altKey){if(o.char&&1=o)return{node:l,offset:o-n};n=c}e:{for(;l;){if(l.nextSibling){l=l.nextSibling;break e}l=l.parentNode}l=void 0}l=vp(l)}}function Sp(n,o){return n&&o?n===o?!0:n&&n.nodeType===3?!1:o&&o.nodeType===3?Sp(n,o.parentNode):"contains"in n?n.contains(o):n.compareDocumentPosition?!!(n.compareDocumentPosition(o)&16):!1:!1}function Ep(){for(var n=window,o=Pe();o instanceof n.HTMLIFrameElement;){try{var l=typeof o.contentWindow.location.href=="string"}catch{l=!1}if(l)n=o.contentWindow;else break;o=Pe(n.document)}return o}function bc(n){var o=n&&n.nodeName&&n.nodeName.toLowerCase();return o&&(o==="input"&&(n.type==="text"||n.type==="search"||n.type==="tel"||n.type==="url"||n.type==="password")||o==="textarea"||n.contentEditable==="true")}function rS(n){var o=Ep(),l=n.focusedElem,c=n.selectionRange;if(o!==l&&l&&l.ownerDocument&&Sp(l.ownerDocument.documentElement,l)){if(c!==null&&bc(l)){if(o=c.start,n=c.end,n===void 0&&(n=o),"selectionStart"in l)l.selectionStart=o,l.selectionEnd=Math.min(n,l.value.length);else if(n=(o=l.ownerDocument||document)&&o.defaultView||window,n.getSelection){n=n.getSelection();var p=l.textContent.length,x=Math.min(c.start,p);c=c.end===void 0?x:Math.min(c.end,p),!n.extend&&x>c&&(p=c,c=x,x=p),p=wp(l,x);var _=wp(l,c);p&&_&&(n.rangeCount!==1||n.anchorNode!==p.node||n.anchorOffset!==p.offset||n.focusNode!==_.node||n.focusOffset!==_.offset)&&(o=o.createRange(),o.setStart(p.node,p.offset),n.removeAllRanges(),x>c?(n.addRange(o),n.extend(_.node,_.offset)):(o.setEnd(_.node,_.offset),n.addRange(o)))}}for(o=[],n=l;n=n.parentNode;)n.nodeType===1&&o.push({element:n,left:n.scrollLeft,top:n.scrollTop});for(typeof l.focus=="function"&&l.focus(),l=0;l=document.documentMode,Eo=null,Pc=null,Li=null,Rc=!1;function kp(n,o,l){var c=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;Rc||Eo==null||Eo!==Pe(c)||(c=Eo,"selectionStart"in c&&bc(c)?c={start:c.selectionStart,end:c.selectionEnd}:(c=(c.ownerDocument&&c.ownerDocument.defaultView||window).getSelection(),c={anchorNode:c.anchorNode,anchorOffset:c.anchorOffset,focusNode:c.focusNode,focusOffset:c.focusOffset}),Li&&Mi(Li,c)||(Li=c,c=wl(Pc,"onSelect"),0bo||(n.current=Bc[bo],Bc[bo]=null,bo--)}function De(n,o){bo++,Bc[bo]=n.current,n.current=o}var ir={},dt=or(ir),St=or(!1),jr=ir;function Po(n,o){var l=n.type.contextTypes;if(!l)return ir;var c=n.stateNode;if(c&&c.__reactInternalMemoizedUnmaskedChildContext===o)return c.__reactInternalMemoizedMaskedChildContext;var p={},x;for(x in l)p[x]=o[x];return c&&(n=n.stateNode,n.__reactInternalMemoizedUnmaskedChildContext=o,n.__reactInternalMemoizedMaskedChildContext=p),p}function Et(n){return n=n.childContextTypes,n!=null}function Cl(){ze(St),ze(dt)}function Op(n,o,l){if(dt.current!==ir)throw Error(r(168));De(dt,o),De(St,l)}function zp(n,o,l){var c=n.stateNode;if(o=o.childContextTypes,typeof c.getChildContext!="function")return l;c=c.getChildContext();for(var p in c)if(!(p in o))throw Error(r(108,de(n)||"Unknown",p));return z({},l,c)}function _l(n){return n=(n=n.stateNode)&&n.__reactInternalMemoizedMergedChildContext||ir,jr=dt.current,De(dt,n),De(St,St.current),!0}function Fp(n,o,l){var c=n.stateNode;if(!c)throw Error(r(169));l?(n=zp(n,o,jr),c.__reactInternalMemoizedMergedChildContext=n,ze(St),ze(dt),De(dt,n)):ze(St),De(St,l)}var _n=null,Nl=!1,Hc=!1;function Bp(n){_n===null?_n=[n]:_n.push(n)}function gS(n){Nl=!0,Bp(n)}function sr(){if(!Hc&&_n!==null){Hc=!0;var n=0,o=Me;try{var l=_n;for(Me=1;n>=_,p-=_,Nn=1<<32-Tt(o)+p|l<Ce?(st=ke,ke=null):st=ke.sibling;var je=se(W,ke,X[Ce],fe);if(je===null){ke===null&&(ke=st);break}n&&ke&&je.alternate===null&&o(W,ke),B=x(je,B,Ce),Ee===null?ve=je:Ee.sibling=je,Ee=je,ke=st}if(Ce===X.length)return l(W,ke),Be&&Ar(W,Ce),ve;if(ke===null){for(;CeCe?(st=ke,ke=null):st=ke.sibling;var gr=se(W,ke,je.value,fe);if(gr===null){ke===null&&(ke=st);break}n&&ke&&gr.alternate===null&&o(W,ke),B=x(gr,B,Ce),Ee===null?ve=gr:Ee.sibling=gr,Ee=gr,ke=st}if(je.done)return l(W,ke),Be&&Ar(W,Ce),ve;if(ke===null){for(;!je.done;Ce++,je=X.next())je=ue(W,je.value,fe),je!==null&&(B=x(je,B,Ce),Ee===null?ve=je:Ee.sibling=je,Ee=je);return Be&&Ar(W,Ce),ve}for(ke=c(W,ke);!je.done;Ce++,je=X.next())je=pe(ke,W,Ce,je.value,fe),je!==null&&(n&&je.alternate!==null&&ke.delete(je.key===null?Ce:je.key),B=x(je,B,Ce),Ee===null?ve=je:Ee.sibling=je,Ee=je);return n&&ke.forEach(function(GS){return o(W,GS)}),Be&&Ar(W,Ce),ve}function Qe(W,B,X,fe){if(typeof X=="object"&&X!==null&&X.type===j&&X.key===null&&(X=X.props.children),typeof X=="object"&&X!==null){switch(X.$$typeof){case M:e:{for(var ve=X.key,Ee=B;Ee!==null;){if(Ee.key===ve){if(ve=X.type,ve===j){if(Ee.tag===7){l(W,Ee.sibling),B=p(Ee,X.props.children),B.return=W,W=B;break e}}else if(Ee.elementType===ve||typeof ve=="object"&&ve!==null&&ve.$$typeof===F&&Yp(ve)===Ee.type){l(W,Ee.sibling),B=p(Ee,X.props),B.ref=Hi(W,Ee,X),B.return=W,W=B;break e}l(W,Ee);break}else o(W,Ee);Ee=Ee.sibling}X.type===j?(B=Hr(X.props.children,W.mode,fe,X.key),B.return=W,W=B):(fe=ea(X.type,X.key,X.props,null,W.mode,fe),fe.ref=Hi(W,B,X),fe.return=W,W=fe)}return _(W);case L:e:{for(Ee=X.key;B!==null;){if(B.key===Ee)if(B.tag===4&&B.stateNode.containerInfo===X.containerInfo&&B.stateNode.implementation===X.implementation){l(W,B.sibling),B=p(B,X.children||[]),B.return=W,W=B;break e}else{l(W,B);break}else o(W,B);B=B.sibling}B=Ff(X,W.mode,fe),B.return=W,W=B}return _(W);case F:return Ee=X._init,Qe(W,B,Ee(X._payload),fe)}if(Gt(X))return ye(W,B,X,fe);if(O(X))return xe(W,B,X,fe);Tl(W,X)}return typeof X=="string"&&X!==""||typeof X=="number"?(X=""+X,B!==null&&B.tag===6?(l(W,B.sibling),B=p(B,X),B.return=W,W=B):(l(W,B),B=zf(X,W.mode,fe),B.return=W,W=B),_(W)):l(W,B)}return Qe}var jo=Xp(!0),Gp=Xp(!1),Il=or(null),jl=null,$o=null,Xc=null;function Gc(){Xc=$o=jl=null}function Kc(n){var o=Il.current;ze(Il),n._currentValue=o}function Qc(n,o,l){for(;n!==null;){var c=n.alternate;if((n.childLanes&o)!==o?(n.childLanes|=o,c!==null&&(c.childLanes|=o)):c!==null&&(c.childLanes&o)!==o&&(c.childLanes|=o),n===l)break;n=n.return}}function Ao(n,o){jl=n,Xc=$o=null,n=n.dependencies,n!==null&&n.firstContext!==null&&((n.lanes&o)!==0&&(kt=!0),n.firstContext=null)}function Vt(n){var o=n._currentValue;if(Xc!==n)if(n={context:n,memoizedValue:o,next:null},$o===null){if(jl===null)throw Error(r(308));$o=n,jl.dependencies={lanes:0,firstContext:n}}else $o=$o.next=n;return o}var Mr=null;function Jc(n){Mr===null?Mr=[n]:Mr.push(n)}function Kp(n,o,l,c){var p=o.interleaved;return p===null?(l.next=l,Jc(o)):(l.next=p.next,p.next=l),o.interleaved=l,Pn(n,c)}function Pn(n,o){n.lanes|=o;var l=n.alternate;for(l!==null&&(l.lanes|=o),l=n,n=n.return;n!==null;)n.childLanes|=o,l=n.alternate,l!==null&&(l.childLanes|=o),l=n,n=n.return;return l.tag===3?l.stateNode:null}var lr=!1;function Zc(n){n.updateQueue={baseState:n.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Qp(n,o){n=n.updateQueue,o.updateQueue===n&&(o.updateQueue={baseState:n.baseState,firstBaseUpdate:n.firstBaseUpdate,lastBaseUpdate:n.lastBaseUpdate,shared:n.shared,effects:n.effects})}function Rn(n,o){return{eventTime:n,lane:o,tag:0,payload:null,callback:null,next:null}}function ar(n,o,l){var c=n.updateQueue;if(c===null)return null;if(c=c.shared,(Te&2)!==0){var p=c.pending;return p===null?o.next=o:(o.next=p.next,p.next=o),c.pending=o,Pn(n,l)}return p=c.interleaved,p===null?(o.next=o,Jc(c)):(o.next=p.next,p.next=o),c.interleaved=o,Pn(n,l)}function $l(n,o,l){if(o=o.updateQueue,o!==null&&(o=o.shared,(l&4194240)!==0)){var c=o.lanes;c&=n.pendingLanes,l|=c,o.lanes=l,hc(n,l)}}function Jp(n,o){var l=n.updateQueue,c=n.alternate;if(c!==null&&(c=c.updateQueue,l===c)){var p=null,x=null;if(l=l.firstBaseUpdate,l!==null){do{var _={eventTime:l.eventTime,lane:l.lane,tag:l.tag,payload:l.payload,callback:l.callback,next:null};x===null?p=x=_:x=x.next=_,l=l.next}while(l!==null);x===null?p=x=o:x=x.next=o}else p=x=o;l={baseState:c.baseState,firstBaseUpdate:p,lastBaseUpdate:x,shared:c.shared,effects:c.effects},n.updateQueue=l;return}n=l.lastBaseUpdate,n===null?l.firstBaseUpdate=o:n.next=o,l.lastBaseUpdate=o}function Al(n,o,l,c){var p=n.updateQueue;lr=!1;var x=p.firstBaseUpdate,_=p.lastBaseUpdate,$=p.shared.pending;if($!==null){p.shared.pending=null;var D=$,K=D.next;D.next=null,_===null?x=K:_.next=K,_=D;var ae=n.alternate;ae!==null&&(ae=ae.updateQueue,$=ae.lastBaseUpdate,$!==_&&($===null?ae.firstBaseUpdate=K:$.next=K,ae.lastBaseUpdate=D))}if(x!==null){var ue=p.baseState;_=0,ae=K=D=null,$=x;do{var se=$.lane,pe=$.eventTime;if((c&se)===se){ae!==null&&(ae=ae.next={eventTime:pe,lane:0,tag:$.tag,payload:$.payload,callback:$.callback,next:null});e:{var ye=n,xe=$;switch(se=o,pe=l,xe.tag){case 1:if(ye=xe.payload,typeof ye=="function"){ue=ye.call(pe,ue,se);break e}ue=ye;break e;case 3:ye.flags=ye.flags&-65537|128;case 0:if(ye=xe.payload,se=typeof ye=="function"?ye.call(pe,ue,se):ye,se==null)break e;ue=z({},ue,se);break e;case 2:lr=!0}}$.callback!==null&&$.lane!==0&&(n.flags|=64,se=p.effects,se===null?p.effects=[$]:se.push($))}else pe={eventTime:pe,lane:se,tag:$.tag,payload:$.payload,callback:$.callback,next:null},ae===null?(K=ae=pe,D=ue):ae=ae.next=pe,_|=se;if($=$.next,$===null){if($=p.shared.pending,$===null)break;se=$,$=se.next,se.next=null,p.lastBaseUpdate=se,p.shared.pending=null}}while(!0);if(ae===null&&(D=ue),p.baseState=D,p.firstBaseUpdate=K,p.lastBaseUpdate=ae,o=p.shared.interleaved,o!==null){p=o;do _|=p.lane,p=p.next;while(p!==o)}else x===null&&(p.shared.lanes=0);Or|=_,n.lanes=_,n.memoizedState=ue}}function Zp(n,o,l){if(n=o.effects,o.effects=null,n!==null)for(o=0;ol?l:4,n(!0);var c=of.transition;of.transition={};try{n(!1),o()}finally{Me=l,of.transition=c}}function xg(){return Ut().memoizedState}function vS(n,o,l){var c=dr(n);if(l={lane:c,action:l,hasEagerState:!1,eagerState:null,next:null},vg(n))wg(o,l);else if(l=Kp(n,o,l,c),l!==null){var p=vt();tn(l,n,c,p),Sg(l,o,c)}}function wS(n,o,l){var c=dr(n),p={lane:c,action:l,hasEagerState:!1,eagerState:null,next:null};if(vg(n))wg(o,p);else{var x=n.alternate;if(n.lanes===0&&(x===null||x.lanes===0)&&(x=o.lastRenderedReducer,x!==null))try{var _=o.lastRenderedState,$=x(_,l);if(p.hasEagerState=!0,p.eagerState=$,Kt($,_)){var D=o.interleaved;D===null?(p.next=p,Jc(o)):(p.next=D.next,D.next=p),o.interleaved=p;return}}catch{}finally{}l=Kp(n,o,p,c),l!==null&&(p=vt(),tn(l,n,c,p),Sg(l,o,c))}}function vg(n){var o=n.alternate;return n===We||o!==null&&o===We}function wg(n,o){qi=Dl=!0;var l=n.pending;l===null?o.next=o:(o.next=l.next,l.next=o),n.pending=o}function Sg(n,o,l){if((l&4194240)!==0){var c=o.lanes;c&=n.pendingLanes,l|=c,o.lanes=l,hc(n,l)}}var Fl={readContext:Vt,useCallback:ht,useContext:ht,useEffect:ht,useImperativeHandle:ht,useInsertionEffect:ht,useLayoutEffect:ht,useMemo:ht,useReducer:ht,useRef:ht,useState:ht,useDebugValue:ht,useDeferredValue:ht,useTransition:ht,useMutableSource:ht,useSyncExternalStore:ht,useId:ht,unstable_isNewReconciler:!1},SS={readContext:Vt,useCallback:function(n,o){return gn().memoizedState=[n,o===void 0?null:o],n},useContext:Vt,useEffect:cg,useImperativeHandle:function(n,o,l){return l=l!=null?l.concat([n]):null,Ol(4194308,4,hg.bind(null,o,n),l)},useLayoutEffect:function(n,o){return Ol(4194308,4,n,o)},useInsertionEffect:function(n,o){return Ol(4,2,n,o)},useMemo:function(n,o){var l=gn();return o=o===void 0?null:o,n=n(),l.memoizedState=[n,o],n},useReducer:function(n,o,l){var c=gn();return o=l!==void 0?l(o):o,c.memoizedState=c.baseState=o,n={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:n,lastRenderedState:o},c.queue=n,n=n.dispatch=vS.bind(null,We,n),[c.memoizedState,n]},useRef:function(n){var o=gn();return n={current:n},o.memoizedState=n},useState:ag,useDebugValue:df,useDeferredValue:function(n){return gn().memoizedState=n},useTransition:function(){var n=ag(!1),o=n[0];return n=xS.bind(null,n[1]),gn().memoizedState=n,[o,n]},useMutableSource:function(){},useSyncExternalStore:function(n,o,l){var c=We,p=gn();if(Be){if(l===void 0)throw Error(r(407));l=l()}else{if(l=o(),it===null)throw Error(r(349));(Dr&30)!==0||rg(c,o,l)}p.memoizedState=l;var x={value:l,getSnapshot:o};return p.queue=x,cg(ig.bind(null,c,x,n),[n]),c.flags|=2048,Gi(9,og.bind(null,c,x,l,o),void 0,null),l},useId:function(){var n=gn(),o=it.identifierPrefix;if(Be){var l=bn,c=Nn;l=(c&~(1<<32-Tt(c)-1)).toString(32)+l,o=":"+o+"R"+l,l=Yi++,0<\/script>",n=n.removeChild(n.firstChild)):typeof c.is=="string"?n=_.createElement(l,{is:c.is}):(n=_.createElement(l),l==="select"&&(_=n,c.multiple?_.multiple=!0:c.size&&(_.size=c.size))):n=_.createElementNS(n,l),n[hn]=o,n[Fi]=c,Bg(n,o,!1,!1),o.stateNode=n;e:{switch(_=pi(l,c),l){case"dialog":Oe("cancel",n),Oe("close",n),p=c;break;case"iframe":case"object":case"embed":Oe("load",n),p=c;break;case"video":case"audio":for(p=0;pzo&&(o.flags|=128,c=!0,Ki(x,!1),o.lanes=4194304)}else{if(!c)if(n=Ml(_),n!==null){if(o.flags|=128,c=!0,l=n.updateQueue,l!==null&&(o.updateQueue=l,o.flags|=4),Ki(x,!0),x.tail===null&&x.tailMode==="hidden"&&!_.alternate&&!Be)return pt(o),null}else 2*Ve()-x.renderingStartTime>zo&&l!==1073741824&&(o.flags|=128,c=!0,Ki(x,!1),o.lanes=4194304);x.isBackwards?(_.sibling=o.child,o.child=_):(l=x.last,l!==null?l.sibling=_:o.child=_,x.last=_)}return x.tail!==null?(o=x.tail,x.rendering=o,x.tail=o.sibling,x.renderingStartTime=Ve(),o.sibling=null,l=Ue.current,De(Ue,c?l&1|2:l&1),o):(pt(o),null);case 22:case 23:return Lf(),c=o.memoizedState!==null,n!==null&&n.memoizedState!==null!==c&&(o.flags|=8192),c&&(o.mode&1)!==0?(At&1073741824)!==0&&(pt(o),o.subtreeFlags&6&&(o.flags|=8192)):pt(o),null;case 24:return null;case 25:return null}throw Error(r(156,o.tag))}function RS(n,o){switch(Uc(o),o.tag){case 1:return Et(o.type)&&Cl(),n=o.flags,n&65536?(o.flags=n&-65537|128,o):null;case 3:return Mo(),ze(St),ze(dt),rf(),n=o.flags,(n&65536)!==0&&(n&128)===0?(o.flags=n&-65537|128,o):null;case 5:return tf(o),null;case 13:if(ze(Ue),n=o.memoizedState,n!==null&&n.dehydrated!==null){if(o.alternate===null)throw Error(r(340));Io()}return n=o.flags,n&65536?(o.flags=n&-65537|128,o):null;case 19:return ze(Ue),null;case 4:return Mo(),null;case 10:return Kc(o.type._context),null;case 22:case 23:return Lf(),null;case 24:return null;default:return null}}var Ul=!1,gt=!1,TS=typeof WeakSet=="function"?WeakSet:Set,me=null;function Do(n,o){var l=n.ref;if(l!==null)if(typeof l=="function")try{l(null)}catch(c){Ye(n,o,c)}else l.current=null}function Cf(n,o,l){try{l()}catch(c){Ye(n,o,c)}}var Ug=!1;function IS(n,o){if(Mc=cl,n=Ep(),bc(n)){if("selectionStart"in n)var l={start:n.selectionStart,end:n.selectionEnd};else e:{l=(l=n.ownerDocument)&&l.defaultView||window;var c=l.getSelection&&l.getSelection();if(c&&c.rangeCount!==0){l=c.anchorNode;var p=c.anchorOffset,x=c.focusNode;c=c.focusOffset;try{l.nodeType,x.nodeType}catch{l=null;break e}var _=0,$=-1,D=-1,K=0,ae=0,ue=n,se=null;t:for(;;){for(var pe;ue!==l||p!==0&&ue.nodeType!==3||($=_+p),ue!==x||c!==0&&ue.nodeType!==3||(D=_+c),ue.nodeType===3&&(_+=ue.nodeValue.length),(pe=ue.firstChild)!==null;)se=ue,ue=pe;for(;;){if(ue===n)break t;if(se===l&&++K===p&&($=_),se===x&&++ae===c&&(D=_),(pe=ue.nextSibling)!==null)break;ue=se,se=ue.parentNode}ue=pe}l=$===-1||D===-1?null:{start:$,end:D}}else l=null}l=l||{start:0,end:0}}else l=null;for(Lc={focusedElem:n,selectionRange:l},cl=!1,me=o;me!==null;)if(o=me,n=o.child,(o.subtreeFlags&1028)!==0&&n!==null)n.return=o,me=n;else for(;me!==null;){o=me;try{var ye=o.alternate;if((o.flags&1024)!==0)switch(o.tag){case 0:case 11:case 15:break;case 1:if(ye!==null){var xe=ye.memoizedProps,Qe=ye.memoizedState,W=o.stateNode,B=W.getSnapshotBeforeUpdate(o.elementType===o.type?xe:Jt(o.type,xe),Qe);W.__reactInternalSnapshotBeforeUpdate=B}break;case 3:var X=o.stateNode.containerInfo;X.nodeType===1?X.textContent="":X.nodeType===9&&X.documentElement&&X.removeChild(X.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(r(163))}}catch(fe){Ye(o,o.return,fe)}if(n=o.sibling,n!==null){n.return=o.return,me=n;break}me=o.return}return ye=Ug,Ug=!1,ye}function Qi(n,o,l){var c=o.updateQueue;if(c=c!==null?c.lastEffect:null,c!==null){var p=c=c.next;do{if((p.tag&n)===n){var x=p.destroy;p.destroy=void 0,x!==void 0&&Cf(o,l,x)}p=p.next}while(p!==c)}}function Wl(n,o){if(o=o.updateQueue,o=o!==null?o.lastEffect:null,o!==null){var l=o=o.next;do{if((l.tag&n)===n){var c=l.create;l.destroy=c()}l=l.next}while(l!==o)}}function _f(n){var o=n.ref;if(o!==null){var l=n.stateNode;switch(n.tag){case 5:n=l;break;default:n=l}typeof o=="function"?o(n):o.current=n}}function Wg(n){var o=n.alternate;o!==null&&(n.alternate=null,Wg(o)),n.child=null,n.deletions=null,n.sibling=null,n.tag===5&&(o=n.stateNode,o!==null&&(delete o[hn],delete o[Fi],delete o[Fc],delete o[hS],delete o[pS])),n.stateNode=null,n.return=null,n.dependencies=null,n.memoizedProps=null,n.memoizedState=null,n.pendingProps=null,n.stateNode=null,n.updateQueue=null}function qg(n){return n.tag===5||n.tag===3||n.tag===4}function Yg(n){e:for(;;){for(;n.sibling===null;){if(n.return===null||qg(n.return))return null;n=n.return}for(n.sibling.return=n.return,n=n.sibling;n.tag!==5&&n.tag!==6&&n.tag!==18;){if(n.flags&2||n.child===null||n.tag===4)continue e;n.child.return=n,n=n.child}if(!(n.flags&2))return n.stateNode}}function Nf(n,o,l){var c=n.tag;if(c===5||c===6)n=n.stateNode,o?l.nodeType===8?l.parentNode.insertBefore(n,o):l.insertBefore(n,o):(l.nodeType===8?(o=l.parentNode,o.insertBefore(n,l)):(o=l,o.appendChild(n)),l=l._reactRootContainer,l!=null||o.onclick!==null||(o.onclick=El));else if(c!==4&&(n=n.child,n!==null))for(Nf(n,o,l),n=n.sibling;n!==null;)Nf(n,o,l),n=n.sibling}function bf(n,o,l){var c=n.tag;if(c===5||c===6)n=n.stateNode,o?l.insertBefore(n,o):l.appendChild(n);else if(c!==4&&(n=n.child,n!==null))for(bf(n,o,l),n=n.sibling;n!==null;)bf(n,o,l),n=n.sibling}var ct=null,Zt=!1;function ur(n,o,l){for(l=l.child;l!==null;)Xg(n,o,l),l=l.sibling}function Xg(n,o,l){if(Ft&&typeof Ft.onCommitFiberUnmount=="function")try{Ft.onCommitFiberUnmount(go,l)}catch{}switch(l.tag){case 5:gt||Do(l,o);case 6:var c=ct,p=Zt;ct=null,ur(n,o,l),ct=c,Zt=p,ct!==null&&(Zt?(n=ct,l=l.stateNode,n.nodeType===8?n.parentNode.removeChild(l):n.removeChild(l)):ct.removeChild(l.stateNode));break;case 18:ct!==null&&(Zt?(n=ct,l=l.stateNode,n.nodeType===8?zc(n.parentNode,l):n.nodeType===1&&zc(n,l),Ri(n)):zc(ct,l.stateNode));break;case 4:c=ct,p=Zt,ct=l.stateNode.containerInfo,Zt=!0,ur(n,o,l),ct=c,Zt=p;break;case 0:case 11:case 14:case 15:if(!gt&&(c=l.updateQueue,c!==null&&(c=c.lastEffect,c!==null))){p=c=c.next;do{var x=p,_=x.destroy;x=x.tag,_!==void 0&&((x&2)!==0||(x&4)!==0)&&Cf(l,o,_),p=p.next}while(p!==c)}ur(n,o,l);break;case 1:if(!gt&&(Do(l,o),c=l.stateNode,typeof c.componentWillUnmount=="function"))try{c.props=l.memoizedProps,c.state=l.memoizedState,c.componentWillUnmount()}catch($){Ye(l,o,$)}ur(n,o,l);break;case 21:ur(n,o,l);break;case 22:l.mode&1?(gt=(c=gt)||l.memoizedState!==null,ur(n,o,l),gt=c):ur(n,o,l);break;default:ur(n,o,l)}}function Gg(n){var o=n.updateQueue;if(o!==null){n.updateQueue=null;var l=n.stateNode;l===null&&(l=n.stateNode=new TS),o.forEach(function(c){var p=FS.bind(null,n,c);l.has(c)||(l.add(c),c.then(p,p))})}}function en(n,o){var l=o.deletions;if(l!==null)for(var c=0;cp&&(p=_),c&=~x}if(c=p,c=Ve()-c,c=(120>c?120:480>c?480:1080>c?1080:1920>c?1920:3e3>c?3e3:4320>c?4320:1960*$S(c/1960))-c,10n?16:n,fr===null)var c=!1;else{if(n=fr,fr=null,Kl=0,(Te&6)!==0)throw Error(r(331));var p=Te;for(Te|=4,me=n.current;me!==null;){var x=me,_=x.child;if((me.flags&16)!==0){var $=x.deletions;if($!==null){for(var D=0;D<$.length;D++){var K=$[D];for(me=K;me!==null;){var ae=me;switch(ae.tag){case 0:case 11:case 15:Qi(8,ae,x)}var ue=ae.child;if(ue!==null)ue.return=ae,me=ue;else for(;me!==null;){ae=me;var se=ae.sibling,pe=ae.return;if(Wg(ae),ae===K){me=null;break}if(se!==null){se.return=pe,me=se;break}me=pe}}}var ye=x.alternate;if(ye!==null){var xe=ye.child;if(xe!==null){ye.child=null;do{var Qe=xe.sibling;xe.sibling=null,xe=Qe}while(xe!==null)}}me=x}}if((x.subtreeFlags&2064)!==0&&_!==null)_.return=x,me=_;else e:for(;me!==null;){if(x=me,(x.flags&2048)!==0)switch(x.tag){case 0:case 11:case 15:Qi(9,x,x.return)}var W=x.sibling;if(W!==null){W.return=x.return,me=W;break e}me=x.return}}var B=n.current;for(me=B;me!==null;){_=me;var X=_.child;if((_.subtreeFlags&2064)!==0&&X!==null)X.return=_,me=X;else e:for(_=B;me!==null;){if($=me,($.flags&2048)!==0)try{switch($.tag){case 0:case 11:case 15:Wl(9,$)}}catch(ve){Ye($,$.return,ve)}if($===_){me=null;break e}var fe=$.sibling;if(fe!==null){fe.return=$.return,me=fe;break e}me=$.return}}if(Te=p,sr(),Ft&&typeof Ft.onPostCommitFiberRoot=="function")try{Ft.onPostCommitFiberRoot(go,n)}catch{}c=!0}return c}finally{Me=l,Wt.transition=o}}return!1}function lm(n,o,l){o=Lo(l,o),o=_g(n,o,1),n=ar(n,o,1),o=vt(),n!==null&&(Ci(n,1,o),_t(n,o))}function Ye(n,o,l){if(n.tag===3)lm(n,n,l);else for(;o!==null;){if(o.tag===3){lm(o,n,l);break}else if(o.tag===1){var c=o.stateNode;if(typeof o.type.getDerivedStateFromError=="function"||typeof c.componentDidCatch=="function"&&(cr===null||!cr.has(c))){n=Lo(l,n),n=Ng(o,n,1),o=ar(o,n,1),n=vt(),o!==null&&(Ci(o,1,n),_t(o,n));break}}o=o.return}}function OS(n,o,l){var c=n.pingCache;c!==null&&c.delete(o),o=vt(),n.pingedLanes|=n.suspendedLanes&l,it===n&&(ft&l)===l&&(nt===4||nt===3&&(ft&130023424)===ft&&500>Ve()-Tf?Fr(n,0):Rf|=l),_t(n,o)}function am(n,o){o===0&&((n.mode&1)===0?o=1:(o=yo,yo<<=1,(yo&130023424)===0&&(yo=4194304)));var l=vt();n=Pn(n,o),n!==null&&(Ci(n,o,l),_t(n,l))}function zS(n){var o=n.memoizedState,l=0;o!==null&&(l=o.retryLane),am(n,l)}function FS(n,o){var l=0;switch(n.tag){case 13:var c=n.stateNode,p=n.memoizedState;p!==null&&(l=p.retryLane);break;case 19:c=n.stateNode;break;default:throw Error(r(314))}c!==null&&c.delete(o),am(n,l)}var um;um=function(n,o,l){if(n!==null)if(n.memoizedProps!==o.pendingProps||St.current)kt=!0;else{if((n.lanes&l)===0&&(o.flags&128)===0)return kt=!1,bS(n,o,l);kt=(n.flags&131072)!==0}else kt=!1,Be&&(o.flags&1048576)!==0&&Hp(o,Pl,o.index);switch(o.lanes=0,o.tag){case 2:var c=o.type;Vl(n,o),n=o.pendingProps;var p=Po(o,dt.current);Ao(o,l),p=lf(null,o,c,n,p,l);var x=af();return o.flags|=1,typeof p=="object"&&p!==null&&typeof p.render=="function"&&p.$$typeof===void 0?(o.tag=1,o.memoizedState=null,o.updateQueue=null,Et(c)?(x=!0,_l(o)):x=!1,o.memoizedState=p.state!==null&&p.state!==void 0?p.state:null,Zc(o),p.updater=Bl,o.stateNode=p,p._reactInternals=o,pf(o,c,n,l),o=xf(null,o,c,!0,x,l)):(o.tag=0,Be&&x&&Vc(o),xt(null,o,p,l),o=o.child),o;case 16:c=o.elementType;e:{switch(Vl(n,o),n=o.pendingProps,p=c._init,c=p(c._payload),o.type=c,p=o.tag=HS(c),n=Jt(c,n),p){case 0:o=yf(null,o,c,n,l);break e;case 1:o=Mg(null,o,c,n,l);break e;case 11:o=Tg(null,o,c,n,l);break e;case 14:o=Ig(null,o,c,Jt(c.type,n),l);break e}throw Error(r(306,c,""))}return o;case 0:return c=o.type,p=o.pendingProps,p=o.elementType===c?p:Jt(c,p),yf(n,o,c,p,l);case 1:return c=o.type,p=o.pendingProps,p=o.elementType===c?p:Jt(c,p),Mg(n,o,c,p,l);case 3:e:{if(Lg(o),n===null)throw Error(r(387));c=o.pendingProps,x=o.memoizedState,p=x.element,Qp(n,o),Al(o,c,null,l);var _=o.memoizedState;if(c=_.element,x.isDehydrated)if(x={element:c,isDehydrated:!1,cache:_.cache,pendingSuspenseBoundaries:_.pendingSuspenseBoundaries,transitions:_.transitions},o.updateQueue.baseState=x,o.memoizedState=x,o.flags&256){p=Lo(Error(r(423)),o),o=Dg(n,o,c,l,p);break e}else if(c!==p){p=Lo(Error(r(424)),o),o=Dg(n,o,c,l,p);break e}else for($t=rr(o.stateNode.containerInfo.firstChild),jt=o,Be=!0,Qt=null,l=Gp(o,null,c,l),o.child=l;l;)l.flags=l.flags&-3|4096,l=l.sibling;else{if(Io(),c===p){o=Tn(n,o,l);break e}xt(n,o,c,l)}o=o.child}return o;case 5:return eg(o),n===null&&qc(o),c=o.type,p=o.pendingProps,x=n!==null?n.memoizedProps:null,_=p.children,Dc(c,p)?_=null:x!==null&&Dc(c,x)&&(o.flags|=32),Ag(n,o),xt(n,o,_,l),o.child;case 6:return n===null&&qc(o),null;case 13:return Og(n,o,l);case 4:return ef(o,o.stateNode.containerInfo),c=o.pendingProps,n===null?o.child=jo(o,null,c,l):xt(n,o,c,l),o.child;case 11:return c=o.type,p=o.pendingProps,p=o.elementType===c?p:Jt(c,p),Tg(n,o,c,p,l);case 7:return xt(n,o,o.pendingProps,l),o.child;case 8:return xt(n,o,o.pendingProps.children,l),o.child;case 12:return xt(n,o,o.pendingProps.children,l),o.child;case 10:e:{if(c=o.type._context,p=o.pendingProps,x=o.memoizedProps,_=p.value,De(Il,c._currentValue),c._currentValue=_,x!==null)if(Kt(x.value,_)){if(x.children===p.children&&!St.current){o=Tn(n,o,l);break e}}else for(x=o.child,x!==null&&(x.return=o);x!==null;){var $=x.dependencies;if($!==null){_=x.child;for(var D=$.firstContext;D!==null;){if(D.context===c){if(x.tag===1){D=Rn(-1,l&-l),D.tag=2;var K=x.updateQueue;if(K!==null){K=K.shared;var ae=K.pending;ae===null?D.next=D:(D.next=ae.next,ae.next=D),K.pending=D}}x.lanes|=l,D=x.alternate,D!==null&&(D.lanes|=l),Qc(x.return,l,o),$.lanes|=l;break}D=D.next}}else if(x.tag===10)_=x.type===o.type?null:x.child;else if(x.tag===18){if(_=x.return,_===null)throw Error(r(341));_.lanes|=l,$=_.alternate,$!==null&&($.lanes|=l),Qc(_,l,o),_=x.sibling}else _=x.child;if(_!==null)_.return=x;else for(_=x;_!==null;){if(_===o){_=null;break}if(x=_.sibling,x!==null){x.return=_.return,_=x;break}_=_.return}x=_}xt(n,o,p.children,l),o=o.child}return o;case 9:return p=o.type,c=o.pendingProps.children,Ao(o,l),p=Vt(p),c=c(p),o.flags|=1,xt(n,o,c,l),o.child;case 14:return c=o.type,p=Jt(c,o.pendingProps),p=Jt(c.type,p),Ig(n,o,c,p,l);case 15:return jg(n,o,o.type,o.pendingProps,l);case 17:return c=o.type,p=o.pendingProps,p=o.elementType===c?p:Jt(c,p),Vl(n,o),o.tag=1,Et(c)?(n=!0,_l(o)):n=!1,Ao(o,l),kg(o,c,p),pf(o,c,p,l),xf(null,o,c,!0,n,l);case 19:return Fg(n,o,l);case 22:return $g(n,o,l)}throw Error(r(156,o.tag))};function cm(n,o){return ol(n,o)}function BS(n,o,l,c){this.tag=n,this.key=l,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=o,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=c,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function qt(n,o,l,c){return new BS(n,o,l,c)}function Of(n){return n=n.prototype,!(!n||!n.isReactComponent)}function HS(n){if(typeof n=="function")return Of(n)?1:0;if(n!=null){if(n=n.$$typeof,n===ne)return 11;if(n===U)return 14}return 2}function pr(n,o){var l=n.alternate;return l===null?(l=qt(n.tag,o,n.key,n.mode),l.elementType=n.elementType,l.type=n.type,l.stateNode=n.stateNode,l.alternate=n,n.alternate=l):(l.pendingProps=o,l.type=n.type,l.flags=0,l.subtreeFlags=0,l.deletions=null),l.flags=n.flags&14680064,l.childLanes=n.childLanes,l.lanes=n.lanes,l.child=n.child,l.memoizedProps=n.memoizedProps,l.memoizedState=n.memoizedState,l.updateQueue=n.updateQueue,o=n.dependencies,l.dependencies=o===null?null:{lanes:o.lanes,firstContext:o.firstContext},l.sibling=n.sibling,l.index=n.index,l.ref=n.ref,l}function ea(n,o,l,c,p,x){var _=2;if(c=n,typeof n=="function")Of(n)&&(_=1);else if(typeof n=="string")_=5;else e:switch(n){case j:return Hr(l.children,p,x,o);case G:_=8,p|=8;break;case V:return n=qt(12,l,o,p|2),n.elementType=V,n.lanes=x,n;case J:return n=qt(13,l,o,p),n.elementType=J,n.lanes=x,n;case I:return n=qt(19,l,o,p),n.elementType=I,n.lanes=x,n;case q:return ta(l,p,x,o);default:if(typeof n=="object"&&n!==null)switch(n.$$typeof){case Y:_=10;break e;case ee:_=9;break e;case ne:_=11;break e;case U:_=14;break e;case F:_=16,c=null;break e}throw Error(r(130,n==null?n:typeof n,""))}return o=qt(_,l,o,p),o.elementType=n,o.type=c,o.lanes=x,o}function Hr(n,o,l,c){return n=qt(7,n,c,o),n.lanes=l,n}function ta(n,o,l,c){return n=qt(22,n,c,o),n.elementType=q,n.lanes=l,n.stateNode={isHidden:!1},n}function zf(n,o,l){return n=qt(6,n,null,o),n.lanes=l,n}function Ff(n,o,l){return o=qt(4,n.children!==null?n.children:[],n.key,o),o.lanes=l,o.stateNode={containerInfo:n.containerInfo,pendingChildren:null,implementation:n.implementation},o}function VS(n,o,l,c,p){this.tag=o,this.containerInfo=n,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=dc(0),this.expirationTimes=dc(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=dc(0),this.identifierPrefix=c,this.onRecoverableError=p,this.mutableSourceEagerHydrationData=null}function Bf(n,o,l,c,p,x,_,$,D){return n=new VS(n,o,l,$,D),o===1?(o=1,x===!0&&(o|=8)):o=0,x=qt(3,null,null,o),n.current=x,x.stateNode=n,x.memoizedState={element:c,isDehydrated:l,cache:null,transitions:null,pendingSuspenseBoundaries:null},Zc(x),n}function US(n,o,l){var c=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}return e(),Yf.exports=tE(),Yf.exports}var Cm;function nE(){if(Cm)return aa;Cm=1;var e=Uy();return aa.createRoot=e.createRoot,aa.hydrateRoot=e.hydrateRoot,aa}var rE=nE(),P=As();const yt=rt(P);var rs={},_m;function oE(){if(_m)return rs;_m=1,Object.defineProperty(rs,"__esModule",{value:!0}),rs.parse=u,rs.serialize=h;const e=/^[\u0021-\u003A\u003C\u003E-\u007E]+$/,t=/^[\u0021-\u003A\u003C-\u007E]*$/,r=/^([.]?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i,i=/^[\u0020-\u003A\u003D-\u007E]*$/,s=Object.prototype.toString,a=(()=>{const y=function(){};return y.prototype=Object.create(null),y})();function u(y,v){const S=new a,E=y.length;if(E<2)return S;const k=(v==null?void 0:v.decode)||g;let C=0;do{const R=y.indexOf("=",C);if(R===-1)break;const N=y.indexOf(";",C),b=N===-1?E:N;if(R>b){C=y.lastIndexOf(";",R-1)+1;continue}const M=f(y,C,R),L=d(y,R,M),j=y.slice(M,L);if(S[j]===void 0){let G=f(y,R+1,b),V=d(y,b,G);const Y=k(y.slice(G,V));S[j]=Y}C=b+1}while(CS;){const E=y.charCodeAt(--v);if(E!==32&&E!==9)return v+1}return S}function h(y,v,S){const E=(S==null?void 0:S.encode)||encodeURIComponent;if(!e.test(y))throw new TypeError(`argument name is invalid: ${y}`);const k=E(v);if(!t.test(k))throw new TypeError(`argument val is invalid: ${v}`);let C=y+"="+k;if(!S)return C;if(S.maxAge!==void 0){if(!Number.isInteger(S.maxAge))throw new TypeError(`option maxAge is invalid: ${S.maxAge}`);C+="; Max-Age="+S.maxAge}if(S.domain){if(!r.test(S.domain))throw new TypeError(`option domain is invalid: ${S.domain}`);C+="; Domain="+S.domain}if(S.path){if(!i.test(S.path))throw new TypeError(`option path is invalid: ${S.path}`);C+="; Path="+S.path}if(S.expires){if(!m(S.expires)||!Number.isFinite(S.expires.valueOf()))throw new TypeError(`option expires is invalid: ${S.expires}`);C+="; Expires="+S.expires.toUTCString()}if(S.httpOnly&&(C+="; HttpOnly"),S.secure&&(C+="; Secure"),S.partitioned&&(C+="; Partitioned"),S.priority)switch(typeof S.priority=="string"?S.priority.toLowerCase():void 0){case"low":C+="; Priority=Low";break;case"medium":C+="; Priority=Medium";break;case"high":C+="; Priority=High";break;default:throw new TypeError(`option priority is invalid: ${S.priority}`)}if(S.sameSite)switch(typeof S.sameSite=="string"?S.sameSite.toLowerCase():S.sameSite){case!0:case"strict":C+="; SameSite=Strict";break;case"lax":C+="; SameSite=Lax";break;case"none":C+="; SameSite=None";break;default:throw new TypeError(`option sameSite is invalid: ${S.sameSite}`)}return C}function g(y){if(y.indexOf("%")===-1)return y;try{return decodeURIComponent(y)}catch{return y}}function m(y){return s.call(y)==="[object Date]"}return rs}oE();var Nm="popstate";function iE(e={}){function t(s,a){let{pathname:u="/",search:f="",hash:d=""}=ro(s.location.hash.substring(1));return!u.startsWith("/")&&!u.startsWith(".")&&(u="/"+u),Ed("",{pathname:u,search:f,hash:d},a.state&&a.state.usr||null,a.state&&a.state.key||"default")}function r(s,a){let u=s.document.querySelector("base"),f="";if(u&&u.getAttribute("href")){let d=s.location.href,h=d.indexOf("#");f=h===-1?d:d.slice(0,h)}return f+"#"+(typeof a=="string"?a:xs(a))}function i(s,a){an(s.pathname.charAt(0)==="/",`relative pathnames are not supported in hash history.push(${JSON.stringify(a)})`)}return lE(t,r,i,e)}function qe(e,t){if(e===!1||e===null||typeof e>"u")throw new Error(t)}function an(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function sE(){return Math.random().toString(36).substring(2,10)}function bm(e,t){return{usr:e.state,key:e.key,idx:t}}function Ed(e,t,r=null,i){return{pathname:typeof e=="string"?e:e.pathname,search:"",hash:"",...typeof t=="string"?ro(t):t,state:r,key:t&&t.key||i||sE()}}function xs({pathname:e="/",search:t="",hash:r=""}){return t&&t!=="?"&&(e+=t.charAt(0)==="?"?t:"?"+t),r&&r!=="#"&&(e+=r.charAt(0)==="#"?r:"#"+r),e}function ro(e){let t={};if(e){let r=e.indexOf("#");r>=0&&(t.hash=e.substring(r),e=e.substring(0,r));let i=e.indexOf("?");i>=0&&(t.search=e.substring(i),e=e.substring(0,i)),e&&(t.pathname=e)}return t}function lE(e,t,r,i={}){let{window:s=document.defaultView,v5Compat:a=!1}=i,u=s.history,f="POP",d=null,h=g();h==null&&(h=0,u.replaceState({...u.state,idx:h},""));function g(){return(u.state||{idx:null}).idx}function m(){f="POP";let k=g(),C=k==null?null:k-h;h=k,d&&d({action:f,location:E.location,delta:C})}function y(k,C){f="PUSH";let R=Ed(E.location,k,C);r&&r(R,k),h=g()+1;let N=bm(R,h),b=E.createHref(R);try{u.pushState(N,"",b)}catch(M){if(M instanceof DOMException&&M.name==="DataCloneError")throw M;s.location.assign(b)}a&&d&&d({action:f,location:E.location,delta:1})}function v(k,C){f="REPLACE";let R=Ed(E.location,k,C);r&&r(R,k),h=g();let N=bm(R,h),b=E.createHref(R);u.replaceState(N,"",b),a&&d&&d({action:f,location:E.location,delta:0})}function S(k){let C=s.location.origin!=="null"?s.location.origin:s.location.href,R=typeof k=="string"?k:xs(k);return R=R.replace(/ $/,"%20"),qe(C,`No window.location.(origin|href) available to create URL for href: ${R}`),new URL(R,C)}let E={get action(){return f},get location(){return e(s,u)},listen(k){if(d)throw new Error("A history only accepts one active listener");return s.addEventListener(Nm,m),d=k,()=>{s.removeEventListener(Nm,m),d=null}},createHref(k){return t(s,k)},createURL:S,encodeLocation(k){let C=S(k);return{pathname:C.pathname,search:C.search,hash:C.hash}},push:y,replace:v,go(k){return u.go(k)}};return E}function Wy(e,t,r="/"){return aE(e,t,r,!1)}function aE(e,t,r,i){let s=typeof t=="string"?ro(t):t,a=On(s.pathname||"/",r);if(a==null)return null;let u=qy(e);uE(u);let f=null;for(let d=0;f==null&&d{let d={relativePath:f===void 0?a.path||"":f,caseSensitive:a.caseSensitive===!0,childrenIndex:u,route:a};d.relativePath.startsWith("/")&&(qe(d.relativePath.startsWith(i),`Absolute route path "${d.relativePath}" nested under path "${i}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),d.relativePath=d.relativePath.slice(i.length));let h=Mn([i,d.relativePath]),g=r.concat(d);a.children&&a.children.length>0&&(qe(a.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${h}".`),qy(a.children,t,g,h)),!(a.path==null&&!a.index)&&t.push({path:h,score:mE(h,a.index),routesMeta:g})};return e.forEach((a,u)=>{var f;if(a.path===""||!((f=a.path)!=null&&f.includes("?")))s(a,u);else for(let d of Yy(a.path))s(a,u,d)}),t}function Yy(e){let t=e.split("/");if(t.length===0)return[];let[r,...i]=t,s=r.endsWith("?"),a=r.replace(/\?$/,"");if(i.length===0)return s?[a,""]:[a];let u=Yy(i.join("/")),f=[];return f.push(...u.map(d=>d===""?a:[a,d].join("/"))),s&&f.push(...u),f.map(d=>e.startsWith("/")&&d===""?"/":d)}function uE(e){e.sort((t,r)=>t.score!==r.score?r.score-t.score:yE(t.routesMeta.map(i=>i.childrenIndex),r.routesMeta.map(i=>i.childrenIndex)))}var cE=/^:[\w-]+$/,fE=3,dE=2,hE=1,pE=10,gE=-2,Pm=e=>e==="*";function mE(e,t){let r=e.split("/"),i=r.length;return r.some(Pm)&&(i+=gE),t&&(i+=dE),r.filter(s=>!Pm(s)).reduce((s,a)=>s+(cE.test(a)?fE:a===""?hE:pE),i)}function yE(e,t){return e.length===t.length&&e.slice(0,-1).every((i,s)=>i===t[s])?e[e.length-1]-t[t.length-1]:0}function xE(e,t,r=!1){let{routesMeta:i}=e,s={},a="/",u=[];for(let f=0;f{if(g==="*"){let S=f[y]||"";u=a.slice(0,a.length-S.length).replace(/(.)\/+$/,"$1")}const v=f[y];return m&&!v?h[g]=void 0:h[g]=(v||"").replace(/%2F/g,"/"),h},{}),pathname:a,pathnameBase:u,pattern:e}}function vE(e,t=!1,r=!0){an(e==="*"||!e.endsWith("*")||e.endsWith("/*"),`Route path "${e}" will be treated as if it were "${e.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${e.replace(/\*$/,"/*")}".`);let i=[],s="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(u,f,d)=>(i.push({paramName:f,isOptional:d!=null}),d?"/?([^\\/]+)?":"/([^\\/]+)"));return e.endsWith("*")?(i.push({paramName:"*"}),s+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):r?s+="\\/*$":e!==""&&e!=="/"&&(s+="(?:(?=\\/|$))"),[new RegExp(s,t?void 0:"i"),i]}function wE(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return an(!1,`The URL path "${e}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${t}).`),e}}function On(e,t){if(t==="/")return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let r=t.endsWith("/")?t.length-1:t.length,i=e.charAt(r);return i&&i!=="/"?null:e.slice(r)||"/"}function SE(e,t="/"){let{pathname:r,search:i="",hash:s=""}=typeof e=="string"?ro(e):e;return{pathname:r?r.startsWith("/")?r:EE(r,t):t,search:_E(i),hash:NE(s)}}function EE(e,t){let r=t.replace(/\/+$/,"").split("/");return e.split("/").forEach(s=>{s===".."?r.length>1&&r.pop():s!=="."&&r.push(s)}),r.length>1?r.join("/"):"/"}function Kf(e,t,r,i){return`Cannot include a '${e}' character in a manually specified \`to.${t}\` field [${JSON.stringify(i)}]. Please separate it out to the \`to.${r}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function kE(e){return e.filter((t,r)=>r===0||t.route.path&&t.route.path.length>0)}function Xy(e){let t=kE(e);return t.map((r,i)=>i===t.length-1?r.pathname:r.pathnameBase)}function Gy(e,t,r,i=!1){let s;typeof e=="string"?s=ro(e):(s={...e},qe(!s.pathname||!s.pathname.includes("?"),Kf("?","pathname","search",s)),qe(!s.pathname||!s.pathname.includes("#"),Kf("#","pathname","hash",s)),qe(!s.search||!s.search.includes("#"),Kf("#","search","hash",s)));let a=e===""||s.pathname==="",u=a?"/":s.pathname,f;if(u==null)f=r;else{let m=t.length-1;if(!i&&u.startsWith("..")){let y=u.split("/");for(;y[0]==="..";)y.shift(),m-=1;s.pathname=y.join("/")}f=m>=0?t[m]:"/"}let d=SE(s,f),h=u&&u!=="/"&&u.endsWith("/"),g=(a||u===".")&&r.endsWith("/");return!d.pathname.endsWith("/")&&(h||g)&&(d.pathname+="/"),d}var Mn=e=>e.join("/").replace(/\/\/+/g,"/"),CE=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),_E=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,NE=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e;function bE(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}var Ky=["POST","PUT","PATCH","DELETE"];new Set(Ky);var PE=["GET",...Ky];new Set(PE);var ai=P.createContext(null);ai.displayName="DataRouter";var Eu=P.createContext(null);Eu.displayName="DataRouterState";var Qy=P.createContext({isTransitioning:!1});Qy.displayName="ViewTransition";var RE=P.createContext(new Map);RE.displayName="Fetchers";var TE=P.createContext(null);TE.displayName="Await";var wn=P.createContext(null);wn.displayName="Navigation";var Ms=P.createContext(null);Ms.displayName="Location";var Fn=P.createContext({outlet:null,matches:[],isDataRoute:!1});Fn.displayName="Route";var ah=P.createContext(null);ah.displayName="RouteError";function IE(e,{relative:t}={}){qe(Ls(),"useHref() may be used only in the context of a component.");let{basename:r,navigator:i}=P.useContext(wn),{hash:s,pathname:a,search:u}=Ds(e,{relative:t}),f=a;return r!=="/"&&(f=a==="/"?r:Mn([r,a])),i.createHref({pathname:f,search:u,hash:s})}function Ls(){return P.useContext(Ms)!=null}function oo(){return qe(Ls(),"useLocation() may be used only in the context of a component."),P.useContext(Ms).location}var Jy="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function Zy(e){P.useContext(wn).static||P.useLayoutEffect(e)}function jE(){let{isDataRoute:e}=P.useContext(Fn);return e?WE():$E()}function $E(){qe(Ls(),"useNavigate() may be used only in the context of a component.");let e=P.useContext(ai),{basename:t,navigator:r}=P.useContext(wn),{matches:i}=P.useContext(Fn),{pathname:s}=oo(),a=JSON.stringify(Xy(i)),u=P.useRef(!1);return Zy(()=>{u.current=!0}),P.useCallback((d,h={})=>{if(an(u.current,Jy),!u.current)return;if(typeof d=="number"){r.go(d);return}let g=Gy(d,JSON.parse(a),s,h.relative==="path");e==null&&t!=="/"&&(g.pathname=g.pathname==="/"?t:Mn([t,g.pathname])),(h.replace?r.replace:r.push)(g,h.state,h)},[t,r,a,s,e])}P.createContext(null);function Ds(e,{relative:t}={}){let{matches:r}=P.useContext(Fn),{pathname:i}=oo(),s=JSON.stringify(Xy(r));return P.useMemo(()=>Gy(e,JSON.parse(s),i,t==="path"),[e,s,i,t])}function AE(e,t){return ex(e,t)}function ex(e,t,r,i){var R;qe(Ls(),"useRoutes() may be used only in the context of a component.");let{navigator:s,static:a}=P.useContext(wn),{matches:u}=P.useContext(Fn),f=u[u.length-1],d=f?f.params:{},h=f?f.pathname:"/",g=f?f.pathnameBase:"/",m=f&&f.route;{let N=m&&m.path||"";tx(h,!m||N.endsWith("*")||N.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${h}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. + +Please change the parent to .`)}let y=oo(),v;if(t){let N=typeof t=="string"?ro(t):t;qe(g==="/"||((R=N.pathname)==null?void 0:R.startsWith(g)),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${g}" but pathname "${N.pathname}" was given in the \`location\` prop.`),v=N}else v=y;let S=v.pathname||"/",E=S;if(g!=="/"){let N=g.replace(/^\//,"").split("/");E="/"+S.replace(/^\//,"").split("/").slice(N.length).join("/")}let k=!a&&r&&r.matches&&r.matches.length>0?r.matches:Wy(e,{pathname:E});an(m||k!=null,`No routes matched location "${v.pathname}${v.search}${v.hash}" `),an(k==null||k[k.length-1].route.element!==void 0||k[k.length-1].route.Component!==void 0||k[k.length-1].route.lazy!==void 0,`Matched leaf route at location "${v.pathname}${v.search}${v.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let C=zE(k&&k.map(N=>Object.assign({},N,{params:Object.assign({},d,N.params),pathname:Mn([g,s.encodeLocation?s.encodeLocation(N.pathname).pathname:N.pathname]),pathnameBase:N.pathnameBase==="/"?g:Mn([g,s.encodeLocation?s.encodeLocation(N.pathnameBase).pathname:N.pathnameBase])})),u,r,i);return t&&C?P.createElement(Ms.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",...v},navigationType:"POP"}},C):C}function ME(){let e=UE(),t=bE(e)?`${e.status} ${e.statusText}`:e instanceof Error?e.message:JSON.stringify(e),r=e instanceof Error?e.stack:null,i="rgba(200,200,200, 0.5)",s={padding:"0.5rem",backgroundColor:i},a={padding:"2px 4px",backgroundColor:i},u=null;return console.error("Error handled by React Router default ErrorBoundary:",e),u=P.createElement(P.Fragment,null,P.createElement("p",null,"💿 Hey developer 👋"),P.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",P.createElement("code",{style:a},"ErrorBoundary")," or"," ",P.createElement("code",{style:a},"errorElement")," prop on your route.")),P.createElement(P.Fragment,null,P.createElement("h2",null,"Unexpected Application Error!"),P.createElement("h3",{style:{fontStyle:"italic"}},t),r?P.createElement("pre",{style:s},r):null,u)}var LE=P.createElement(ME,null),DE=class extends P.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!=="idle"&&e.revalidation==="idle"?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error!==void 0?e.error:t.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){console.error("React Router caught the following error during render",e,t)}render(){return this.state.error!==void 0?P.createElement(Fn.Provider,{value:this.props.routeContext},P.createElement(ah.Provider,{value:this.state.error,children:this.props.component})):this.props.children}};function OE({routeContext:e,match:t,children:r}){let i=P.useContext(ai);return i&&i.static&&i.staticContext&&(t.route.errorElement||t.route.ErrorBoundary)&&(i.staticContext._deepestRenderedBoundaryId=t.route.id),P.createElement(Fn.Provider,{value:e},r)}function zE(e,t=[],r=null,i=null){if(e==null){if(!r)return null;if(r.errors)e=r.matches;else if(t.length===0&&!r.initialized&&r.matches.length>0)e=r.matches;else return null}let s=e,a=r==null?void 0:r.errors;if(a!=null){let d=s.findIndex(h=>h.route.id&&(a==null?void 0:a[h.route.id])!==void 0);qe(d>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(a).join(",")}`),s=s.slice(0,Math.min(s.length,d+1))}let u=!1,f=-1;if(r)for(let d=0;d=0?s=s.slice(0,f+1):s=[s[0]];break}}}return s.reduceRight((d,h,g)=>{let m,y=!1,v=null,S=null;r&&(m=a&&h.route.id?a[h.route.id]:void 0,v=h.route.errorElement||LE,u&&(f<0&&g===0?(tx("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),y=!0,S=null):f===g&&(y=!0,S=h.route.hydrateFallbackElement||null)));let E=t.concat(s.slice(0,g+1)),k=()=>{let C;return m?C=v:y?C=S:h.route.Component?C=P.createElement(h.route.Component,null):h.route.element?C=h.route.element:C=d,P.createElement(OE,{match:h,routeContext:{outlet:d,matches:E,isDataRoute:r!=null},children:C})};return r&&(h.route.ErrorBoundary||h.route.errorElement||g===0)?P.createElement(DE,{location:r.location,revalidation:r.revalidation,component:v,error:m,children:k(),routeContext:{outlet:null,matches:E,isDataRoute:!0}}):k()},null)}function uh(e){return`${e} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function FE(e){let t=P.useContext(ai);return qe(t,uh(e)),t}function BE(e){let t=P.useContext(Eu);return qe(t,uh(e)),t}function HE(e){let t=P.useContext(Fn);return qe(t,uh(e)),t}function ch(e){let t=HE(e),r=t.matches[t.matches.length-1];return qe(r.route.id,`${e} can only be used on routes that contain a unique "id"`),r.route.id}function VE(){return ch("useRouteId")}function UE(){var i;let e=P.useContext(ah),t=BE("useRouteError"),r=ch("useRouteError");return e!==void 0?e:(i=t.errors)==null?void 0:i[r]}function WE(){let{router:e}=FE("useNavigate"),t=ch("useNavigate"),r=P.useRef(!1);return Zy(()=>{r.current=!0}),P.useCallback(async(s,a={})=>{an(r.current,Jy),r.current&&(typeof s=="number"?e.navigate(s):await e.navigate(s,{fromRouteId:t,...a}))},[e,t])}var Rm={};function tx(e,t,r){!t&&!Rm[e]&&(Rm[e]=!0,an(!1,r))}P.memo(qE);function qE({routes:e,future:t,state:r}){return ex(e,void 0,r,t)}function nx(e){qe(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function YE({basename:e="/",children:t=null,location:r,navigationType:i="POP",navigator:s,static:a=!1}){qe(!Ls(),"You cannot render a inside another . You should never have more than one in your app.");let u=e.replace(/^\/*/,"/"),f=P.useMemo(()=>({basename:u,navigator:s,static:a,future:{}}),[u,s,a]);typeof r=="string"&&(r=ro(r));let{pathname:d="/",search:h="",hash:g="",state:m=null,key:y="default"}=r,v=P.useMemo(()=>{let S=On(d,u);return S==null?null:{location:{pathname:S,search:h,hash:g,state:m,key:y},navigationType:i}},[u,d,h,g,m,y,i]);return an(v!=null,` is not able to match the URL "${d}${h}${g}" because it does not start with the basename, so the won't render anything.`),v==null?null:P.createElement(wn.Provider,{value:f},P.createElement(Ms.Provider,{children:t,value:v}))}function XE({children:e,location:t}){return AE(kd(e),t)}function kd(e,t=[]){let r=[];return P.Children.forEach(e,(i,s)=>{if(!P.isValidElement(i))return;let a=[...t,s];if(i.type===P.Fragment){r.push.apply(r,kd(i.props.children,a));return}qe(i.type===nx,`[${typeof i.type=="string"?i.type:i.type.name}] is not a component. All component children of must be a or `),qe(!i.props.index||!i.props.children,"An index route cannot have child routes.");let u={id:i.props.id||a.join("-"),caseSensitive:i.props.caseSensitive,element:i.props.element,Component:i.props.Component,index:i.props.index,path:i.props.path,loader:i.props.loader,action:i.props.action,hydrateFallbackElement:i.props.hydrateFallbackElement,HydrateFallback:i.props.HydrateFallback,errorElement:i.props.errorElement,ErrorBoundary:i.props.ErrorBoundary,hasErrorBoundary:i.props.hasErrorBoundary===!0||i.props.ErrorBoundary!=null||i.props.errorElement!=null,shouldRevalidate:i.props.shouldRevalidate,handle:i.props.handle,lazy:i.props.lazy};i.props.children&&(u.children=kd(i.props.children,a)),r.push(u)}),r}var za="get",Fa="application/x-www-form-urlencoded";function ku(e){return e!=null&&typeof e.tagName=="string"}function GE(e){return ku(e)&&e.tagName.toLowerCase()==="button"}function KE(e){return ku(e)&&e.tagName.toLowerCase()==="form"}function QE(e){return ku(e)&&e.tagName.toLowerCase()==="input"}function JE(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function ZE(e,t){return e.button===0&&(!t||t==="_self")&&!JE(e)}var ua=null;function ek(){if(ua===null)try{new FormData(document.createElement("form"),0),ua=!1}catch{ua=!0}return ua}var tk=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function Qf(e){return e!=null&&!tk.has(e)?(an(!1,`"${e}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${Fa}"`),null):e}function nk(e,t){let r,i,s,a,u;if(KE(e)){let f=e.getAttribute("action");i=f?On(f,t):null,r=e.getAttribute("method")||za,s=Qf(e.getAttribute("enctype"))||Fa,a=new FormData(e)}else if(GE(e)||QE(e)&&(e.type==="submit"||e.type==="image")){let f=e.form;if(f==null)throw new Error('Cannot submit a - ); -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/UI/buttons/quitButton.tsx b/FRONT/BUILD/resources/app/src/components/UI/buttons/quitButton.tsx deleted file mode 100644 index 373655f..0000000 --- a/FRONT/BUILD/resources/app/src/components/UI/buttons/quitButton.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { MainButton } from "./mainButton"; -import styled from "styled-components"; -import { buttonNames } from "../../../const/buttons/names"; -import { IconComponent as Icon } from '../../../const/buttons/icons'; -import { useWorkSpaceStore } from "../../../store"; - -type Props = { - size: "small" | "big"; -}; - -const Container = styled.div<{ size: "small" | "big" }>` - ${(props) => props.size === "small" && "width: 100%;"} -`; - -const Button = styled.div` - width: 100%; - cursor: pointer; - color: ${(props) => props.theme.textColor}; -`; - -const IconContainer = styled.div` - padding: 10px; -`; - -export const QuitButton = ({ size }: Props) => { - const handleExit = () => { - window.electron.quitApp(); - }; - - const { setWorkModule } = useWorkSpaceStore() - - return ( - setWorkModule('helloWindow')}> - {size === "big" ? ( - - ) : ( - - )} - - ); -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/UI/buttons/toggletheme.tsx b/FRONT/BUILD/resources/app/src/components/UI/buttons/toggletheme.tsx deleted file mode 100644 index f1ca231..0000000 --- a/FRONT/BUILD/resources/app/src/components/UI/buttons/toggletheme.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import styled, { useTheme } from "styled-components"; -import { IconComponent as Icon } from '../../../const/theme/icons'; -import { ThemName } from "../../../const/theme/names"; - -const Button = styled.div` - width: 100%; - cursor: pointer; -` - -const IconContainer = styled.div` - padding: 10px; - color: ${props => props.theme.text}; -` - -export default function ThemeButton() { - const theme = useTheme(); - const name = localStorage.getItem('theme') as ThemName - - return ( - - ); -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/UI/inputs/input.tsx b/FRONT/BUILD/resources/app/src/components/UI/inputs/input.tsx deleted file mode 100644 index 0fac915..0000000 --- a/FRONT/BUILD/resources/app/src/components/UI/inputs/input.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; - -type Props = { - placeholderText: string; - id: string; - value?: string; - onChange?: (identifier: string, value: string) => void; - onFileChange?: (event: React.ChangeEvent) => void; - type?: string; - multiple?: boolean; - style?: React.CSSProperties; - search?: boolean; - onFocus?: any - onBlur?: any - $size?: string -} - -const InputContainer = styled.div` - width: 100%; - display: flex; - flex: 3; - -`; - -const StyledInput = styled.input` - padding: 8px 10px; - color: black; - border-radius: 8px; - border: 1px solid #A1A1A1; - background-color: transparent; - &:focus{ - border: 1px solid #adadad; - } - width: 100%; - font-size: 14px; - color: ${props=>props.theme.textColor} -`; - -const Input = React.forwardRef(({ placeholderText, id, value, onChange, onFileChange, type = "text", multiple, style, onFocus, onBlur }, ref) => { - const handleInputChange = (e: React.ChangeEvent) => { - const { value } = e.target; - if (onChange) { - onChange(id, value); - } - }; - - return ( - - - - ); -}); - -export default Input; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/UI/loader.tsx b/FRONT/BUILD/resources/app/src/components/UI/loader.tsx deleted file mode 100644 index c13bd3f..0000000 --- a/FRONT/BUILD/resources/app/src/components/UI/loader.tsx +++ /dev/null @@ -1,101 +0,0 @@ -type Props = { - loaderText: string; //Тип передаваемых данных loaderText - string -} - -import styled, { keyframes } from 'styled-components' -import { AnimDot } from './animDot'; - - -//Константа которая устанавливает кейфвреймы для вращения -const spinAnimation = keyframes` - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -` - -//Стиль экрана загрузки -export const LoaderContainer = styled.div` - top: 0px; - z-index:998; - background-color: rgba(0,0,0, 0.7); - position: fixed; - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - overflow-y: hidden; -` - - -//Стиль блока с элементами загрузки -export const Loaderblock = styled.div` - display: flex; - flex-direction: column; - align-items: center; - gap: 15px; - - p{ - font-size: 24px; - color: white; - } -` -//Стиль для блока с кольцом загрузки -export const LoadingSpinner = styled.div` - position: relative; - width: 65px; - height: 65px; -` - -//Стиль для кольца загрузки -export const LoadingRing = styled.div` - position: absolute; - top: 0; - left: 0; - width: 65px; - height: 65px; - border-radius: 50%; - border: 12px solid white; -` -//Стиль для части кольца загрузки, которая будет вращаться -export const LoadingPart = styled.div` - position: absolute; - top: 0; - left: 0; - width: 65px; - height: 65px; - border-radius: 50%; - border: 12px solid transparent; - border-top: 12px solid ${props => props.theme.accent}; - - // Добавление аниманции и использование кейфремов - animation: ${spinAnimation} 1s linear infinite; -` - -const Text = styled.div` - display: flex; - gap: 3px; - font-size: 24px; - color: white; - margin-top: 30px; - margin-left: 25px; -` - -export default function Loader({ loaderText }: Props) { - return ( - - - - - - - - - {/* Текст экрана загрузки устанавливается с помощью передаваемых данных loaderText */} - {loaderText} - - ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/UI/routerLoader.tsx b/FRONT/BUILD/resources/app/src/components/UI/routerLoader.tsx deleted file mode 100644 index 9a788f4..0000000 --- a/FRONT/BUILD/resources/app/src/components/UI/routerLoader.tsx +++ /dev/null @@ -1,103 +0,0 @@ -type Props = { - loaderText: string; //Тип передаваемых данных loaderText - string -} - -import styled, { keyframes } from 'styled-components' -import { AnimDot } from './animDot'; -import { ModalWindow } from '../modalwindows/style'; - - -//Константа которая устанавливает кейфвреймы для вращения -const spinAnimation = keyframes` - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -` - -//Стиль экрана загрузки -export const LoaderContainer = styled.div` - top: 0px; - z-index:998; - background-color: rgba(0,0,0, 0.7); - position: fixed; - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - overflow-y: hidden; -` - - -//Стиль блока с элементами загрузки -export const Loaderblock = styled.div` - display: flex; - flex-direction: column; - align-items: center; - gap: 15px; - - p{ - font-size: 24px; - color: white; - } -` -//Стиль для блока с кольцом загрузки -export const LoadingSpinner = styled.div` - position: relative; - width: 65px; - height: 65px; -` - -//Стиль для кольца загрузки -export const LoadingRing = styled.div` - position: absolute; - top: 0; - left: 0; - width: 65px; - height: 65px; - border-radius: 50%; - border: 12px solid white; -` -//Стиль для части кольца загрузки, которая будет вращаться -export const LoadingPart = styled.div` - position: absolute; - top: 0; - left: 0; - width: 65px; - height: 65px; - border-radius: 50%; - border: 12px solid transparent; - border-top: 12px solid ${props => props.theme.accent}; - - // Добавление аниманции и использование кейфремов - animation: ${spinAnimation} 1s linear infinite; -` - -const Text = styled.div` - display: flex; - gap: 3px; - font-size: 24px; - color: white; - margin-top: 30px; - margin-left: 25px; -` - -export default function RouterLoader({ loaderText }: Props) { - return ( - - - - - - - - {/* Текст экрана загрузки устанавливается с помощью передаваемых данных loaderText */} - {loaderText} - - - - ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/UI/signalSvg.tsx b/FRONT/BUILD/resources/app/src/components/UI/signalSvg.tsx deleted file mode 100644 index a8c5c32..0000000 --- a/FRONT/BUILD/resources/app/src/components/UI/signalSvg.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, { useEffect, useRef } from 'react'; - -interface SignalSVGProps { - signal: number; // Принимаем signal как пропс -} - -const SignalSVG: React.FC = ({ signal }) => { - const svgRef = useRef(null); - - useEffect(() => { - // Устанавливаем цвета в зависимости от signal - const colors = { - '--st1-color': signal >= 1 ? 'white' : 'black', - '--st2-color': signal >= 2 ? 'white' : 'black', - '--st3-color': signal >= 3 ? 'white' : 'black', - '--st4-color': signal >= 4 ? 'white' : 'black', - '--st5-color': signal === 5 ? 'white' : 'black', - }; - - // Применяем стили к SVG - if (svgRef.current) { - Object.entries(colors).forEach(([key, value]) => { - svgRef.current?.style.setProperty(key, value); - }); - } - }, [signal]); - - return ( - - - - - - - - - - - ); -}; - -export default SignalSVG; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/circle/circleNode.tsx b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/circle/circleNode.tsx deleted file mode 100644 index 94eb76c..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/circle/circleNode.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import { Handle, Position } from '@xyflow/react'; -import { Circle } from './style'; - -interface CustomNodeProps { - data: { - label: string; - style:{ - width: any; - height: any; - borderType: any; - borderColor: any; - } - }; -} - -export const CircleNode = ({ data }: CustomNodeProps) => { - return ( - {/* */} - {/*
*/} - {data.label} - - {/* Узлы для подключения */} - - - - - - {/* Для входящих подключений */} - - - - - {/*
*/} - -
- ); -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/circle/style.ts b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/circle/style.ts deleted file mode 100644 index df61ace..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/circle/style.ts +++ /dev/null @@ -1,20 +0,0 @@ -import styled from "styled-components"; - -type Props={ - width: string, - height: string, - bordertype: string, - bordercolor: string -} - - -export const Circle = styled.div` - background-color: white; - border-radius: 100%; - display: flex; - align-items: center; - justify-content: center; - border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor}; - width: ${(props) => props.width}px; - height: ${(props) => props.height}px; -` \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/default/defaultNode.tsx b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/default/defaultNode.tsx deleted file mode 100644 index 388e9fb..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/default/defaultNode.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { Handle, Position } from '@xyflow/react'; -import { Default } from './style'; - -interface CustomNodeProps { - data: { - label: string; - style:{ - width: any; - height: any; - borderType: any; - borderColor: any; - } - }; -} - -export const DefaultNode = ({ data }: CustomNodeProps) => { - return ( - - {data.label} - - {/* Узлы для подключения */} - {/* - - - */} - - {/* Для входящих подключений */} - {/* */} - - {/* - */} - {/* */} - - - ); -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/default/style.ts b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/default/style.ts deleted file mode 100644 index 62595bc..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/default/style.ts +++ /dev/null @@ -1,20 +0,0 @@ -import styled from "styled-components"; - -type Props={ - width: string, - height: string, - bordertype: string, - bordercolor: string -} - -export const Default = styled.div` - background-color: white; - border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor}; - width: ${(props) => props.width}px; - height: ${(props) => props.height}px; - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; - visibility: visible; -` \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/diamond/diamondNode.tsx b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/diamond/diamondNode.tsx deleted file mode 100644 index 49ab61c..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/diamond/diamondNode.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import { Handle, Position } from '@xyflow/react'; -import { Diamond, InnerDiamond, Border } from './style'; - -interface CustomNodeProps { - data: { - label: string; - style:{ - width: any; - height: any; - borderType: any; - borderColor: any; - } - }; -} - -export const DiamondNode = ({ data }: CustomNodeProps) => { - - const baseSize = Math.max(data.style.width, data.style.height); - - return ( - {/* */} - - - {data.label} - - - -
- {/* Узлы для подключения */} - - - - - - {/* Для входящих подключений */} - - - - -
- -
- ); -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/diamond/style.ts b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/diamond/style.ts deleted file mode 100644 index 5eb0ba0..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/diamond/style.ts +++ /dev/null @@ -1,32 +0,0 @@ -import styled from "styled-components"; - -interface StyledDefaultProps { - width: any; - height: any; -} - -export const Diamond = styled.div` - position: relative; - width: ${(props) => props.width}px; - height: ${(props) => props.height}px; -`; - -export const InnerDiamond = styled.div` - clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); - width: ${(props) => props.width - 6}px; - height: ${(props) => props.height - 4}px; - background-color: white; - display: flex; - align-items: center; - justify-content: center; -` - -export const Border = styled.div<{ width: number; height: number, bordercolor: any }>` - clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); - width: ${(props) => props.width}px; - height: ${(props) => props.height}px; - background-color: ${(props) => props.bordercolor}; - display: flex; - align-items: center; - justify-content: center; -`; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/index.ts b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/index.ts deleted file mode 100644 index 9827eef..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export {DefaultNode} from './default/defaultNode' -export {PillNode} from './pill/pillNode' -export {ParalelorgamNode} from './paralelogram/paralelogramNode' -export {CircleNode} from './circle/circleNode' -export {DiamondNode} from './diamond/diamondNode' \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/paralelogram/paralelogramNode.tsx b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/paralelogram/paralelogramNode.tsx deleted file mode 100644 index 699f00c..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/paralelogram/paralelogramNode.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import { Handle, Position } from '@xyflow/react'; -import { Paralelorgam } from './style'; - -interface CustomNodeProps { - data: { - label: string; - style:{ - width: any; - height: any; - borderType: any; - borderColor: any; - } - }; -} - -export const ParalelorgamNode = ({ data }: CustomNodeProps) => { - return ( - - {data.label} - - {/* Узлы для подключения */} - - - - - - {/* Для входящих подключений */} - - - - - - ); -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/paralelogram/style.ts b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/paralelogram/style.ts deleted file mode 100644 index 46791e4..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/paralelogram/style.ts +++ /dev/null @@ -1,43 +0,0 @@ -import styled from "styled-components"; - -type Props={ - width: string, - height: string, - bordertype: string, - bordercolor: string -} - - -export const Paralelorgam = styled.div` - transform: skewX(-20deg); - background-color: white; - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; - border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor}; - width: ${(props) => props.width}px; - height: ${(props) => props.height}px; - - .react-flow__handle-top{ - transform: skewX(20deg); - top: -5px; - } - - .react-flow__handle-bottom{ - transform: skewX(20deg); - bottom: -5px; - } - - .react-flow__handle-right{ - transform: skewX(20deg); - right: -5px; - top: 25px; - } - - .react-flow__handle-left{ - transform: skewX(20deg); - left: -5px; - top: 25px; - } -` \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/pill/pillNode.tsx b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/pill/pillNode.tsx deleted file mode 100644 index 7adf271..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/pill/pillNode.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import { Handle, Position } from '@xyflow/react'; -import { Pill } from './style'; - -interface CustomNodeProps { - data: { - label: string; - style:{ - width: any; - height: any; - borderType: any; - borderColor: any; - } - }; -} - -export const PillNode = ({ data }: CustomNodeProps) => { - return ( - {/* */} - {/*
*/} - {data.label} - - {/* Узлы для подключения */} - - - - - - {/* Для входящих подключений */} - - - - - {/*
*/} - -
- ); -}; - -export default PillNode; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/pill/style.ts b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/pill/style.ts deleted file mode 100644 index d08fe21..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/baseForm/pill/style.ts +++ /dev/null @@ -1,19 +0,0 @@ -import styled from "styled-components"; - -type Props={ - width: string, - height: string, - bordertype: string, - bordercolor: string -} - -export const Pill = styled.div` - background-color: white; - border-radius: 50px; - display: flex; - align-items: center; - justify-content: center; - border: 2px ${(props) => props.bordertype} ${(props) => props.bordercolor}; - width: ${(props) => props.width}px; - height: ${(props) => props.height}px; -` \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/index.ts b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/index.ts deleted file mode 100644 index 64b1036..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export {NodeGroupNode} from './networkGroup/nodeGroup' -export {NetWorkNode} from './networkNode/networkNode' -export {IpGroupeNode} from './ipGroup/ipGroup' -export {IpNode} from './ipNode/ipNode' -export {PortNode} from './portNode/portNode' \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipGroup/ipGroup.tsx b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipGroup/ipGroup.tsx deleted file mode 100644 index 750e612..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipGroup/ipGroup.tsx +++ /dev/null @@ -1,18 +0,0 @@ - -import { IpGroup } from "./style"; - -interface CustomNodeProps { - data: { - style: { - width: any, - height: any, - } - }; -} - -export const IpGroupeNode = ({ data }: CustomNodeProps) => { - return ( - - ); -}; - diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipGroup/style.ts b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipGroup/style.ts deleted file mode 100644 index 606ce1a..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipGroup/style.ts +++ /dev/null @@ -1,12 +0,0 @@ -import styled from "styled-components" - -type Props={ - $width: string, - $height?: any -} - - -export const IpGroup = styled.div` - width: ${(props) => props.$width}px; - height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height}; -` \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipNode/ipNode.tsx b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipNode/ipNode.tsx deleted file mode 100644 index 3af7e64..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipNode/ipNode.tsx +++ /dev/null @@ -1,245 +0,0 @@ -import { useState } from 'react'; -import { Handle, Position } from '@xyflow/react'; -import { Ip } from './style'; -import { Row, IpTitle, IpContent } from './style'; -import Input from '../../../../UI/inputs/input'; -import { useModalContext, usePannableStore } from '../../../../../store'; -import { ModalMap } from '../../../../modalwindows/windowsType'; -import { validateIPAddress, validateSameSubnet } from '../../../../../utils/nodeController/validator'; -import {changeIp} from '../../../../../const/types/types' -import { IconComponent } from "../../../../../const/buttons/icons"; -import { buttonNames } from "../../../../../const/buttons/names"; - -interface IIp { - ipType: string - ipAdress: string, - ipGate: string, - ipDns: string[] - ipVersion: string -} - -type StateType = Record; -const typeMap: StateType = { - 'auto': "DHCP", - 'manual': "Static" -} - -interface CustomNodeProps { - data: { - ipData: IIp, - interfaceData: { - device: string, - connection: string - }, - style: { - width: any, - height: any, - borderType: string, - borderColor: string, - } - route: boolean - } -} - -export const IpNode = ({ data }: CustomNodeProps) => { - const { toggleEditingMode } = usePannableStore(); - const [isInput, setIsInput] = useState(false) - const { openModal } = useModalContext(); - - const [ipData, setIpData] = useState( - { - currentIp: data.ipData.ipAdress, - newIp: data.ipData.ipAdress, - currentGate: data.ipData.ipGate, - newGate: data.ipData.ipGate, - currentDns: data.ipData.ipDns, - newDns: data.ipData.ipDns, - device: data.interfaceData.device, - connection: data.interfaceData.connection, - version: data.ipData.ipVersion, - prefix: 0 - } - ) - - const handleFocus = () => { - toggleEditingMode(); - }; - - const handleBlur = () => { - toggleEditingMode(); - }; - - const handleClick = () => { - if (isInput) { - setIsInput(false) - setIpData(prevData => ({ - ...prevData, - newIp: data.ipData.ipAdress, - newGate: data.ipData.ipGate, - newDns: data.ipData.ipDns - })); - } - else { - setIsInput(true) - } - } - - const handleChange = (id: string, value: string) => { - if (id === 'newDns') { - const dnsArray = value.split(',').map(dns => dns.trim()); - setIpData(prevData => ({ - ...prevData, - [id]: dnsArray - })); - } else { - setIpData(prevData => ({ - ...prevData, - [id]: value - })); - } - }; - - function getSubnetMask(ipAddress: string, ipVersion: string): number { - if (ipVersion === "6") { - return 64; - } - - const firstOctet = parseInt(ipAddress.split(".")[0], 10); - if (firstOctet >= 0 && firstOctet <= 127) { - return 8; - } else if (firstOctet >= 128 && firstOctet <= 191) { - return 16; - } else if (firstOctet >= 192 && firstOctet <= 223) { - return 24; - } else { - return 24; - } - } - - - const handleConfirm = () => { - if ( - !validateIPAddress(ipData.newIp) || - !validateIPAddress(ipData.newGate) || - !ipData.newDns.every(dns => validateIPAddress(dns)) - ) { - alert('Один или несколько IP-адресов некорректны.'); - return; - } - - if (!validateSameSubnet(ipData.newIp, ipData.newGate, ipData.newDns)) { - alert('Адрес, шлюз и хотя бы один DNS должны находиться в одной подсети.'); - return; - } - - const calculatedPrefix = getSubnetMask(ipData.newIp, data.ipData.ipVersion); - - setIpData(prevData => ({ - ...prevData, - prefix: calculatedPrefix, - })); - - openModal(ModalMap['1'], { - ...ipData, - prefix: calculatedPrefix, - }); - - setIsInput(false) - }; - - - return ( - <> - - - - {/* -

Версия IP:

-

v{data.ipData.ipVersion}

-
*/} - - -

Метод:

-

{typeMap[data.ipData.ipType]}

-
- - {isInput ? ( - - - - - - ) : ( - - )} - -
- - - -

IPv{data.ipData.ipVersion}-Addr:

- {isInput ? ( - handleChange(id, value)} - onBlur={handleBlur} - onFocus={handleFocus} - style={{fontSize: '12px', padding: '4px 10px'}} - /> - ) : ( -

{data.ipData.ipAdress}

- )} - -
- - -

IPv{data.ipData.ipVersion}-GW:

- {isInput ? ( - handleChange(id, value)} - onBlur={handleBlur} - onFocus={handleFocus} - style={{fontSize: '12px', padding: '4px 10px'}} - /> - ) : ( -

{data.ipData.ipGate}

- )} -
- - -

IPv{data.ipData.ipVersion}-DNS:

- {isInput ? ( - handleChange(id, value)} - onBlur={handleBlur} - onFocus={handleFocus} - style={{fontSize: '12px', padding: '4px 10px'}} - /> - ) : ( -

{data.ipData.ipDns.join(', ')}

- )} -
- - {data.route && - <> - - - - } -
-
- - - ); -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipNode/style.ts b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipNode/style.ts deleted file mode 100644 index 3355417..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/ipNode/style.ts +++ /dev/null @@ -1,74 +0,0 @@ -import styled from "styled-components"; - -type Props={ - $width: string, - $height?: any - $bordertype: string, - $bordercolor: string -} - -export const Ip = styled.div` - background-color: ${(props)=>props.theme.secondary}; - display: flex; - flex-direction: column; - width: ${(props) => props.$width}px; - height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height}; - // box-shadow: 0px 0px 15px 2px rgba(0, 0, 0, 0.1); - border-radius: 8px; - - p{ - margin: 0; - } - - img{ - width: 15px; - height: 15px; - } -` - -export const IpTitle = styled.div` - background: ${(props)=>props.theme.accent}; - border-top-left-radius: 6px; - border-top-right-radius: 6px; - color: white; - display: flex; - justify-content: space-between; - padding: 10px; - -` - -export const IpContent = styled.div` - display: flex; - flex-direction: column; - justify-content: space-between; - height: 100%; - padding: 10px; - - .row{ - height: 24px; - - .row-label{ - flex: 1; - } - - .row-text{ - flex: 3; - } - - p{ - - text-wrap: nowrap; - text-align: left; - } - } -` - -export const Row = styled.div` - display: flex; - align-items: center; - gap: 5px; - - p{ - margin: 0; - } -` diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/networkGroup/nodeGroup.tsx b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/networkGroup/nodeGroup.tsx deleted file mode 100644 index e7b3f66..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/networkGroup/nodeGroup.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useState } from "react"; -import { NodeGroup, Row, CollapseButton } from "./style"; -import { useGroupStore } from "../../../../../store"; -import { MainButton } from "../../../../UI/buttons/mainButton"; -import { IconComponent } from "../../../../../const/buttons/icons"; -import { buttonNames } from "../../../../../const/buttons/names"; - -interface CustomNodeProps { - data: { - id: string; - label: string; - height: any - style: { - width: any; - height: any; - borderType: any; - borderColor: any; - }; - }; -} - -export const NodeGroupNode = ({ data }: CustomNodeProps) => { - const [click, setClick] = useState(false); - const { groups, setGroups } = useGroupStore(); - - const handleClick = () => { - const newClickState = !click; - setClick(newClickState); - - const updatedNodes = groups.map((groups) => - groups.id === data.id ? { ...groups, click: newClickState } : groups - ); - - if (!groups.some((groups) => groups.id === data.id)) { - updatedNodes.push({ id: data.id, click: newClickState }); - } - - setGroups(updatedNodes); - }; - - return ( - - -

{data.label}

- - {/* */} - - {click ?( - 'Развернуть' - ):( - 'Свернуть' - )} - - {/* */} - -
-
- ); -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/networkGroup/style.ts b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/networkGroup/style.ts deleted file mode 100644 index 9311bd5..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/networkGroup/style.ts +++ /dev/null @@ -1,47 +0,0 @@ -import styled from "styled-components" - -type Props={ - $width?: string, - $height?: any - $bordertype?: string, - $bordercolor?: string - $backgroundcolor?: string -} - - -export const NodeGroup = styled.div` - border: 2px ${(props) => props.$bordertype} ${(props) => props.$bordercolor}; - width: ${(props) => props.$width}px; - height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height}; - border-radius: 12px; - display: flex; - align-items: center; - justify-content: center; - visibility: visible; -` - -export const Row = styled.div` - position: absolute; - top: -25px; - left: 10px; - display: flex; - justify-content: space-between; - align-items: center; - width: ${props=>props.$width}px; - gap: 5px; - padding-right: 10px; - - p{ - margin: 0; - } -` - -export const CollapseButton = styled.p` - padding: unset; - background: none; - color: ${(props)=>props.theme.textColor}; - margin-right: 15px !important; - &:hover{ - background: none; - } -` \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/networkNode/networkNode.tsx b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/networkNode/networkNode.tsx deleted file mode 100644 index 738a2b0..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/networkNode/networkNode.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Handle, Position } from '@xyflow/react'; -import { Network, TitleContainer, Device, Status, Content, Row, MAC, LabelColumn } from './style'; -import SignalSVG from '../../../../UI/signalSvg'; - -interface CustomNodeProps { - data: { - networkData: { - device: string - type: string - mac: string - mtu: number - state: string - connection: string - id: string, - strength: string - } - style: { - width: any, - height: any, - borderType: string, - borderColor: string, - backgroundColor: string - } - height: string - visible: true - }; -} - -export const NetWorkNode = ({ data }: CustomNodeProps) => { - const [signal, setSignal] = useState(1); - - useEffect(() => { - const perc = Number(data.networkData.strength); - - switch (true) { - case perc < 20: - setSignal(1); - break; - case perc < 40: - setSignal(2); - break; - case perc < 60: - setSignal(3); - break; - case perc < 80: - setSignal(4); - break; - default: - setSignal(5); - break; - } - - }, [data.networkData.strength]) - - return ( - - - - {data.networkData.type == "WIFI" && - - } - - {data.networkData.type == "ETHERNET" && - - } - - {data.networkData.type == "TUN" && - - } - - {data.networkData.device} - - {data.networkData.type == 'WIFI' && data.networkData.state === "Подключено" ? ( - - - - ) : ( -
- ) - } - -
- - {data.visible && - - -

Конфигурация

- - -

MAC:

-

{data.networkData.mac}

-
- -

MTU:

-

{data.networkData.mtu}

-
-
-
-
- } - - {data.networkData.connection !== 'unknown' && - - } -
- ); -}; - -export default NetWorkNode \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/networkNode/style.ts b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/networkNode/style.ts deleted file mode 100644 index 109aef0..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/networkNode/style.ts +++ /dev/null @@ -1,95 +0,0 @@ -import styled from "styled-components"; - -type Props = { - $width?: string, - $height?: any - $bordertype?: string, - $bordercolor?: string - $backgroundcolor?: string -} - -export const Network = styled.div` - background-color: ${(props) => props.theme.primary}; - border: 2px ${(props) => props.$bordertype} ${(props) => props.$bordercolor}; - min-width: ${(props) => props.$width}px; - height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height}; - border-radius: 8px; - display: flex; - flex-direction: column; - gap: 10px; - visibility: visible; - - p{ - margin: 0; - } -` - -export const TitleContainer = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - background: ${(props) => props.$backgroundcolor}; - border-top-left-radius: 8px; - border-top-right-radius: 8px; - color: white; - padding: 15px; - height: ${(props) => props.$height ? props.$height : 'unset'}; - - img{ - width: 20px - } -` - -export const Device = styled.p` - font-size: 18px; -` - -export const Status = styled.p` - font-size: 14px; - - - img{ - width: 20px; - height: 20px; - } -` - -export const Content = styled.div` - display: flex; - flex-direction: column; - gap: 15px; - padding: 15px; -` - -export const Row = styled.div` - display: flex; - gap: 5px; -` - -export const MAC = styled.div` - display: flex; - justify-content: space-between; - background: ${(props) => props.theme.secondary}; - border-radius: 8px; - // box-shadow: 0px 0px 15px 2px rgba(0, 0, 0, 0.1); - width: 100%; - - .networkNode-row{ - padding: 10px; - } -` - -export const LabelColumn = styled.div` - display: flex; - flex-direction: column; - align-items: center; - gap: 5px; - width: 100%; - - .networkNode-label{ - font-size: 10px; - width: 100%; - text-align: left; - margin-left: 15px; - } -` \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/portNode/portNode.tsx b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/portNode/portNode.tsx deleted file mode 100644 index 68939f7..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/portNode/portNode.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Handle, Position } from '@xyflow/react'; -import { Port, Column } from './style'; - -interface CustomNodeProps { - data: { - label: string; - port: string - id: string; - style: { - width: any; - height: any; - borderType: any; - borderColor: any; - } - }; -} - -export const PortNode = ({ data }: CustomNodeProps) => { - return ( - - - -

{data.label}

- - - {data.port && -

{data.port}

- } -
- - - {data.port ? ( - <> - - - - - ) : ( - <> - - - - - )} - -
- ); -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/portNode/style.ts b/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/portNode/style.ts deleted file mode 100644 index 17a1b0f..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/nodes/networkInterface/portNode/style.ts +++ /dev/null @@ -1,30 +0,0 @@ -import styled from "styled-components" - -type Props={ - $width: string, - $height?: any - $bordertype: string, - $bordercolor: string -} - -export const Port = styled.div` - background-color: ${(props)=>props.theme.primary}; - border: 2px ${(props) => props.$bordertype} ${(props) => props.$bordercolor}; - width: ${(props) => props.$width}px; - height: ${(props) => typeof props.$height === 'number' ? `${props.$height}px` : props.$height}; - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; - visibility: visible; -` - -export const Column = styled.div` - display: flex; - flex-direction: column; - gap: 15px; - - p{ - margin: 0; - } -` \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/types/colors.ts b/FRONT/BUILD/resources/app/src/components/customeNodes/types/colors.ts deleted file mode 100644 index 342c6f7..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/types/colors.ts +++ /dev/null @@ -1,11 +0,0 @@ -type ColorType = Record; - -export const NodeColors: ColorType = { - '0': '#c0c0c0', - '1': '#ef6e7d', - '2': '#f19e4c', - '3': '#e9c44c', - '4': '#51ce82', - '5': '#4cd2d0', - '6': '#a085f3', -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/customeNodes/types/types.ts b/FRONT/BUILD/resources/app/src/components/customeNodes/types/types.ts deleted file mode 100644 index 5b7ce87..0000000 --- a/FRONT/BUILD/resources/app/src/components/customeNodes/types/types.ts +++ /dev/null @@ -1,16 +0,0 @@ - -import { DefaultNode, PillNode, ParalelorgamNode, DiamondNode, CircleNode } from '../nodes/baseForm' -import { NodeGroupNode, NetWorkNode, IpGroupeNode, IpNode, PortNode } from '../nodes/networkInterface'; - -export const nodeTypes = { - custom: DefaultNode, - pill: PillNode, - parallelogram: ParalelorgamNode, - diamond: DiamondNode, - circle: CircleNode, - nodegroup: NodeGroupNode, - portNode: PortNode, - network: NetWorkNode, - ipgroup: IpGroupeNode, - ip: IpNode, -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/modalwindows/modal.tsx b/FRONT/BUILD/resources/app/src/components/modalwindows/modal.tsx deleted file mode 100644 index b030ace..0000000 --- a/FRONT/BUILD/resources/app/src/components/modalwindows/modal.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { ModalWindow, ModalContainer } from "./style" -import { modalNames } from "../../const/modals/names" - -export const WindowComponent = ()=>{ - return( - - - - ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/addDevice.tsx b/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/addDevice.tsx deleted file mode 100644 index c7221bc..0000000 --- a/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/addDevice.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import Input from "../../UI/inputs/input" -import { MainButton } from "../../UI/buttons/mainButton"; -import { useState } from "react"; -import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style" -import { validateIPAddress } from "../../../utils/nodeController/validator"; - -interface Props { - onSave?: (data: string) => void; - onClose: () => void; -} - -export const AddDevice = ({ onSave, onClose }: Props) => { - const [ipAddress, setIpAddress] = useState('') - const [error, setError] = useState(false) - - const handleSubmit = () => { - if (!validateIPAddress(ipAddress)) { - setError(true) - return; - } - // e.preventDefault(); // Предотвращаем перезагрузку страницы - if (onSave && ipAddress) { - setError(false) - onSave(ipAddress); - onClose(); - } - }; - - return ( - - {/* */} - -

Добавление узла

-
- - - setIpAddress(value)} - /> - {error && ( -

- Некорректный адрес узла -

- )} -
- - - - - - {/* */} -
- ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/connectionError.tsx b/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/connectionError.tsx deleted file mode 100644 index 224ba6e..0000000 --- a/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/connectionError.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style" -import { useModalContext } from "../../../store" -import { useIpStore } from "../../../store" -import { MainButton } from "../../UI/buttons/mainButton" - -type Props={ - message: string -} - -export const ConnectionError = ({message}: Props)=>{ - const {closeModal} = useModalContext() - const { setIpAddress } = useIpStore(); - - const close = ()=>{ - setIpAddress('localhost') - closeModal() - } - - return( - - -

Соединение прервано

-
- - - -

Произошёл разрыв с сервером

-

Пожалуйста, проверьте доступ к адресу и попробуйте снова.

-
-
- - - - - -
- ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/index.ts b/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/index.ts deleted file mode 100644 index ed95168..0000000 --- a/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export {IpConfirm} from './ipConfirm' -export {ConnectionError} from './connectionError' -export {AddDevice} from './addDevice' -export {RightsConfirm} from './rightsConfirm' \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/ipConfirm.tsx b/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/ipConfirm.tsx deleted file mode 100644 index 94dafac..0000000 --- a/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/ipConfirm.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import styled from "styled-components"; -import Loader from "../../UI/loader"; -import { useEffect, useState } from "react"; -import { changeIp } from '../../../const/types/types' -import { NetworkService } from '../../../api/request' -import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer, ErrorMessage } from "../style" -import { useIpStore } from "../../../store"; -import { MainButton } from "../../UI/buttons/mainButton"; -import { useAdminStore } from "../../../store/adminStore"; -import { useDevicesStore } from "../../../store"; - -type Props = { - ipData: changeIp - onClose: () => void; - smoothClose: () => void; -} - -const Information = styled.div` - display: flex; - flex-direction: column; - gap: 15px; -` - -const Info = styled.div` - display: flex; - gap: 10px; -` - -const DataContainer = styled.div` - display: flex; - gap: 35px; -` - -const CurrentData = styled.div` - display: flex; - flex-direction: column; - gap: 10px; -` - -const NewData = styled.div` - display: flex; - flex-direction: column; - gap: 10px; -` - -export const IpConfirm = ({ ipData, onClose, smoothClose }: Props) => { - const [loader, setLoader] = useState(true) - const [newIP, setNewIp] = useState(null) - const [currentIp, setCurrentIP] = useState({}) - const [checkStatus, setcheckStatus] = useState(false) - const [message, setMessgae] = useState('') - const { ipAddress, setIpAddress } = useIpStore(); - const {isAdmin} = useAdminStore() - const { devices, addDevice, deleteDevice } = useDevicesStore(); - - useEffect(() => { - if (ipData && isAdmin) { - const data = { - "connection_id": ipData.connection, - "ipv4_updates": { - "ipv4.method": "manual", - "ipv4.address-data": [ - { - "address": ipData.newIp, - "prefix": ipData.prefix - } - ], - "ipv4.gateway": ipData.newGate, - "ipv4.dns-data": ipData.newDns - } - }; - - const oldData = { - "connection_id": ipData.connection, - "ipv4_updates": { - "ipv4.method": "manual", - "ipv4.address-data": [ - { - "address": ipData.currentIp, - "prefix": ipData.prefix - } - ], - "ipv4.gateway": ipData.currentGate, - "ipv4.dns-data": ipData.currentDns - } - } - - setCurrentIP(oldData) - - checkIp(data); - } - }, [ipData, isAdmin]); - - const checkIp = async (data: any) => { - try { - const result = await NetworkService.checkIp(ipAddress, data); - setcheckStatus(result.data.available) - setMessgae(result.data.message) - if (result.data.available) { - setNewIp(data) - } - } catch (err) { - console.error('Error:', err); - } finally { - setLoader(false); - } - }; - - const saveIp = async (data: any) => { - try { - const result = await NetworkService.saveIp(ipAddress, data); - if (result.status === 200) { - setIpAddress(ipData.newIp) - - const updatedDevices = devices.map(device => - device.address === ipData.currentIp - ? { ...device, address: ipData.newIp } - : device - ); - useDevicesStore.setState({ devices: updatedDevices }); - - onClose() - } - } catch (err) { - console.error('Error:', err); - } finally { - setLoader(false); - } - }; - - const cancelIp = async (data: any) => { - try { - const result = await NetworkService.saveIp(ipAddress, data); - if (result.status === 200) { - onClose() - } - } catch (err) { - console.error('Error:', err); - } finally { - setLoader(false); - } - }; - - return ( - <> - {loader && isAdmin ? ( - - ) : ( - - -

Изменение IP-Адреса

-
- {checkStatus ? ( - - -

Общие сведения

- -

Интерфейс: {ipData.device}

-

--{'>'}

-

{ipData.connection}

-
-

Сведения об IP

- - -

Текущий ip: {ipData.currentIp}

-

Текущий шлюз: {ipData.currentGate}

-

Текущий DNS: {ipData.currentDns}

-
- - -

Новый ip: {ipData.newIp}

-

Новый шлюз: {ipData.newGate}

-

Новый DNS: {ipData.newDns}

-
-
-
- - - cancelIp(currentIp)}/> - saveIp(newIP)}/> - -
- ) : ( - <> - - - {isAdmin ? ( - <> -

Произошла ошибка проверки корректности

- {message} - - - ):( -

Для изменения сетевых настроек необходимо авторизироваться под администратором

- )} - -
-
- - - - - - )} -
- )} - - ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/rightsConfirm.tsx b/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/rightsConfirm.tsx deleted file mode 100644 index 603edb6..0000000 --- a/FRONT/BUILD/resources/app/src/components/modalwindows/modalContainers/rightsConfirm.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { useEffect, useState } from "react" -import { ModalContainer, ModalTitle, ModalContent, ModalText, ButtonContainer } from "../style" -import Input from "../../UI/inputs/input" -import { MainButton } from "../../UI/buttons/mainButton" -import { NetworkService } from "../../../api/request" -import { useIpStore } from "../../../store" -import { useAdminStore } from "../../../store/adminStore" -import { useCheckAdmin } from "../../../utils/checkAdmin" - -import { buttonNames } from "../../../const/buttons/names" -import { IconComponent } from "../../../const/buttons/icons" - -interface Props { - onClose: () => void; -} - -export const RightsConfirm = ({ onClose }: Props) => { - - const { ipAddress } = useIpStore(); - const { isAdmin, setAdmin } = useAdminStore() - const checkAdmin = useCheckAdmin(); - const [error, setError] = useState(false) - const [success, setSuccess] = useState({ - message: '', - status: false - }) - - const [user, setUser] = useState({ - username: 'operator', - password: "" - }) - - const handleChange = (id: string, value: string) => { - setUser(prevData => ({ - ...prevData, - [id]: value - })); - } - - const login = async (user: object) => { - try { - const response = await NetworkService.login(ipAddress, user); - if(response.status === 200){ - await checkAdmin(); - setError(false) - setSuccess(prevData => ({ - ...prevData, - message: 'Успешный вход', - status: true - })); - await new Promise(resolve => setTimeout(resolve, 3000)); - onClose(); - }else{ - setError(true) - } - } catch (err) { - console.error('Error:', err); - } - } - - const logout = async () => { - await NetworkService.logout(ipAddress); - await checkAdmin(); - setSuccess(prevData => ({ - ...prevData, - message: 'Успешный выход', - status: true - })); - await new Promise(resolve => setTimeout(resolve, 3000)); - onClose(); - } - - return ( - - {!success.status ? ( - !isAdmin ? ( - <> - -

Получение прав доступа

-
- - - -

Включить административный доступ

- handleChange(id, value)} - /> - - {error && ( -

- Неверный пароль оператора -

- )} - -
- - - - login(user)} /> - - - ) : ( - <> - -

Переключиться на ограниченный доступ

-
- - -

В данном режиме доступа ограничиваются полномочия администратора.

-
- - - - - - - - ) - ):( - - -

{success.message}

-

Это окно можно закрыть, либо оно пропадёт само

- -
- )} - - -
- ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/modalwindows/modalWindow.tsx b/FRONT/BUILD/resources/app/src/components/modalwindows/modalWindow.tsx deleted file mode 100644 index ca04701..0000000 --- a/FRONT/BUILD/resources/app/src/components/modalwindows/modalWindow.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useModalContext } from "../../store"; -import { IpConfirm, ConnectionError, AddDevice, RightsConfirm } from "./modalContainers"; -import { ModalMap } from "./windowsType"; -import Loader from "../UI/loader"; -import { ModalWindow } from "./style"; - -type Props = { - isOpen: boolean, - type: string - data: any; -} - -export default function WindowComponent({ isOpen, type, data }: Props) { - - const { closeModal, closeLoader } = useModalContext() - - return ( - <> - {isOpen && - - {type == ModalMap['1'] && data && - - } - - {type == ModalMap['2'] && - - } - - {type === ModalMap['3'] && - - } - - {type === ModalMap['4'] && ( - - )} - - {type === ModalMap['5'] && ( - - )} - - } - - ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/modalwindows/style.ts b/FRONT/BUILD/resources/app/src/components/modalwindows/style.ts deleted file mode 100644 index c8d1c6d..0000000 --- a/FRONT/BUILD/resources/app/src/components/modalwindows/style.ts +++ /dev/null @@ -1,70 +0,0 @@ -import styled from "styled-components"; - -export const ModalWindow = styled.div` - width: 100%; - position: fixed; - top: 0; - left: 0; - z-index: 9999; - background-color: rgba(0, 0, 0, 0.8); - display: flex; - align-items: center; - justify-content: center; - height: 100%; -` - -export const ModalContainer = styled.div<{height?: string}>` - border-radius: 12px; - background: ${(props)=>props.theme.secondary}; - color: ${(props=>props.theme.textColor)}; - position: relative; - max-height: 100%; - overflow: auto; - display: flex; - flex-direction: column; - justify-content: space-between; - gap: 15px; - height: ${(props) => props.height ? props.height : '200px'}; - width: 520px; -` - -export const ModalTitle = styled.div` - padding: 20px; - border-bottom: 1px solid #9D9D9D; - display: flex; - justify-content: space-between; - - h3{ - font-size: 18px; - } -` - -export const ModalContent = styled.div` - padding: 0 20px; - - h3{ - font-size: 14px; - } - - p{ - font-size: 12px; - } -` - -export const ModalText = styled.div` - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 10px; -` - -export const ErrorMessage = styled.p` - color: #d95454; -` - -export const ButtonContainer = styled.div` - justify-content: flex-end; - display: flex; - gap: 10px; - padding: 20px; -` \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/modalwindows/windowsType.ts b/FRONT/BUILD/resources/app/src/components/modalwindows/windowsType.ts deleted file mode 100644 index 4582266..0000000 --- a/FRONT/BUILD/resources/app/src/components/modalwindows/windowsType.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type ModalType = Record; - -export const ModalMap: ModalType = { - '1': 'ipConfirm', - '2': 'loader', - '3': 'connectionError', - '4': 'addDevice', - '5': 'sudo' -} diff --git a/FRONT/BUILD/resources/app/src/components/register.tsx b/FRONT/BUILD/resources/app/src/components/register.tsx deleted file mode 100644 index eec88af..0000000 --- a/FRONT/BUILD/resources/app/src/components/register.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { ModalContainer, ModalTitle, ModalContent, ModalText } from "./modalwindows/style" -import Input from "./UI/inputs/input" -import { MainButton } from "./UI/buttons/mainButton" -import styled from "styled-components" -import { useEffect, useState } from "react" -import { useIpStore } from "../store" -import { NetworkService } from "../api/request" -import { IconComponent } from "../const/buttons/icons" -import { buttonNames } from "../const/buttons/names" - -const Wrapper = styled.div` - display: flex; - align-items: center; - justify-content: center; - height: 100%; - background: ${(props)=> props.theme.background}; -` - -const Container = styled.form` - display: flex; - flex-direction: column; - gap: 25px; - align-items: center; - background: ${(props) => props.theme.primary}; - padding: 25px; - border-radius: 18px; - width: 350px; -` - -const Title = styled.div` - display: flex; - flex-direction: column; - gap: 15px; - align-items: center; - text-align: center; - font-size: 25px; -` - -const Content = styled.div` - display: flex; - flex-direction: column; - gap: 25px; - text-align: center; -` - -const ButtonContainer = styled.div` - width: 100%; - display: flex; - justify-content: center; -` - -export const CreateUser = () => { - - const { ipAddress } = useIpStore(); - const [repeatPassword, setRepeatPassword] = useState(''); - const [passwordsMatch, setPasswordsMatch] = useState(true); - - const [user, setUser] = useState({ - username: 'operator', - password: '', - is_admin: true - }) - - const handleChange = (id: string, value: string) => { - if (id === 'repeatPassword') { - setRepeatPassword(value); - setPasswordsMatch(user.password === value); - } else { - setUser(prevData => ({ - ...prevData, - [id]: value - })); - if (id === 'password') { - setPasswordsMatch(value === repeatPassword); - } - } - } - - const handleSave = async (user: object) => { - try { - const result = await NetworkService.createUser(ipAddress, user); - if (result.status === 200) { window.location.reload() } - } catch (err) { - console.error('Error:', err); - } finally { - } - } - - const handleSubmit = () => { - if (passwordsMatch) { - handleSave(user); - } - }; - - return ( - - - - - <IconComponent name={buttonNames[3]} width="52" height="52"></IconComponent> - <h3>Первоначальная настройка приложения</h3> - - - - -
-

Перед началом работы необходимо создать пароль оператора приложения

-
- -
- handleChange(id, value)} - /> - - handleChange(id, value)} - style={{ marginTop: '10px' }} - /> - {!passwordsMatch && ( -

- Пароли не совпадают! -

- )} -
- -
- - - - -
-
- ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/about.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/about.tsx deleted file mode 100644 index 344c733..0000000 --- a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/about.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import styled from "styled-components" -import { useNavigate } from "react-router-dom" - -const Container = styled.div` - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 25px; - text-align: center; - - h1, h2, h3, p{ - margin: 0; - } -` - -const Content = styled.div` - max-width: 450px; - display: flex; - flex-direction: column; - align-items: center; - gap: 100px; - button{ - width: 250px; - } -` - -const Row = styled.div` - display: flex; - gap: 10px; - align-items: flex-end; - justify-content: center; - margin-left: 50px; -` - -const Column = styled.div` - display: flex; - flex-direction: column; - gap: 10px; -` - -export default function About() { - const navigate = useNavigate() - const redirect = (path: string) => { - navigate(path) - } - - - return ( - - - -

GoNetMan

-

v 0.0.1

-
- - -

О программе

- GoNetMan - Это приложение представляющее собой панель управления сетевыми настройками в удобном и понятном графическом виде. -
- -

Copyright © ООО "Гибридные офисные системы"

- - -
-
- ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/header.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/header.tsx deleted file mode 100644 index 356764b..0000000 --- a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/header.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import styled from "styled-components" -import { IconComponent } from "../../../../const/buttons/icons" -import { buttonNames } from "../../../../const/buttons/names" -import { useWorkSpaceStore } from "../../../../store" -import { useModalContext } from "../../../../store" -import { useAdminStore } from "../../../../store/adminStore" -import { ModalMap } from "../../../modalwindows/windowsType" -import { QuitButton } from "../../../UI/buttons/quitButton" -import { usePosStore } from "../../../../store/posStore" - -const Container = styled.div` - display: flex; - align-items: center; - justify-content: flex-end; - background-color: ${(props) => props.theme.primary}; - padding: 0 5px; -` - -const ButtonContainer = styled.div` - display: flex; - align-items: center; -` - -const IconContainer = styled.div` - padding: 10px; - - svg{ - cursor: pointer; - } -` - -export const Header = () => { - const {setNullPos} = usePosStore() - - return ( - - - - setNullPos(true)}/> - - - - - - ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/helloWindow.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/helloWindow.tsx deleted file mode 100644 index b4b80d9..0000000 --- a/FRONT/BUILD/resources/app/src/components/workSpace/modules/appModules/helloWindow.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import styled from "styled-components" -import { MainButton } from "../../../UI/buttons/mainButton" -import { useWorkSpaceStore } from "../../../../store" -import { workSpaceNames } from "../../../../const/workspace/names" -import { buttonNames } from "../../../../const/buttons/names" - -const Wrapper = styled.div` - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - background-color: ${(props => props.theme.background)}; - background-image: url('./assets/images/back1.svg'); - background-size: 100%; - background-repeat: no-repeat; - background-position: bottom; -` - -const Container = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 52px; - margin-bottom: 100px; -` - -const ButtonWrapper = styled.div` - display: flex; - gap: 75px; - flex-direction: column; -` - -const ButtonContainer = styled.div` - display: flex; - gap: 15px; - flex-direction: column; -` - -const Title = styled.div` - display: flex; - align-items: flex-end; - gap: 10px; - - h1{ - font-size: 52px; - } -` - -export const HelloWindow = () => { - - const { setWorkModule } = useWorkSpaceStore() - - return ( - - - - <img src="./assets/images/logo.svg" alt="" style={{width: '152px'}}/> - - - - - - setWorkModule(workSpaceNames[3])} - /> - - - - - - ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/modules/workModules/networkInterfaces.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/modules/workModules/networkInterfaces.tsx deleted file mode 100644 index 61a6cdb..0000000 --- a/FRONT/BUILD/resources/app/src/components/workSpace/modules/workModules/networkInterfaces.tsx +++ /dev/null @@ -1,282 +0,0 @@ -import { useEffect, useState, useCallback, useRef } from 'react'; -import { useTheme } from 'styled-components'; -import { usePosStore } from '../../../../store/posStore'; - -import { ReactFlow, useNodesState, useEdgesState, Background, addEdge, applyEdgeChanges, Controls } from '@xyflow/react'; -import '@xyflow/react/dist/style.css'; -import '../../../../assets/css/flow.css' - -import { nodeTypes } from '../../../customeNodes/types/types'; -import { ModalMap } from '../../../modalwindows/windowsType'; - -import { - useGroupStore, - useModalContext, - usePannableStore -} from '../../../../store' - -import { - autosavePosition, - resetPosition, - processNodeChanges, - initNetworkIntData, - groupSort, - collapseGroup, - updateWifiData, - useInitCanvas -} from '../../../../utils/nodeController'; - -import { - ResponseData, - ResponseNetwork, - IEdge, - Interfaces, -} from '../../../../const/types/types'; - -type Props = { - startConnection: boolean - ws: string -} - -const proOptions = { hideAttribution: true, }; - -export const NetworkInterfaces = ({ startConnection, ws }: Props) => { - - const initializationAttempt = useRef(0); - const [requestError, setRequestError] = useState({ status: false, message: '' }) - - const { openModal, closeLoader } = useModalContext(); - const [loader, setLoader] = useState(true) - const theme = useTheme(); - - const [socket, setSocket] = useState(null); - const [data, setData] = useState([]); - const [wifiData, setWifi] = useState() - const [hasInited, setHasInited] = useState(false); - const { initedNodes, initedEdges, handleInit } = useInitCanvas(); - - const [interfaces, setInterfaces] = useState([]); - const [edgeData, setEdgeData] = useState([]); - - const [nodes, setNodes, onNodesChange] = useNodesState([] as any[]); - const [edges, setEdges] = useEdgesState([] as any[]); - const { isPanDisabled } = usePannableStore() - - const [changeArray, setChangeArray] = useState([]); - const { groups } = useGroupStore(); - const {isNullPos, setNullPos} = usePosStore() - - useEffect(() => { - if (startConnection) { - - openModal(ModalMap['2'], 'Идет загрузка данных'); - - setNodes([]) - setEdges([]) - setHasInited(false) - initializationAttempt.current = 0; - - const newSocket = new WebSocket(`wss://${ws}:8001/api/v1`); - - let connectionTimeout; - setSocket(newSocket); - - connectionTimeout = setTimeout(() => { - if (newSocket.readyState !== WebSocket.OPEN) { - newSocket.close(); - } - }, 5000); - - newSocket.onopen = () => { - console.log('WebSocket connection established'); - }; - - newSocket.onmessage = (event) => { - const newData = JSON.parse(event.data); - - setData(() => [ - newData.activation, - newData.deactivation, - ]); - }; - - newSocket.onerror = (error) => { - console.error('WebSocket error:', error); - }; - - newSocket.onclose = () => { - console.log('WebSocket connection closed'); - setRequestError({ - status: true, - message: 'Соединение с сервером разорвано' - }) - }; - - return () => { - newSocket.close(); - closeLoader() - }; - } - }, [startConnection, ws]); - - - useEffect(() => { - if (data.length > 0) { - const { interfaces: newInterfaces, edgeData: newEdgeData } = initNetworkIntData(data); - const activationData = data[0].interfaces; - - const wifi = activationData.find((el: any) => el.type === 'WIFI'); - - setInterfaces(newInterfaces); - setEdgeData(newEdgeData); - if (wifi) { setWifi(wifi); } - } - }, [data]); - - useEffect(() => { - if (interfaces.length > 0 && !hasInited) { - handleInit(edgeData, interfaces) - setHasInited(true) - } - }, [interfaces, edgeData, hasInited]) - - useEffect(() => { - if (initializationAttempt.current === 0 && !requestError.status) { - const checkInterval = setInterval(() => { - - if(initedNodes.length> 0 && initedNodes.length<=4){ - window.location.reload() - } - - if (initedNodes.length > 0 && initedEdges.length > 0) { - setNodes(initedNodes); - setEdges(initedEdges); - console.log("Ноды и эджи успешно инициализированы"); - initializationAttempt.current = 1; - clearInterval(checkInterval); - clearTimeout(timeoutId); - } else { - console.log("Попытка инициализации нод и эджей..."); - } - }, 1000); - - const timeoutId = setTimeout(() => { - console.error("Ошибка: слишком долгая загрузка данных"); - clearInterval(checkInterval); - setRequestError({ - status: true, - message: 'Превышено время ожидания' - }) - }, 60000); - - return () => { - clearInterval(checkInterval); - clearTimeout(timeoutId); - }; - } - }, [initedNodes, initedEdges, requestError]); - - useEffect(() => { - if (nodes.length === 0) { - initializationAttempt.current = 0; - } else { - initializationAttempt.current = 1; - } - }, [nodes]); - - useEffect(() => { - if ((nodes.length > 0 && loader == true)) { - setLoader(false) - closeLoader() - } - }, [nodes, loader]) - - const onEdgesChange = useCallback( - (changes: any) => setEdges((eds) => applyEdgeChanges(changes, eds)), - [setEdges], - ); - - const onConnect = useCallback( - (connection: any) => setEdges((eds) => addEdge(connection, eds)), - [setEdges], - ); - - const handleNodesChange = (changes: any) => { - onNodesChange(changes); - - const newData = processNodeChanges(changes, data); - - if (newData) { - setChangeArray((prevArray) => [...prevArray, newData]); - } - }; - - useEffect(() => { - const interval = setInterval(() => { - if (socket && socket.readyState === WebSocket.OPEN) { - autosavePosition(changeArray, socket); - } - setChangeArray([]); - }, 1000); - - return () => { - clearInterval(interval); - }; - }, [changeArray, socket]); - - useEffect(() => { - const sortedGroups = groupSort(groups, nodes) - setNodes(collapseGroup(sortedGroups, nodes)); - }, [groups]); - - useEffect(() => { - if (wifiData) { - setNodes(updateWifiData(wifiData, nodes)); - } - }, [wifiData]) - - useEffect(() => { - if (requestError.status) { - setTimeout(() => { - openModal(ModalMap['3'], { - message: requestError.message, - }); - }, 1000); - } - }, [requestError]); - - useEffect(() => { - if(isNullPos){ - if(socket && socket.readyState === WebSocket.OPEN){ - resetPosition(socket, setNullPos, nodes, setNodes) - } - } - }, [isNullPos]); - - return ( - <> - - - - - - - ); -}; diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/sideBar.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/sideBar.tsx deleted file mode 100644 index 256f62d..0000000 --- a/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/sideBar.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { ToolBar } from "./toolBar/toolBar" -import { ToolSpace } from "./toolSpace/toolSpace" -import styled from "styled-components" - -const Container = styled.div` - height: 100%; - display: flex; -` - -export const SideBar = ()=>{ - return( - - - - - ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolBar/toolBar-button.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolBar/toolBar-button.tsx deleted file mode 100644 index 0b3b2cc..0000000 --- a/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolBar/toolBar-button.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import styled from "styled-components" -import { IconComponent as Icon } from "../../../../const/menus/sideBar/icons" -import { useToolBar } from "../../../../hooks/useToolBar" -import { useTheme } from "styled-components" - -type Props = { - menuId: string -} - -const Button = styled.div` - width: 100%; - cursor: pointer; -` - -const IconContainer = styled.div` - padding: 10px; -` - -export const ToolBarButton = ({ menuId }: Props) => { - const { isActive, collapseMenu } = useToolBar(menuId); - const theme = useTheme(); - - const iconColor = isActive ? theme.textColor : theme.active; - - return ( - - ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolBar/toolBar.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolBar/toolBar.tsx deleted file mode 100644 index 3cd080a..0000000 --- a/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolBar/toolBar.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useToolBarStore } from "../../../../store" -import { ToolBarButton } from "./toolBar-button" -import styled from "styled-components" -import ThemeButton from "../../../UI/buttons/toggletheme" -import { IconComponent } from "../../../../const/buttons/icons" -import { buttonNames } from "../../../../const/buttons/names" -import { workSpaceNames } from "../../../../const/workspace/names" - -import { useModalContext } from "../../../../store" -import { useAdminStore } from "../../../../store/adminStore" -import { ModalMap } from "../../../modalwindows/windowsType" -import { useWorkSpaceStore } from "../../../../store" - -const Container = styled.div` - display: flex; - flex-direction: column; - align-items: center; - justify-content: space-between; - background-color: ${(props) => props.theme.primary}; - padding: 5px 0; -` - -const ButtonContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; -` - -const Button = styled.div` - width: 100%; - cursor: pointer; -` - -const IconContainer = styled.div` - padding: 10px; - color: ${props => props.theme.text}; -` - -export const ToolBar = () => { - const { toolBar } = useToolBarStore() - const toolBarMenu = toolBar?.toolSpace - - const { openModal } = useModalContext() - const {isAdmin} = useAdminStore() - const {setWorkModule} = useWorkSpaceStore() - - return ( - - - {toolBarMenu && toolBarMenu.map((menu) => ( - - ))} - - - - - - - - - - - - ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolSpace/devicesBar.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolSpace/devicesBar.tsx deleted file mode 100644 index ad5b7b2..0000000 --- a/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolSpace/devicesBar.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import styled from "styled-components" -import { useState, useEffect } from "react" -import { useModalContext } from "../../../../store" -import { ModalMap } from "../../../modalwindows/windowsType" -import { NetworkService } from "../../../../api/request" -import { AnimDot } from "../../../UI/animDot" -import { useMenu } from "../../../../hooks/useMenu" -import { useIpStore, useDevicesStore } from "../../../../store" -import { menusNames } from "../../../../const/menus/othersMenus/names" -import { useWorkSpaceStore } from "../../../../store" -import { workSpaceNames } from "../../../../const/workspace/names" -import { IconComponent } from "../../../../const/buttons/icons" -import { buttonNames } from "../../../../const/buttons/names" - -const Container = styled.div` - display: flex; - flex-direction: column; - align-items: center; - gap: 15px; - padding: 20px; - - p{ - margin: 0; - } -` - -const Title = styled.div` - padding: 5px 0; - width: 100%; - text-align: center; - border-bottom: 1px solid ${props => props.theme.text}; -` - -const DeviceBlock = styled.div` - border-radius: 8px; - border: 2px solid ${props => props.theme.accent}; - display: flex; - flex-direction: column; - width: 100%; - - .paddingP{ - padding: 7px 5px; - } -` - -const DeviceControl = styled.div` - padding: 5px 8px; - background: ${props => props.theme.accent}; - display: flex; - justify-content: space-between; - align-items: center; - color: white; - cursor: pointer; -` - -const PlusBlock = styled.div` - font-size: 24px; - cursor: pointer; - border-radius: 8px; - border: 2px dotted ${props => props.theme.accent}; - width: 100%; - text-align: center; - padding: 5px 0; -` - -const Text = styled.div` - display: flex; - gap: 3px; - align-items: center; -` - -const DevicesContainer = styled.div` - width: 100%; - display: flex; - flex-direction: column; - gap: 15px; -` - -const DevicesHeader = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - cursor: pointer; - - .hControl{ - font-size: 20px; - cursor: pointer; - } -` - -const DevicesContent = styled.div<{ $isactive: string }>` - ${props => props.$isactive === 'true' && `display: flex;`} - ${props => props.$isactive === 'false' && `display: none;`} - flex-direction: column; - align-items: center; - gap: 15px; -` - -interface CurrentDevice { - hostname: string - address: string -} - -export const DevicesBar = () => { - const { openModal } = useModalContext(); - const { setIpAddress, ipAddress } = useIpStore(); - const { devices, addDevice, deleteDevice } = useDevicesStore() - const { isOpen, collapseMenu } = useMenu(menusNames[1]); - const { setWorkModule } = useWorkSpaceStore() - - const [currentDevice, setCurrentDevice] = useState({ - address: 'localhost', - hostname: '' - }) - - const checkWebSocketAvailability = async (ip: string): Promise => { - return new Promise((resolve) => { - const testSocket = new WebSocket(`wss://${ip}:8001/api/v1`); - const timeout = setTimeout(() => { - testSocket.close(); - resolve(false); - }, 500 - ); - - testSocket.onopen = () => { - clearTimeout(timeout); - testSocket.close(); - resolve(true); - }; - - testSocket.onerror = () => { - clearTimeout(timeout); - testSocket.close(); - resolve(false); - }; - }); - }; - - useEffect(() => { - const fetchHostame = async () => { - let response = await NetworkService.getHostname(); - if (response.status === 200) { - setCurrentDevice(prevData => ({ - ...prevData, - hostname: response.data.hostname - })) - } - }; - fetchHostame(); - }, []) - - const handleAdd = () => { - openModal(ModalMap['4'], { - onSave: handleAddDeviceComplete, - }); - }; - - const handleAddDeviceComplete = (data: string) => { - addDevice(data) - setIpAddress(data) - }; - - const handleDelete = (number: number, adress: string) => { - deleteDevice(number) - - - if(adress == ipAddress){ - setIpAddress('localhost') - } - }; - - const switchWs = async (ip: string) => { - // Проверяем доступность WebSocket - const isAvailable = await checkWebSocketAvailability(ip); - if (!isAvailable) { - openModal(ModalMap['3'], { - message: `Не удалось подключиться к серверу по адресу ${ip}`, - }); - return; - } - - // Если WebSocket доступен, обновляем IP и переключаем модуль - setIpAddress(ip); - setWorkModule(workSpaceNames[3]); - }; - - useEffect(() => { - if (devices.length === 0) { - setIpAddress('localhost'); - } - }, [devices]); - - return ( - - - <p className="unselectable">Доступные узлы</p> - - - - switchWs(currentDevice.address)}> -

Текущее устройство

-
- {currentDevice.hostname == '' ? ( - Адрес: Загрузка - ) : ( -

Адрес: {currentDevice.hostname}

- )} -
- - - -

Дополнительные узлы

- -
- - - {devices.length > 0 && - devices.map((device, index) => ( - - switchWs(device.address)}> -

Узел {device.number}

- { - e.stopPropagation(); // Предотвращаем всплытие события - handleDelete(device.number, device.address); - }} - /> -
-

Адрес: {device.address}

-
- )) - } - - - + - -
- -
-
- ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolSpace/toolSpace.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolSpace/toolSpace.tsx deleted file mode 100644 index 75e6328..0000000 --- a/FRONT/BUILD/resources/app/src/components/workSpace/sideBar/toolSpace/toolSpace.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useToolBarStore } from "../../../../store" -import { useToolBar } from "../../../../hooks/useToolBar" -import { Space } from "../../../../const/menus/sideBar/components" -import styled from "styled-components" - -const Container = styled.div` - background-color: ${(props) => props.theme.secondary}; - width: 250px; - font-size: 14px; - flex: 3; -` - -export const ToolSpace = () => { - const { toolBar } = useToolBarStore() - const name = toolBar?.current || '' - const { isActive, isOpen } = useToolBar(name) - - if (!isActive || !isOpen) { - return null - } - - return ( - - - - ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/components/workSpace/workSpace.tsx b/FRONT/BUILD/resources/app/src/components/workSpace/workSpace.tsx deleted file mode 100644 index d1a81bd..0000000 --- a/FRONT/BUILD/resources/app/src/components/workSpace/workSpace.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useEffect, useState } from "react" -import { HelloWindow } from "./modules/appModules/helloWindow"; -import { NetworkInterfaces } from "./modules/workModules/networkInterfaces"; -import styled from "styled-components" -import { useIpStore, useToolBarStore } from "../../store"; -import { SideBar } from "./sideBar/sideBar"; -import { useWorkSpaceStore } from "../../store"; -import { workSpaceNames } from "../../const/workspace/names"; -import { Header } from "./modules/appModules/header"; -import { ReactFlowProvider } from "@xyflow/react"; - -const Section = styled.div` - display: flex; - flex-direction: column; - height: 100%; -` - -const FlowContainer = styled.div` - display: flex; - height: 100%; -` - -const WorkSection = styled.div` - display: flex; - flex-direction: column; - width: 100%; - position: relative; -` - -export default function WorkSpace() { - const { ipAddress, setIpAddress } = useIpStore(); - const { workModule } = useWorkSpaceStore() - const [currentWs, setCurrentWs] = useState(ipAddress); - const initMenus = useToolBarStore((state) => state.initialize); - const initModule = useWorkSpaceStore((state) => state.initialize) - - useEffect(() => { - if (!localStorage.getItem('toolBar-storage')) { - initMenus(); - } - }, [initMenus]); - - useEffect(() => { - if (!localStorage.getItem('workSpace-storage')) { - initModule(); - } - }, [initModule]); - - useEffect(() => { - setCurrentWs(ipAddress); - }, [ipAddress]); - - useEffect(() => { - setIpAddress(currentWs); - }, [currentWs]); - - console.log('aboba'); - - - return ( -
- - - - {workModule === workSpaceNames[1] && } - - {workModule === workSpaceNames[3] && - -
- - - - - } - -
- ) -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/buttons/icons.tsx b/FRONT/BUILD/resources/app/src/const/buttons/icons.tsx deleted file mode 100644 index b6ef983..0000000 --- a/FRONT/BUILD/resources/app/src/const/buttons/icons.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from "react"; -import { buttonNames } from "./names"; -import { useTheme } from "styled-components"; -import { Icon } from '@iconify/react'; - -import exitToApp from '@iconify/icons-ic/twotone-exit-to-app'; -import deleteIcon from '@iconify/icons-ic/baseline-delete'; -import settings from '@iconify/icons-ic/baseline-settings'; -import info from '@iconify/icons-ic/baseline-info'; -import arrowDown from '@iconify/icons-ic/baseline-keyboard-arrow-down'; -import arrowRight from '@iconify/icons-ic/baseline-keyboard-arrow-right'; -import edit from '@iconify/icons-ic/baseline-edit'; -import checkCircle from '@iconify/icons-ic/round-check-circle'; -import cancel from '@iconify/icons-ic/baseline-cancel'; -import admin from '@iconify/icons-ic/baseline-admin-panel-settings'; -import noAdmin from '@iconify/icons-ic/baseline-remove-moderator' -import refresh from '@iconify/icons-ic/baseline-refresh'; -import close from '@iconify/icons-ic/round-close'; - -export const ICONS = { - [buttonNames[1]]: exitToApp, - [buttonNames[2]]: deleteIcon, - [buttonNames[3]]: settings, - [buttonNames[4]]: info, - [buttonNames[5]]: arrowDown, - [buttonNames[6]]: arrowRight, - [buttonNames[7]]: edit, - [buttonNames[8]]: checkCircle, - [buttonNames[9]]: cancel, - [buttonNames[10]]: admin, - [buttonNames[11]]: refresh, - [buttonNames[12]]: close, - [buttonNames[13]]: noAdmin - -} as const; - -type IconName = keyof typeof ICONS; - -interface IconsProps { - name: IconName, - width?: string - height?: string - setColor?: string - onClick?: (event?: React.MouseEvent) => void; -} - -export const IconComponent = ({ name, width = "24", height = "24", setColor, onClick}: IconsProps) => { - const theme = useTheme(); - return ( - - ); -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/buttons/names.ts b/FRONT/BUILD/resources/app/src/const/buttons/names.ts deleted file mode 100644 index a90d106..0000000 --- a/FRONT/BUILD/resources/app/src/const/buttons/names.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { names } from "../types/types" - -export const buttonNames: names ={ - 1: 'powerOff', - 2: 'delete', - 3: 'settings', - 4: 'information', - 5: 'arrow-down', - 6: 'arrow-right', - 7: 'edit', - 8: 'accept', - 9: 'cancel', - 10: 'admin', - 11: 'reset', - 12: 'close', - 13: 'noAdmin' -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/menus/othersMenus/data.ts b/FRONT/BUILD/resources/app/src/const/menus/othersMenus/data.ts deleted file mode 100644 index c5f2713..0000000 --- a/FRONT/BUILD/resources/app/src/const/menus/othersMenus/data.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Menu } from "../../types/types"; -import { menusNames } from "./names"; - -export const menusData: Menu[] = [ - { - id: menusNames[1], - isOpen: false - } -] \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/menus/othersMenus/names.ts b/FRONT/BUILD/resources/app/src/const/menus/othersMenus/names.ts deleted file mode 100644 index 1f06e93..0000000 --- a/FRONT/BUILD/resources/app/src/const/menus/othersMenus/names.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { names } from "../../types/types" - -export const menusNames: names ={ - 1: 'collapse-devices', -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/menus/sideBar/components.tsx b/FRONT/BUILD/resources/app/src/const/menus/sideBar/components.tsx deleted file mode 100644 index e2bd113..0000000 --- a/FRONT/BUILD/resources/app/src/const/menus/sideBar/components.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from "react" -import { toolBarNames } from "./names" -import { DevicesBar } from "../../../components/workSpace/sideBar/toolSpace/devicesBar" - -export const COMPONENTS = { - [toolBarNames[1]]:( - - ), -} as const - -type ComponentName = keyof typeof COMPONENTS - -interface SpaceProps { - name: ComponentName, -} - -export const Space = ({name}: SpaceProps) => { - const Component = COMPONENTS[name] - return React.cloneElement(Component); -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/menus/sideBar/data.ts b/FRONT/BUILD/resources/app/src/const/menus/sideBar/data.ts deleted file mode 100644 index c2bd739..0000000 --- a/FRONT/BUILD/resources/app/src/const/menus/sideBar/data.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ToolBar } from "../../types/types"; -import { toolBarNames } from "./names"; - -export const toolBarData: ToolBar = { - current: toolBarNames[1], - toolSpace: [ - { - id: toolBarNames[1], - isOpen: true, - isActive: true - }, - ] -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/menus/sideBar/icons.tsx b/FRONT/BUILD/resources/app/src/const/menus/sideBar/icons.tsx deleted file mode 100644 index 1d80d6b..0000000 --- a/FRONT/BUILD/resources/app/src/const/menus/sideBar/icons.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from "react" -import { toolBarNames } from "./names" -import { useTheme } from "styled-components" -import { Icon } from '@iconify/react'; - -import desktop from '@iconify/icons-ic/round-desktop-windows'; - -export const ICONS = { - [toolBarNames[1]]: desktop, -} as const - - -type IconName = keyof typeof ICONS - -interface IconsProps { - name: IconName, - width?: string - height?: string - setColor?: string -} - -export const IconComponent = ({ name, width = "24", height = "24", setColor}: IconsProps) => { - const theme = useTheme(); - return ( - - ); -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/menus/sideBar/names.ts b/FRONT/BUILD/resources/app/src/const/menus/sideBar/names.ts deleted file mode 100644 index 32d3d9d..0000000 --- a/FRONT/BUILD/resources/app/src/const/menus/sideBar/names.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { names } from "../../types/types" - -export const toolBarNames: names ={ - 1: 'toolSpace-devices', -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/modals/names.ts b/FRONT/BUILD/resources/app/src/const/modals/names.ts deleted file mode 100644 index face023..0000000 --- a/FRONT/BUILD/resources/app/src/const/modals/names.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { names } from "../types/types" - -export const modalNames: names ={ - 1: 'loader', - 2: 'ipConfirm', - 3: 'connectionError', - 4: 'addDevice' -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/theme/globalStyles.ts b/FRONT/BUILD/resources/app/src/const/theme/globalStyles.ts deleted file mode 100644 index ca6059d..0000000 --- a/FRONT/BUILD/resources/app/src/const/theme/globalStyles.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { createGlobalStyle } from 'styled-components'; - -export const GlobalStyle = createGlobalStyle` - body { - background-color: ${(props) => props.theme.backgroundColor}; - color: ${(props) => props.theme.textColor}; - box-sizing: border-box; - font-family: sans-serif; - box-sizing: border-box; - min-height: 100%; - height: 100%; - margin: 0; - - h1, h2, h3, h4, P{ - margin: 0; - display: unset; - } - } - - #root{ - width: 100%; - height: 100%; - margin: 0; - padding: 0; - } - - .react-flow__controls{ - border-radius: 12px; - background-color: ${(props) => props.theme.secondary}; - button{ - background-color: ${(props) => props.theme.secondary}; - border: none; - border-radius: 12px; - - &:hover{ - background-color: ${(props) => props.theme.primary}; - } - } - - .react-flow__controls-button{ - border-radius: 12px; - } - } - - .react-flow__handle{ - background-color: ${(props) => props.theme.nodeColor}; - } - - .unselectable { - user-select: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - } -`; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/theme/icons.tsx b/FRONT/BUILD/resources/app/src/const/theme/icons.tsx deleted file mode 100644 index 04b689e..0000000 --- a/FRONT/BUILD/resources/app/src/const/theme/icons.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { Icon } from '@iconify/react'; -import { useTheme } from 'styled-components'; -import { themeNames } from './names'; - -import light from '@iconify/icons-ic/baseline-brightness-7'; -import dark from '@iconify/icons-ic/baseline-brightness-5'; -import system from '@iconify/icons-ic/baseline-brightness-6'; - -export const ICONS = { - [themeNames[1]]: light, - [themeNames[2]]: dark, - [themeNames[3]]: system, - -} as const; - -type IconName = keyof typeof ICONS; - -interface IconsProps { - name: IconName, - width?: string - height?: string - setColor?: string -} - -export const IconComponent = ({ name, width = "24", height = "24", setColor}: IconsProps) => { - const theme = useTheme(); - return ( - - ); -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/theme/names.ts b/FRONT/BUILD/resources/app/src/const/theme/names.ts deleted file mode 100644 index ec3fc5f..0000000 --- a/FRONT/BUILD/resources/app/src/const/theme/names.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { names } from "../types/types" - -export const themeNames: names ={ - 1: 'light', - 2: 'dark', - 3: 'system' -} - -const themeType = themeNames[1] || themeNames[2] || themeNames[3] -export type ThemName = typeof themeType \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/theme/theme.ts b/FRONT/BUILD/resources/app/src/const/theme/theme.ts deleted file mode 100644 index ebef5c4..0000000 --- a/FRONT/BUILD/resources/app/src/const/theme/theme.ts +++ /dev/null @@ -1,20 +0,0 @@ -const baseTheme = { - accent: '#007ACC', - accentHover: '#4A90E2', -} - -export const light = { - ...baseTheme, - primary: '#F3F3F3', - secondary: '#EDEDED', - background: '#E8E8E8', - textColor: '#000000', -}; - -export const dark = { - ...baseTheme, - primary: '#1E1E1E', - secondary: '#252525', - background: '#3A3A3A', - textColor: '#FFFFFF', -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/types/types.ts b/FRONT/BUILD/resources/app/src/const/types/types.ts deleted file mode 100644 index 473ffc2..0000000 --- a/FRONT/BUILD/resources/app/src/const/types/types.ts +++ /dev/null @@ -1,213 +0,0 @@ -export interface IEdge { - source: string - sourceHandle: string - target: string - targetHandle: string - id: string - type: string - style?: any -} - -export interface ICanvas { - nodes: any[], - edges: IEdge[] -} - -export interface IpRoutes { - destination: string - prefix: number, - next_hop: string - metric: number -} - -export interface ResponseIpConf { - address: string, - method: string, - gateway: string, - dns: string[] - routes: IpRoutes[] -} - -export interface ResponseNetIp { - configurations: ResponseIpConf[] -} - -export interface ResponseNetwork { - name: string, - type: string, - mac_address: string, - device_state: string, - active_connection: string, - device_mtu: string, - strength: string, - ipv4: ResponseNetIp - ipv6: ResponseNetIp -} - -export interface ResponseData { - interfaces: ResponseNetwork[], - container_x: number, - container_y: number, - id: number | string -} - -export interface OutputIpData { - ipType: string, - ipAdress: string, - ipGate: string, - ipDns: string[] - ipRoutese: IpRoutes[] - ipVersion: string -} - -export interface IPosition { - x: number; - y: number; -} - -export interface IStyle { - width: number, - height: number, - borderType?: string; - borderColor?: string; - backgroundColor?: string -} - -export interface NetworkData { - device: string - type: string - mac: string - mtu: string, - state: string, - connection: string - id?: string, - strength?: string -} - -export interface IpData { - ipType: string, - ipAdress: string, - ipGate: string, - ipDns: string[] - ipRoutese: IpRoutes[] - ipVersion: string -} - - -export interface NetworkNode { - id: string; - position: IPosition; - data: { - networkData: NetworkData, - style: IStyle, - height: any, - visible: boolean - } - type: string; - parentId: string, - extent: string, -} - -export interface PortNode { - id: string - position: IPosition - data: { - id: string - label: string - port?: string - style: IStyle - } - type: string, - parentId?: string, - extent?: string, -} - -export interface IpNode { - id: string; - position: IPosition; - parentId: string - extent: string - draggable: boolean, - data: { - ipData: IpData; - interfaceData:{ - device: string, - connection: string - } - route: boolean - style: IStyle - }; - hidden?: boolean - type: string; -} - -export interface NodeGroup { - id: string - position: IPosition - parentId?: string - extent?: string - draggable?: boolean, - data: { - id?: string - label?: string - height?: any - style: IStyle - }, - hidden?: boolean - type: string -} - - -export interface OutputIpArray { - route: boolean, - ipData: OutputIpData[] -} - -export interface MainData { - networkData: NetworkData - ipArrays: OutputIpArray[] -} - -export interface Interfaces { - mainData: MainData[] - container_x: number, - container_y: number -} - -export interface IData{ - activation: ResponseData - deactivation: ResponseData -} - -export interface changeIp { - currentIp: string - newIp: string - currentGate: string - newGate: string - currentDns: string[] - newDns: string[] - device: string - connection: string - version: string - prefix: number -} - -export interface WorkSpace { - id: string, - isActive: boolean -} - -export interface Menu { - id: string, - isOpen: boolean, - isActive?: boolean -} - -export interface ToolBar{ - toolSpace: Menu[] - current: string -} - -export type names = Record; - -export type NetworkInterface = NetworkNode | PortNode | NodeGroup | IpNode \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/const/workspace/names.ts b/FRONT/BUILD/resources/app/src/const/workspace/names.ts deleted file mode 100644 index 796b9c6..0000000 --- a/FRONT/BUILD/resources/app/src/const/workspace/names.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { names } from "../types/types" - -export const workSpaceNames: names ={ - 1: 'helloWindow', - 2: 'aboutApp', - 3: 'networkInterfaces' -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/electron.d.ts b/FRONT/BUILD/resources/app/src/electron.d.ts deleted file mode 100644 index 88da5b9..0000000 --- a/FRONT/BUILD/resources/app/src/electron.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -interface Window { - electron: { - quitApp: () => void; - }; -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/hooks/useMenu.ts b/FRONT/BUILD/resources/app/src/hooks/useMenu.ts deleted file mode 100644 index 703b7b0..0000000 --- a/FRONT/BUILD/resources/app/src/hooks/useMenu.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useMenuStore } from "../store" - -export const useMenu = (menuId: string)=>{ - const { menus, toggleMenu } = useMenuStore(); - const menu = menus.find((m)=>m.id === menuId) - const isOpen = menu ? menu.isOpen : false; - - const collapseMenu = ()=>{ - if(menu){ - toggleMenu(menuId) - } - } - - return { isOpen, collapseMenu }; -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/hooks/useToolBar.ts b/FRONT/BUILD/resources/app/src/hooks/useToolBar.ts deleted file mode 100644 index debf65c..0000000 --- a/FRONT/BUILD/resources/app/src/hooks/useToolBar.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useToolBarStore } from "../store"; - -export const useToolBar = (menuId: string) => { - const { toolBar, toggleMenu } = useToolBarStore(); - - const menu = toolBar.toolSpace.find((m) => m.id === menuId); - - const isOpen = menu ? menu.isOpen : false; - const isActive = menu ? menu.isActive : false; - - const collapseMenu = () => { - toggleMenu(menuId); - }; - - return { isOpen, isActive, collapseMenu }; -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/router.tsx b/FRONT/BUILD/resources/app/src/router.tsx deleted file mode 100644 index d4828ce..0000000 --- a/FRONT/BUILD/resources/app/src/router.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Routes, Route, Outlet } from 'react-router-dom'; -import WorkSpace from './components/workSpace/workSpace'; -import { useIpStore } from './store'; -import { useEffect, useState } from 'react'; -import { NetworkService } from './api/request'; -import { CreateUser } from './components/register'; -import RouterLoader from './components/UI/routerLoader'; - -export default function Router() { - - const { ipAddress } = useIpStore(); - const [configExists, setConfigExists] = useState(null); - - useEffect(() => { - const fetchServerConf = async () => { - try { - let response = await NetworkService.checkServerConf(ipAddress); - if(response.status === 200) { - console.log(response.data.message); - setConfigExists(response.data.has_users); - } - } catch (error) { - console.error('Error checking server config:', error); - setConfigExists(false); - } - }; - - fetchServerConf(); - }, [ipAddress]); - - if (configExists === null) { - return - } - - if (configExists === false) { - return ; - } - - return ( - - } /> - - ); -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/store/adminStore.ts b/FRONT/BUILD/resources/app/src/store/adminStore.ts deleted file mode 100644 index a853372..0000000 --- a/FRONT/BUILD/resources/app/src/store/adminStore.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { create } from 'zustand' - -interface AdminState { - isAdmin: boolean; - setAdmin: (isAdmin: boolean) => void; -} - -export const useAdminStore = create()((set) => ({ - isAdmin: false, - setAdmin: (isAdmin: boolean) => set({ isAdmin }), -})) \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/store/devicesStore.ts b/FRONT/BUILD/resources/app/src/store/devicesStore.ts deleted file mode 100644 index 3e7d143..0000000 --- a/FRONT/BUILD/resources/app/src/store/devicesStore.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { create } from 'zustand' -import { persist, createJSONStorage } from 'zustand/middleware' - -export interface Device { - number: number - address: string -} - -interface DevicesState { - devices: Device[] - addDevice: (address: string) => void - deleteDevice: (number: number) => void -} - -export const useDevicesStore = create()( - persist( - (set) => ({ - devices: [], - addDevice: (address) => - set((state) => { - const newDevice = { - number: state.devices.length + 1, - address - } - return { devices: [...state.devices, newDevice] } - }), - deleteDevice: (number) => - set((state) => ({ - devices: state.devices - .filter(device => device.number !== number) - .map((device, index) => ({ ...device, number: index + 1 })) - })) - }), - { - name: 'connectedDevices-storage', - storage: createJSONStorage(() => localStorage) - } - ) -) \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/store/flowStore.ts b/FRONT/BUILD/resources/app/src/store/flowStore.ts deleted file mode 100644 index 5e66ece..0000000 --- a/FRONT/BUILD/resources/app/src/store/flowStore.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { create } from 'zustand'; - -interface NodesStore { - nodes: any[]; - edges: any[]; - setNodes: (nodes: any[]) => void; - setEdges: (edges: any[]) => void; - updateNode: (nodeId: string, newData: any) => void; -} - -export const useNodesStore = create((set) => ({ - nodes: [], - edges: [], - setNodes: (nodes) => set({ nodes }), - setEdges: (edges) => set({ edges }), - updateNode: (nodeId, newData) => - set((state) => ({ - nodes: state.nodes.map((node) => - node.id === nodeId ? { ...node, data: { ...node.data, ...newData } } : node - ), - })), -})); \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/store/groupStore.ts b/FRONT/BUILD/resources/app/src/store/groupStore.ts deleted file mode 100644 index e2859b7..0000000 --- a/FRONT/BUILD/resources/app/src/store/groupStore.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {create} from 'zustand'; - -interface GroupState { - groups: { id: string; click: boolean }[]; - setGroups: (groups: { id: string; click: boolean }[]) => void; -} - -const useGroupStore = create((set) => ({ - groups: [], - setGroups: (groups) => set({ groups }), -})); - -export { useGroupStore }; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/store/index.ts b/FRONT/BUILD/resources/app/src/store/index.ts deleted file mode 100644 index f341a28..0000000 --- a/FRONT/BUILD/resources/app/src/store/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export {useModalContext, ModalProvider} from './modalContext' -export {useTheme, ThemeProvider} from './themeContext' -export {usePannableStore} from './pannableStore' -export {useGroupStore} from './groupStore' -export {useIpStore} from './ipStore' -export {useToolBarStore} from './toolBarStore' -export {useDevicesStore} from './devicesStore' -export {useMenuStore} from './menusStore' -export {useWorkSpaceStore} from './workSpaceStore' \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/store/ipStore.ts b/FRONT/BUILD/resources/app/src/store/ipStore.ts deleted file mode 100644 index 2b6b378..0000000 --- a/FRONT/BUILD/resources/app/src/store/ipStore.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {create} from 'zustand' -import { persist, createJSONStorage } from 'zustand/middleware' - -interface IpAddressState { - ipAddress: string; - setIpAddress: (newIp: string) => void; -} - -export const useIpStore = create()( - persist( - (set)=>({ - ipAddress: 'localhost', - setIpAddress: (newIp: string) => set({ ipAddress: newIp }), - }), - { - name: 'currentIp-storage', - storage: createJSONStorage(()=>localStorage) - } - ) -) \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/store/menusStore.ts b/FRONT/BUILD/resources/app/src/store/menusStore.ts deleted file mode 100644 index a7cc1ee..0000000 --- a/FRONT/BUILD/resources/app/src/store/menusStore.ts +++ /dev/null @@ -1,28 +0,0 @@ - -import { create } from 'zustand'; -import { persist, createJSONStorage } from 'zustand/middleware'; -import { Menu } from '../const/types/types'; -import { menusData } from '../const/menus/othersMenus/data'; - -interface MenuState { - menus: Menu[]; - toggleMenu: (menuId: string) => void; -} - -export const useMenuStore = create()( - persist( - (set) => ({ - menus: menusData, - toggleMenu: (menuId) => - set((state) => ({ - menus: state.menus.map((menu) => - menu.id === menuId ? { ...menu, isOpen: !menu.isOpen } : menu - ), - })), - }), - { - name: 'menus-storage', - storage: createJSONStorage(() => localStorage) - } - ) -); \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/store/modalContext.tsx b/FRONT/BUILD/resources/app/src/store/modalContext.tsx deleted file mode 100644 index af27986..0000000 --- a/FRONT/BUILD/resources/app/src/store/modalContext.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { createContext, useContext, useState, ReactNode, useCallback } from 'react'; -import { useEffect } from 'react'; - -interface ModalContextType { - isOpen: boolean; - modalType: string; - modalData: any; - openModal: (modalType: string, modalData?: any) => void; - closeModal: () => void; - closeLoader: () => void; -} - -const ModalContext = createContext({ - isOpen: false, - modalType: '', - modalData: null, - openModal: () => {}, - closeModal: () => {}, - closeLoader: () => {}, -}); - -export const useModalContext = () => useContext(ModalContext); - -export function ModalProvider({ children }: { children: ReactNode }) { - const [isOpen, setOpen] = useState(false); - const [modalType, setType] = useState(''); - const [modalData, setData] = useState(null); - - const openModal = (type: string, data?: any) => { - setOpen(true); - setType(type); - setData(data); - }; - - const closeModal = useCallback(() => { - setOpen(false); - setType(''); - setData(null); - - window.location.reload() - }, []); - - const closeLoader = useCallback(() => { - setOpen(false); - setType(''); - setData(null); - }, []); - - useEffect(() => { - if (isOpen) { - document.body.style.overflow = 'hidden'; - } - else { - document.body.style.overflow = 'auto'; - } - }, [isOpen]) - - return ( - - {children} - - ); -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/store/pannableStore.ts b/FRONT/BUILD/resources/app/src/store/pannableStore.ts deleted file mode 100644 index 75b9c65..0000000 --- a/FRONT/BUILD/resources/app/src/store/pannableStore.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {create} from 'zustand'; - -interface FlowState { - isPanDisabled: boolean; - toggleEditingMode: () => void; -} - -export const usePannableStore = create((set) => ({ - isPanDisabled: false, - toggleEditingMode: () => set((state) => ({ isPanDisabled: !state.isPanDisabled })), -})); \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/store/posStore.ts b/FRONT/BUILD/resources/app/src/store/posStore.ts deleted file mode 100644 index 979545c..0000000 --- a/FRONT/BUILD/resources/app/src/store/posStore.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { create } from 'zustand' - -interface PosState { - isNullPos: boolean; - setNullPos: (isNullPos: boolean) => void; -} - -export const usePosStore = create()((set) => ({ - isNullPos: false, - setNullPos: (isNullPos: boolean) => set({ isNullPos }), -})) \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/store/themeContext.tsx b/FRONT/BUILD/resources/app/src/store/themeContext.tsx deleted file mode 100644 index 7e0623c..0000000 --- a/FRONT/BUILD/resources/app/src/store/themeContext.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { createContext, useContext, useState, useEffect } from 'react'; -import { ThemeProvider as StyledTheme } from 'styled-components'; -import { light, dark} from '../const/theme/theme'; -import { GlobalStyle } from '../const/theme/globalStyles'; -import { themeNames, ThemName } from '../const/theme/names'; - -type ThemeContextType = { - theme: ThemName; - toggleTheme: () => void; -}; - -const ThemeContext = createContext(undefined); - -export const useTheme = () => { - const context = useContext(ThemeContext); - if (!context) { - throw new Error('useTheme must be used within a ThemeProvider'); - } - return context; -}; - -export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const getSystemTheme = () => - window.matchMedia('(prefers-color-scheme: dark)').matches ? themeNames[2] : themeNames[1]; - - const getInitialTheme = () => { - let savedTheme = localStorage.getItem('theme') as ThemName; - if (!savedTheme) { - savedTheme = themeNames[3]; // Устанавливаем значение по умолчанию - localStorage.setItem('theme', savedTheme); // Сохраняем без JSON.stringify - } - return savedTheme; - }; - - const [theme, setTheme] = useState(getInitialTheme); - - const toggleTheme = () => { - setTheme((prev) => { - let newTheme: ThemName; - if (prev === themeNames[3]) { - newTheme = themeNames[1]; - } else if (prev === themeNames[1]) { - newTheme = themeNames[2]; - } else { - newTheme = themeNames[3]; - } - localStorage.setItem('theme', newTheme); - - return newTheme; - }); - }; - - useEffect(() => { - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - let currentTheme = localStorage.getItem('theme') - const handleChange = () => { - if (currentTheme === themeNames[3]) { - const newTheme = getSystemTheme(); - - setTheme(newTheme); - localStorage.setItem('theme', themeNames[3]); - } - }; - mediaQuery.addEventListener('change', handleChange); - return () => mediaQuery.removeEventListener('change', handleChange); - }, [theme]); - - const currentTheme = theme === themeNames[3] ? getSystemTheme() : theme; - - return ( - - - - {children} - - - ); -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/store/toolBarStore.ts b/FRONT/BUILD/resources/app/src/store/toolBarStore.ts deleted file mode 100644 index 761defc..0000000 --- a/FRONT/BUILD/resources/app/src/store/toolBarStore.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { create } from 'zustand'; -import { persist, createJSONStorage } from 'zustand/middleware'; -import { toolBarData } from '../const/menus/sideBar/data'; -import { ToolBar } from '../const/types/types'; - -interface MenuState { - toolBar: ToolBar; - toggleMenu: (menuId: string) => void; - initialize: () => void; -} - -export const useToolBarStore = create()( - persist( - (set, get) => ({ - toolBar: toolBarData, - toggleMenu: (menuId) => set((state) => { - const updatedToolBar = state.toolBar.toolSpace.map((menu) => { - if (menu.id === menuId) { - return { - ...menu, - isOpen: !menu.isOpen, - isActive: true, - }; - } else { - return { - ...menu, - isActive: false, - isOpen: false - }; - } - }); - - return { - toolBar: { - ...state.toolBar, - current: menuId, - toolSpace: updatedToolBar, - }, - }; - }), - initialize: () => { - set({ toolBar: toolBarData }); - }, - }), - { - name: 'toolBar-storage', - storage: createJSONStorage(() => localStorage), - } - ) -); \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/store/workSpaceStore.ts b/FRONT/BUILD/resources/app/src/store/workSpaceStore.ts deleted file mode 100644 index 2f14215..0000000 --- a/FRONT/BUILD/resources/app/src/store/workSpaceStore.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { create } from 'zustand' -import { persist, createJSONStorage } from 'zustand/middleware' -import { workSpaceNames } from '../const/workspace/names'; - -interface WorkSpaceState { - workModule: string; - setWorkModule: (newModule: string) => void; - initialize: () => void; -} - -export const useWorkSpaceStore = create()( - persist( - (set) => ({ - workModule: workSpaceNames[1], - setWorkModule: (newModule: string) => set({ workModule: newModule }), - initialize: () => { - set({ workModule: workSpaceNames[1]}); - }, - }), - - { - name: 'workSpace-storage', - storage: createJSONStorage(() => localStorage) - } - ) -) \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/utils/checkAdmin.ts b/FRONT/BUILD/resources/app/src/utils/checkAdmin.ts deleted file mode 100644 index 752df21..0000000 --- a/FRONT/BUILD/resources/app/src/utils/checkAdmin.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useIpStore } from "../store"; -import { useAdminStore } from "../store/adminStore"; -import { useEffect } from "react"; -import { NetworkService } from "../api/request"; - -export const useCheckAdmin = () => { - const { ipAddress } = useIpStore() - const { setAdmin } = useAdminStore() - - return async () => { - const response = await NetworkService.whoami(ipAddress); - console.log(response.data); - if(response.status == 401){ - setAdmin(false) - } - else{ - setAdmin(response.data.user.is_admin); - } - } -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/utils/nodeController/autosavePosition.ts b/FRONT/BUILD/resources/app/src/utils/nodeController/autosavePosition.ts deleted file mode 100644 index 7b9e21e..0000000 --- a/FRONT/BUILD/resources/app/src/utils/nodeController/autosavePosition.ts +++ /dev/null @@ -1,104 +0,0 @@ -export const processNodeChanges = (changes: any, mainGroups: any) => { - let newData; - - changes.forEach((change: any) => { - if (change.type === 'position' && change.position) { - const nodeId = change.id; - - if (nodeId === 'g0') { - newData = { - container_id: mainGroups[0].id, - container_x: change.position.x, - container_y: change.position.y, - isOpen: true - }; - } else if (nodeId === 'g1') { - newData = { - container_id: mainGroups[1].id, - container_x: change.position.x, - container_y: change.position.y, - isOpen: true - }; - } - } - }); - - return newData; -}; - -const getLastChanges = (changes: any[]) => { - const latestChanges: { [key: number]: any } = {}; - - changes.forEach((change) => { - latestChanges[change.container_id] = change; - }); - - return Object.values(latestChanges); -}; - -export const autosavePosition = (changeArray: any[], socket: any,) => { - if (changeArray.length > 0) { - const lastChanges = getLastChanges(changeArray); - - lastChanges.forEach((change) => { - if (socket && socket.readyState === WebSocket.OPEN && change) { - socket.send(JSON.stringify(change)) - console.log('JSON отправлен:', JSON.stringify(change)); - console.log('Отправленные данные:', change); - } else { - console.log('WebSocket не открыт или нет данных для отправки'); - } - }); - } -} - -export const resetPosition = (socket: any, setNullPos: (value: boolean) => void, nodes: any[], setNodes: (nodes: any[]) => void) => { - const nullPositions = [ - { - container_id: 1, - container_x: 0, - container_y: 0, - isOpen: true - }, - { - container_id: 2, - container_x: 500, - container_y: 0, - isOpen: true - }, - ] - - nullPositions.forEach(pos => { - if (socket && socket.readyState === WebSocket.OPEN) { - socket.send(JSON.stringify(pos)) - console.log('JSON отправлен:', JSON.stringify(pos)) - console.log('Отправленные данные:', pos) - } else { - console.log('WebSocket не открыт или нет данных для отправки') - } - }) - - const updatedNodes = nodes.map(node => { - - if(node.id=="g0"){ - return { - ...node, - position: { x: 0, y: 0 }, - positionAbsolute: { x: 0, y: 0 } - }; - } - - if(node.id=="g1"){ - return { - ...node, - position: { x: 500, y: 0 }, - positionAbsolute: { x: 500, y: 0 } - }; - } - - return node; - }); - - setNodes(updatedNodes); - setNullPos(false); -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/utils/nodeController/collapseGroup.ts b/FRONT/BUILD/resources/app/src/utils/nodeController/collapseGroup.ts deleted file mode 100644 index a21cc9f..0000000 --- a/FRONT/BUILD/resources/app/src/utils/nodeController/collapseGroup.ts +++ /dev/null @@ -1,65 +0,0 @@ -export const collapseGroup = (sortedGroup: any, nodes: any[]) => { - const nodesMap = new Map(nodes.map(node => [node.id, structuredClone(node)])); - - Object.entries(sortedGroup).forEach(([groupId, groupData]: any) => { - const groupNode = nodesMap.get(groupId); - if (!groupNode) return; - - const clicked = groupData.clicked; - const padding = 20; - - const higherOrderNodes = groupData.higherOrder.map((childId: string) => { - const child = nodesMap.get(childId); - if (!child) return null; - - const newHeight = clicked ? 60 : child.data.height; - const updatedChild = { - ...child, - data: { - ...child.data, - visible: !clicked, - style: { ...child.data.style, height: newHeight } - }, - measured: { - ...child.measured, - height: newHeight + 4, - width: child.data.style.width + 4 - } - }; - nodesMap.set(childId, updatedChild); - return updatedChild; - }).filter(Boolean); - - groupData.lowerOrder.forEach((childId: string) => { - const child = nodesMap.get(childId); - if (child) child.hidden = clicked; - }); - - let currentY = padding; - higherOrderNodes.forEach((child: any) => { - nodesMap.set(child.id, { - ...child, - position: { ...child.position, y: currentY } - }); - currentY += child.measured.height + padding; - }); - - const newGroupHeight = clicked - ? currentY - : groupNode.data.height; - - nodesMap.set(groupId, { - ...groupNode, - data: { - ...groupNode.data, - style: { ...groupNode.data.style, height: newGroupHeight } - }, - measured: { - ...groupNode.measured, - height: newGroupHeight + 4 - } - }); - }); - - return Array.from(nodesMap.values()); -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/utils/nodeController/groupSort.ts b/FRONT/BUILD/resources/app/src/utils/nodeController/groupSort.ts deleted file mode 100644 index 3d9064f..0000000 --- a/FRONT/BUILD/resources/app/src/utils/nodeController/groupSort.ts +++ /dev/null @@ -1,37 +0,0 @@ -export const groupSort = (groups: any, nodes: any)=>{ - - const result: { - [key: string]: { - higherOrder: string[]; - lowerOrder: string[]; - clicked: boolean - }; - } = {}; - - groups.forEach((group: any) => { - const groupId = group.id; - result[groupId] = { - higherOrder: [], - lowerOrder: [], - clicked: group.click - }; - - const children = nodes.filter((node: any) => node.id.startsWith(`${groupId}_`)); - - children.forEach((child: any) => { - const childId = child.id; - - const parts = childId.split('_'); - if (parts.length === 2) { - const higherOrderValue = childId - result[groupId].higherOrder.push(higherOrderValue) - } else { - const lowerOrderValue = childId - result[groupId].lowerOrder.push(lowerOrderValue) - } - }); - }); - - - return result -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/utils/nodeController/index.ts b/FRONT/BUILD/resources/app/src/utils/nodeController/index.ts deleted file mode 100644 index 26cebec..0000000 --- a/FRONT/BUILD/resources/app/src/utils/nodeController/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { autosavePosition, processNodeChanges , resetPosition} from './autosavePosition'; -export { initNetworkIntData } from './initNetIntData'; -export { collapseGroup } from './collapseGroup'; -export {groupSort} from './groupSort' -export { updateWifiData } from './updateWifiData'; -export {useInitCanvas} from './nodeCreator/inits/initor' \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/utils/nodeController/initNetIntData.ts b/FRONT/BUILD/resources/app/src/utils/nodeController/initNetIntData.ts deleted file mode 100644 index c54f060..0000000 --- a/FRONT/BUILD/resources/app/src/utils/nodeController/initNetIntData.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { ResponseData, Interfaces, MainData, IEdge, NetworkData } from '../../const/types/types' -import { ipsInitor } from './nodeCreator/inits/initIpArray'; - -export const initNetworkIntData = (mainGroups: ResponseData[]): { interfaces: Interfaces[], edgeData: IEdge[] } => { - const edgeData: IEdge[] = []; - const interfaces: Interfaces[] = []; - - if (mainGroups) { - mainGroups.map((el, indx) => { - let nodeData: MainData[] = []; - el.interfaces.map((net, index) => { - let nodeID = `g${indx}_nw${index}`; - let portID = `p-nw${index}`; - let targetId = `${portID}_target-right`; - let sourceId = `${nodeID}-source-left`; - - if (indx === 0) { - const edge: IEdge = { - id: `${nodeID}-${portID}`, - source: nodeID, - target: portID, - sourceHandle: sourceId, - targetHandle: targetId, - type: "default", - }; - edgeData.push(edge); - } - - const network: NetworkData = { - device: net.name, - type: net.type, - mac: net.mac_address, - connection: net.active_connection, - mtu: net.device_mtu, - state: net.device_state, - strength: net.strength - }; - - const ipArray = ipsInitor(net); - - let interfaceData: MainData = { - networkData: network, - ipArrays: ipArray - }; - - nodeData.push(interfaceData); - }); - - let j: Interfaces = { - container_x: el.container_x, - container_y: el.container_y, - mainData: nodeData - }; - - interfaces.push(j); - }); - } - - return { interfaces, edgeData }; -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/deviceState.ts b/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/deviceState.ts deleted file mode 100644 index aa2dd6f..0000000 --- a/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/deviceState.ts +++ /dev/null @@ -1,33 +0,0 @@ -type StateType = Record; - -export const stateMap: StateType = { - '0': "Неизвестно", - '10': "Не обслуживается", - '20': "Недоступно", - '30': "Отключено", - '40': "Подготовка", - '50': "Конфигурация", - '60': "Требуется аутентификация", - '70': "Настройка IP", - '80': "Проверка IP", - '90': "Дополнительные настройки", - '100': "Подключено", - '110': "Отключается", - '120': "Ошибка", -} - -export const stateColor: StateType = { - '0': "#7e7e7e", - '10': "#d95454", - '20': "#d95454", - '30': "#d95454", - '40': "", - '50': "", - '60': "", - '70': "", - '80': "", - '90': "", - '100': "#66BB6A", - '110': "", - '120': "", -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/inits/initEdges.ts b/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/inits/initEdges.ts deleted file mode 100644 index c422a83..0000000 --- a/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/inits/initEdges.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useState } from "react"; -import { IEdge } from "../../../../const/types/types"; - -export const useInitEdges = () => { - const [initedEdges, setEdges] = useState([]) - - const setInitEdges = (something: IEdge[]) => { - setEdges((prevEdges) => { - const edges: IEdge[] = [] - - something.map(edge=>{ - edges.push(edge) - }) - - return [...prevEdges, ...edges] - }) - } - return { initedEdges, setInitEdges } -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/inits/initIpArray.ts b/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/inits/initIpArray.ts deleted file mode 100644 index 5806438..0000000 --- a/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/inits/initIpArray.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ResponseIpConf, ResponseNetwork, IpData } from "../../../../const/types/types" - -interface IpArray { - route: boolean, - ipData: IpData[] -} - -export const ipsInitor = (network: ResponseNetwork) => { - const array: IpArray[] = [ - { route: true, ipData: [] }, - { route: false, ipData: [] }, - ]; - - const ipArrayInit = (ip: ResponseIpConf, version: string) => { - const isRouted = ip.routes.length > 0 - - const newData: IpData = { - ipType: ip.method, - ipAdress: ip.address, - ipGate: ip.gateway, - ipDns: ip.dns, - ipRoutese: ip.routes, - ipVersion: version - }; - - if (isRouted) { - array[0].ipData.push(newData); - } else { - array[1].ipData.push(newData); - } - } - - network.ipv4.configurations.forEach((ip: ResponseIpConf) => { - ipArrayInit(ip, '4') - }); - - network.ipv6.configurations.forEach((ip: ResponseIpConf) => { - ipArrayInit(ip, '6') - }); - - const resultArray = array.filter((item) => item.ipData.length > 0); - - return resultArray; -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/inits/initNodes.ts b/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/inits/initNodes.ts deleted file mode 100644 index a539069..0000000 --- a/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/inits/initNodes.ts +++ /dev/null @@ -1,297 +0,0 @@ -import { useState } from "react"; -import { NodeColors } from "../../../../components/customeNodes/types/colors"; -import { stateColor, stateMap } from "../deviceState"; -import { Interfaces, NetworkNode, IpNode, PortNode, NodeGroup, NetworkInterface } from "../../../../const/types/types" - -export const useInitNodes = () => { - const [initedNodes, setNodes] = useState<(NetworkInterface)[]>([]); - const yGap = 20 - - interface MainHeight { - x?: number, - y?: number, - height?: number; - } - - const findMaxHeight = (data: any[])=>{ - const filtred = data.filter((el)=> el.x ===0 || el.x ===1) - if(filtred.length ===0){ - return 140 - } - const max =filtred.reduce((max, el)=> Math.max(max, el.height), 0) - return max - - } - - const setInitNodes = (interfaces: Interfaces[]) => { - setNodes(() => { - const networkNodes: NetworkNode[] = [] - const portNodes: PortNode[] = [] - const ipGroupes: NodeGroup[][] = [] - const ipNodes: IpNode[][] = [] - const networkGroups: NodeGroup[] = [] - - let width: number, columnCount: MainHeight[] = [] - let widthArr: number[] = [] - let heightArr: number[] = [] - - interfaces.map((b, indexB) => { - const centerX = 20 - const centerY = 20 - let currentY = centerY; - b.mainData.map((data, networkIndex) => { - data.ipArrays.map((el) => { - if (el.route === true) { - const column = { x: 1 } - columnCount.push(column) - } - else if (el.route === false) { - const column = { x: 0 } - columnCount.push(column) - } - }) - - if (columnCount.length == 2) { - width = (260 * 2) + 45 - } - else { - width = 260 + 35 - } - - let mainHeight: MainHeight[] = [] - - data.ipArrays.forEach((ipInfo, groupIndx) => { - if(!ipInfo || !ipInfo.ipData || ipInfo.ipData.length ===0){ - return - } - let groupes: NodeGroup[] = [] - let nodeCount = ipInfo.ipData.length - let x = 20, y = 150, newX, newY, gap = 0 - - let height = (140 + 15) * (nodeCount) - - - if (ipInfo.route) { - newX = width - 260 - 15 - - const groupHeight = { - x: 1, - height: height - } - mainHeight.push(groupHeight) - } - else { - newX = x - const groupHeight = { - x: 0, - height: height - } - mainHeight.push(groupHeight) - } - - - const newIpGroupe: NodeGroup = { - id: `g${indexB}_nw${networkIndex}_rx${groupIndx}`, - position: { - x: newX, - y: y - }, - data: { - style: { - width: 260, - height: height - } - }, - hidden: false, - type: "ipgroup", - parentId: `g${indexB}_nw${networkIndex}`, - extent: 'parent', - draggable: false, - } - - let startY = 0 - const ipGap = 15 - - - ipInfo.ipData.map((ip, ipIndex) => { - let nodes: IpNode[] = [] - const y = startY; - - startY += 140 + ipGap; - - - const newIpNode: IpNode = { - id: `g${indexB}_nw${networkIndex}_rx${groupIndx}_ip${ipIndex}`, - position: { - x: 0, - y: y - }, - type: 'ip', - data: { - ipData: { - ipType: ip.ipType, - ipAdress: ip.ipAdress, - ipGate: ip.ipGate, - ipDns: ip.ipDns, - ipRoutese: [], - ipVersion: ip.ipVersion - }, - interfaceData:{ - device: data.networkData.device, - connection: data.networkData.connection, - }, - route: ipInfo.route, - style: { - width: 260, - height: 140, - borderType: "solid", - borderColor: NodeColors["1"], - }, - }, - hidden: false, - parentId: `g${indexB}_nw${networkIndex}_rx${groupIndx}`, - extent: 'parent', - draggable: false, - } - nodes.push(newIpNode) - ipNodes.push(nodes) - }) - groupes.push(newIpGroupe) - ipGroupes.push(groupes) - }) - - const maxHeight = findMaxHeight(mainHeight); - // const networkNodeHeight = (maxHeight || 0) + 150; - const networkNodeHeight = maxHeight +150 - - let groupId: string - if (data.networkData.connection === 'unknown') { - groupId = 'cn1' - } - else { - groupId = 'cn0' - } - - const y = currentY; - - currentY += networkNodeHeight + yGap; - - - const newNode: NetworkNode = { - id: `g${indexB}_nw${networkIndex}`, - position: { - x: centerX, - y: y - }, - type: "network", - data: { - networkData: { - device: data.networkData.device, - type: data.networkData.type, - mac: data.networkData.mac, - mtu: data.networkData.mtu, - state: stateMap[data.networkData.state], - connection: data.networkData.connection, - id: `nw${networkIndex}`, - strength: data.networkData.strength - }, - style: { - width: width, - height: networkNodeHeight as number, - borderType: "solid", - borderColor: NodeColors["0"], - backgroundColor: stateColor[data.networkData.state] - }, - height: networkNodeHeight, - visible: true - }, - parentId: `g${indexB}`, - extent: 'parent', - }; - - widthArr.push(width) - heightArr.push(networkNodeHeight) - networkNodes.push(newNode) - columnCount = [] - - let portNodeHeight = 60; - let absoluteY = (currentY - portNodeHeight + 10) - (networkNodeHeight / 2); - if (data.networkData.connection !== 'unknown') { - const portNode: PortNode = { - id: `p-nw${networkIndex}`, - position: { - x: -350, - y: absoluteY, - }, - type: 'portNode', - data: { - label: `${data.networkData.connection}`, - id: `p-nw${networkIndex}`, - style: { - width: 260, - height: portNodeHeight, - borderType: "solid", - borderColor: NodeColors["0"], - }, - } - } - portNodes.push(portNode) - } - }) - - const sumHeight = heightArr.reduce((a, c) => a + c, 0); - const maxWidth = Math.max(...widthArr) - - let count = b.mainData.length - let groupHeight = sumHeight + (count * 30) - let groupWidth = maxWidth + 40 - - let label: string; - - if (indexB == 0) { - label = 'Активные интерфейсы' - } - else { - label = 'Неактивные интерфейсы' - } - - let x = b.container_x - if (indexB > 0 && x === 0) { - x = x + 500 - } - - if (b.mainData.length > 0) { - const networkGroup: NodeGroup = { - id: `g${indexB}`, - position: { - x: x, - y: b.container_y - }, - type: "nodegroup", - data: { - label: label, - id: `g${indexB}`, - height: groupHeight, - style: { - width: groupWidth, - height: groupHeight, - borderType: "solid", - borderColor: NodeColors["0"], - }, - }, - } - - networkGroups.push(networkGroup) - } - - - heightArr = [] - }) - - return [...networkGroups, ...networkNodes, ...ipGroupes.flat(), ...portNodes, ...ipNodes.flat()]; - }); - - }; - - return { initedNodes, setInitNodes }; -}; diff --git a/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/inits/initor.ts b/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/inits/initor.ts deleted file mode 100644 index bf2f187..0000000 --- a/FRONT/BUILD/resources/app/src/utils/nodeController/nodeCreator/inits/initor.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useInitNodes } from "./initNodes"; -import { useInitEdges } from "./initEdges"; -import { IEdge, Interfaces } from "../../../../const/types/types"; - -export const useInitCanvas = () => { - const { initedNodes, setInitNodes } = useInitNodes(); - const { initedEdges, setInitEdges } = useInitEdges(); - - const handleInit = (edgeData: IEdge[], interfaces: Interfaces[])=>{ - setInitNodes(interfaces) - setInitEdges(edgeData) - } - - return { initedNodes, initedEdges, handleInit }; -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/utils/nodeController/updateWifiData.ts b/FRONT/BUILD/resources/app/src/utils/nodeController/updateWifiData.ts deleted file mode 100644 index 7be7d01..0000000 --- a/FRONT/BUILD/resources/app/src/utils/nodeController/updateWifiData.ts +++ /dev/null @@ -1,21 +0,0 @@ -export const updateWifiData = (wifiData: any, nodes: any)=>{ - const wifiName = wifiData.name; - - const updatedNodes = nodes.map((node: any) => { - if (node.data.networkData && node.data.networkData.device === wifiName) { - return { - ...node, - data: { - ...node.data, - networkData: { - ...node.data.networkData, - strength: wifiData.strength, - }, - }, - }; - } - return node; - }); - - return updatedNodes -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/utils/nodeController/validator.ts b/FRONT/BUILD/resources/app/src/utils/nodeController/validator.ts deleted file mode 100644 index 0f5294c..0000000 --- a/FRONT/BUILD/resources/app/src/utils/nodeController/validator.ts +++ /dev/null @@ -1,35 +0,0 @@ -export const validateIPAddress = (ip: string) => { - const ipv4Pattern = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; - const ipv6Pattern = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/; - return ipv4Pattern.test(ip) || ipv6Pattern.test(ip); -}; - -export const validateSameSubnet = (ip1: string, ip2: string, ip3: string | string[]): boolean => { - const extractSubnet = (ip: string): string => { - if (typeof ip !== 'string') { - throw new TypeError(`Некорректный формат IP-адреса: ${ip}`); - } - return ip.split('.').slice(0, 3).join('.'); - }; - - const areInSameSubnet = (ip: string, subnet: string): boolean => { - return extractSubnet(ip) === subnet; - }; - - try { - const subnet1 = extractSubnet(ip1); - const subnet2 = extractSubnet(ip2); - - // Проверяем, что IP и шлюз в одной подсети - if (subnet1 !== subnet2) { - return false; - } - - // Если DNS — массив, проверяем, что хотя бы один DNS в той же подсети - const dnsArray = Array.isArray(ip3) ? ip3 : [ip3]; - return dnsArray.some(dns => areInSameSubnet(dns, subnet1)); - } catch (error) { - console.error(error); - return false; - } -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/src/utils/pageReloads.ts b/FRONT/BUILD/resources/app/src/utils/pageReloads.ts deleted file mode 100644 index 0830c75..0000000 --- a/FRONT/BUILD/resources/app/src/utils/pageReloads.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useEffect, useState } from "react"; - -export const usePageReloadCounter = (): number => { - const [reloadCount, setReloadCount] = useState(() => { - // Инициализируем состояние из sessionStorage или начинаем с 0 - const savedCount = sessionStorage.getItem('pageReloadCount'); - return savedCount ? parseInt(savedCount, 10) : 0; - }); - - useEffect(() => { - // Увеличиваем счётчик при каждом монтировании компонента - const newCount = reloadCount + 1; - setReloadCount(newCount); - sessionStorage.setItem('pageReloadCount', newCount.toString()); - }, []); // Пустой массив зависимостей означает, что эффект сработает только при монтировании - - return reloadCount; -}; \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/tsconfig.app.json b/FRONT/BUILD/resources/app/tsconfig.app.json deleted file mode 100644 index 358ca9b..0000000 --- a/FRONT/BUILD/resources/app/tsconfig.app.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["src"] -} diff --git a/FRONT/BUILD/resources/app/tsconfig.json b/FRONT/BUILD/resources/app/tsconfig.json deleted file mode 100644 index 347251f..0000000 --- a/FRONT/BUILD/resources/app/tsconfig.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": [ - "DOM", - "DOM.Iterable", - "ESNext" - ], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - }, - "include": [ - "src", - "electron.d.ts", - "main.js" - ], - "references": [ - { - "path": "./tsconfig.node.json" - } - ] -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/tsconfig.node.json b/FRONT/BUILD/resources/app/tsconfig.node.json deleted file mode 100644 index dcfd137..0000000 --- a/FRONT/BUILD/resources/app/tsconfig.node.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "module": "ESNext", - "moduleResolution": "Node", - "allowSyntheticDefaultImports": true - }, - "include": [ - "vite.config.ts" - ] -} \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/vite-env.d.ts b/FRONT/BUILD/resources/app/vite-env.d.ts deleted file mode 100644 index 151aa68..0000000 --- a/FRONT/BUILD/resources/app/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// \ No newline at end of file diff --git a/FRONT/BUILD/resources/app/vite.config.ts b/FRONT/BUILD/resources/app/vite.config.ts deleted file mode 100644 index 8de6065..0000000 --- a/FRONT/BUILD/resources/app/vite.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -export default defineConfig({ - plugins: [react()], - build: { - rollupOptions: { - external: ['@electron/remote'], - }, - }, -}); \ No newline at end of file diff --git a/FRONT/DEV/index.tsx b/FRONT/DEV/index.tsx index 38c8de5..eeb0635 100644 --- a/FRONT/DEV/index.tsx +++ b/FRONT/DEV/index.tsx @@ -1,10 +1,8 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; import { HashRouter } from 'react-router-dom'; -import { NetworkService } from './src/api/request.ts'; import { useIpStore } from './src/store'; import { useEffect } from 'react'; -import { useAdminStore } from './src/store/adminStore.ts'; import {useCheckAdmin} from './src/utils/checkAdmin.ts' import { diff --git a/FRONT/DEV/main.js b/FRONT/DEV/main.js index 9273bcc..c3bcadc 100644 --- a/FRONT/DEV/main.js +++ b/FRONT/DEV/main.js @@ -21,7 +21,7 @@ function createWindow() { win.show(); if (process.env.NODE_ENV === 'development') { - win.loadURL('http://localhost:3000'); + win.loadURL('http://192.168.1.152:8001'); win.webContents.openDevTools(); } else { win.loadFile(path.join(__dirname, 'dist/index.html')); @@ -35,7 +35,7 @@ app.whenReady().then(() => { // 2. Всех IP в локальной сети (192.168.x.x, 10.x.x.x и т.д.) // 3. Конкретного домена (если есть) if ( - request.hostname === 'localhost' || + request.hostname === '192.168.1.152' || request.hostname === '127.0.0.1' || request.hostname === '0.0.0.0' || /^(192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.)/.test(request.hostname) @@ -44,6 +44,7 @@ app.whenReady().then(() => { } callback(-2); }); + Menu.setApplicationMenu(null); createWindow(); }); diff --git a/FRONT/DEV/public/assets/images/gossys.png b/FRONT/DEV/public/assets/images/gossys.png new file mode 100644 index 0000000..5b01b0e Binary files /dev/null and b/FRONT/DEV/public/assets/images/gossys.png differ diff --git a/FRONT/DEV/src/api/request.ts b/FRONT/DEV/src/api/request.ts index 377d522..b0568fc 100644 --- a/FRONT/DEV/src/api/request.ts +++ b/FRONT/DEV/src/api/request.ts @@ -1,13 +1,13 @@ import axios, { AxiosResponse } from "axios"; const axiosWithoutCredentials = axios.create({ - baseURL: `https://localhost:8001`, + baseURL: `http://localhost:8001`, }) export class NetworkService { static createAxiosInstance(ipAddress: string) { return axios.create({ - baseURL: `https://${ipAddress}:8001`, + baseURL: `http://${ipAddress}:8001`, }); } @@ -82,4 +82,22 @@ export class NetworkService { return error.response; } } + + static async scanUser(ipAddress: string): Promise { + try { + const axiosInstance = this.createAxiosInstance(ipAddress); + return await axiosInstance.get('/subnet/scan'); + } catch (error: any) { + return error.response; + } + } + + static async scanWifi(ipAddress: string): Promise { + try { + const axiosInstance = this.createAxiosInstance(ipAddress); + return await axiosInstance.get('/subnet/user'); + } catch (error: any) { + return error.response; + } + } } \ No newline at end of file diff --git a/FRONT/DEV/src/components/UI/Dropdown/DeviceCard.tsx b/FRONT/DEV/src/components/UI/Dropdown/DeviceCard.tsx new file mode 100644 index 0000000..79328b2 --- /dev/null +++ b/FRONT/DEV/src/components/UI/Dropdown/DeviceCard.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import styled, { css } from 'styled-components'; +import { DeviceUser } from "../../../store/useDeviceStore"; + +interface DeviceCardProps { + device: DeviceUser; + status: 'online' | 'offline'; +} + +const CardWrapper = styled.div<{ status: 'online' | 'offline' }>` + background: white; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + overflow: hidden; + transition: all 0.3s ease; + cursor: pointer; + margin-bottom: 16px; + + &:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15); + } +`; + +const CardHeader = styled.div<{ status: 'online' | 'offline' }>` + background: ${({ status, theme }) => + status === 'online' ? theme.successColor : theme.errorColor}; + color: white; + padding: 16px; + font-size: 18px; + font-weight: 500; + display: flex; + justify-content: space-between; + align-items: center; +`; + + +const CardContent = styled.div<{ isExpanded: boolean }>` + max-height: 0; + opacity: 0; + padding: 0 16px; + transition: all 0.3s ease; + color: #333; + font-size: 14px; + display: grid; + gap: 12px; + + ${({ isExpanded }) => isExpanded && css` + max-height: 500px; + opacity: 1; + padding: 16px; + `} +`; + +const InfoRow = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; + +const Label = styled.span` + font-weight: 500; + color: #666; +`; + +const Value = styled.span` + color: #333; +`; + +const DeviceCard: React.FC = ({ device, status }) => { + const [isExpanded, setIsExpanded] = React.useState(false); + const toggleExpanded = () => setIsExpanded(!isExpanded); + + + return ( + + +

+ MAC: {device.mac} +

+
+ + + + + {device.hostname || 'Неизвестно'} + + + + + {device.vendor || 'Неизвестно'} + + + + + {device.ip} + + + {device.open_ports?.length > 0 && ( + + + {device.open_ports.join(', ')} + + )} + + + + {device.last_seen} + + +
+ ); +}; + +export default DeviceCard; \ No newline at end of file diff --git a/FRONT/DEV/src/components/UI/Dropdown/DropdownType.tsx b/FRONT/DEV/src/components/UI/Dropdown/DropdownType.tsx new file mode 100644 index 0000000..180684a --- /dev/null +++ b/FRONT/DEV/src/components/UI/Dropdown/DropdownType.tsx @@ -0,0 +1,129 @@ +import React, {useEffect, useRef, useState} from 'react'; +import styled from "styled-components"; +import {buttonNames} from "../../../const/buttons/names"; +import {IconComponent} from "../../../const/buttons/icons"; + +type Option = { + value: number, + label: string, +} + +interface DropdownTypeProps { + options: Option[]; + label?: string; + onChange?: (selectedItem: Option) => void; + value: Option | null; +} + +const DropdownWrapper = styled.div` + position: absolute; + top: 18px; + right:20px; + display: inline-block; + background-color: ${({theme}) => theme.background}; + padding: 12px 16px; + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + z-index: 10; +` +const Label = styled.p` + color: ${({theme}) => theme.textColor}; + font-size: 16px; +` + +const Dropdown = styled.div` + background-color: ${({theme}) => theme.primary}; + border-radius: 15px; + overflow: hidden; + transition: all .3s ease; + cursor: pointer; + z-index: 10; + margin-top: 6px; +` + +const DropdownHeader = styled.div` + display: flex; + justify-content: space-between; + gap: 14px; + padding: 12px 24px; + font-size: 16px; + align-items: center; + height: 100%; + min-width: 200px; +` + +const DropdownContent = styled.div<{$isOpen: boolean}>` + max-height: ${({$isOpen}) => $isOpen ? '500px' : '0'}; + opacity: ${({$isOpen}) => $isOpen ? 1 : 0}; + transition: all .3s ease; + display: flex; + flex-direction: column; + overflow: hidden; +` + +const DropdownRow = styled.button` + padding: 12px 24px; + background-color: transparent; + text-align: start; + font-size: 16px; + border: none; + width: 100%; + transition: all .3s ease; + cursor: pointer; + + &:hover { + background-color: ${({theme}) => theme.background}; + } +` + +const DropdownArrow = styled.div<{ $isOpen: boolean }>` + transition: transform 0.3s ease; + transform: rotate(${({ $isOpen }) => ($isOpen ? "90deg" : "0deg")}); +`; + +const DropdownType = ({options, value, onChange, label}: DropdownTypeProps) => { + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + + function toggleOpen() { + setIsOpen((prev) => !prev); + } + + function handleSelect(option: Option) { + onChange?.(option); + setIsOpen(false); + } + + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsOpen(false); + } + } + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + return ( + + + + +

{value?.label || 'Select an option'}

+ + + +
+ + {options.map((option, i) => ( + handleSelect(option)}> + {option.label} + + ))} + +
+
+ ); +}; + +export default DropdownType; \ No newline at end of file diff --git a/FRONT/DEV/src/components/UI/tables/TableScanner.tsx b/FRONT/DEV/src/components/UI/tables/TableScanner.tsx new file mode 100644 index 0000000..569e295 --- /dev/null +++ b/FRONT/DEV/src/components/UI/tables/TableScanner.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { DeviceUser } from '../../../store/useDeviceStore'; + +interface TableScannerProps { + devices: DeviceUser[]; +} +import styled from 'styled-components'; + +const TableContainer = styled.div` + width: 100%; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08); + background: ${({ theme }) => theme.primary}; + margin: 24px 0; +`; + +const StyledTable = styled.table` + width: 100%; + border-collapse: separate; + border-spacing: 0; +`; + +const TableHeaderCell = styled.th<{ sortable?: boolean }>` + padding: 16px 24px; + text-align: left; + font-weight: 600; + font-size: 14px; + color: ${({ theme }) => theme.textSecondary}; + background: ${({ theme }) => theme.primary}; + border-bottom: 1px solid ${({ theme }) => theme.borderColor}; + transition: all 0.2s ease; +`; + +const TableRow = styled.tr<{ clickable?: boolean }>` + transition: all 0.2s ease; + &:hover { + background-color: ${({ theme }) => theme.background}; + } +`; + +const TableCell = styled.td` + padding: 16px 24px; + font-size: 14px; + color: ${({ theme }) => theme.text}; + border-bottom: 1px solid ${({ theme }) => theme.borderColor}; + transition: all 0.2s ease; +`; + +const StatusBadge = styled.span<{ status:string }>` + width: 10px; + height: 30px; + background-color: ${({theme, status}) => (status==='online' ? theme.successColor : theme.errorColor)}; + display: inline-block; + border-radius:4px; +` + +const TableScanner = ({devices}:TableScannerProps) => { + + return ( + + + + + + Имя + Производитель + IP адрес + Открытые порты + Последняя активность + + + + {devices.map((item, i) => ( + + + {item.hostname || 'Неизвестен'} + {item.vendor} + {item.ip} + {item.open_ports.join(', ') || '-'} + {item.last_seen} + + ))} + + + + ); +}; + +export default TableScanner; \ No newline at end of file diff --git a/FRONT/DEV/src/components/modalwindows/modalContainers/ipConfirm.tsx b/FRONT/DEV/src/components/modalwindows/modalContainers/ipConfirm.tsx index 94dafac..2e2b998 100644 --- a/FRONT/DEV/src/components/modalwindows/modalContainers/ipConfirm.tsx +++ b/FRONT/DEV/src/components/modalwindows/modalContainers/ipConfirm.tsx @@ -110,15 +110,17 @@ export const IpConfirm = ({ ipData, onClose, smoothClose }: Props) => { try { const result = await NetworkService.saveIp(ipAddress, data); if (result.status === 200) { - setIpAddress(ipData.newIp) - - const updatedDevices = devices.map(device => - device.address === ipData.currentIp - ? { ...device, address: ipData.newIp } - : device - ); - useDevicesStore.setState({ devices: updatedDevices }); + if(ipAddress!=='localhost'){ + setIpAddress(ipData.newIp) + const updatedDevices = devices.map(device => + device.address === ipData.currentIp + ? { ...device, address: ipData.newIp } + : device + ); + useDevicesStore.setState({ devices: updatedDevices }); + } + onClose() } } catch (err) { diff --git a/FRONT/DEV/src/components/modalwindows/style.ts b/FRONT/DEV/src/components/modalwindows/style.ts index c8d1c6d..bbbeb26 100644 --- a/FRONT/DEV/src/components/modalwindows/style.ts +++ b/FRONT/DEV/src/components/modalwindows/style.ts @@ -23,7 +23,7 @@ export const ModalContainer = styled.div<{height?: string}>` display: flex; flex-direction: column; justify-content: space-between; - gap: 15px; + gap: 15px; height: ${(props) => props.height ? props.height : '200px'}; width: 520px; ` diff --git a/FRONT/DEV/src/components/register.tsx b/FRONT/DEV/src/components/register.tsx index eec88af..b604928 100644 --- a/FRONT/DEV/src/components/register.tsx +++ b/FRONT/DEV/src/components/register.tsx @@ -7,13 +7,16 @@ import { useIpStore } from "../store" import { NetworkService } from "../api/request" import { IconComponent } from "../const/buttons/icons" import { buttonNames } from "../const/buttons/names" +import ThemeButton from "./UI/buttons/toggletheme" +import About from "./workSpace/modules/appModules/about" const Wrapper = styled.div` display: flex; + flex-direction: column; align-items: center; justify-content: center; height: 100%; - background: ${(props)=> props.theme.background}; + background: ${(props) => props.theme.background}; ` const Container = styled.form` @@ -33,7 +36,7 @@ const Title = styled.div` gap: 15px; align-items: center; text-align: center; - font-size: 25px; + font-size: 20px; ` const Content = styled.div` @@ -49,11 +52,27 @@ const ButtonContainer = styled.div` justify-content: center; ` +const Menu = styled.div` + display: flex; + align-items: center; +` + +const Button = styled.div` + width: 100%; + cursor: pointer; +` + +const IconContainer = styled.div` + padding: 10px; + color: ${props => props.theme.text}; +` + export const CreateUser = () => { - const { ipAddress } = useIpStore(); + const { ipAddress, setIpAddress } = useIpStore(); const [repeatPassword, setRepeatPassword] = useState(''); const [passwordsMatch, setPasswordsMatch] = useState(true); + const [about, setAbout] = useState(false) const [user, setUser] = useState({ username: 'operator', @@ -92,51 +111,85 @@ export const CreateUser = () => { } }; + const back = ()=>{ + setIpAddress('localhost') + } + + const toAbout = ()=>{ + setAbout(true) + } return ( - - - - <IconComponent name={buttonNames[3]} width="52" height="52"></IconComponent> - <h3>Первоначальная настройка приложения</h3> - + <> + {!about ? ( + - - -
-

Перед началом работы необходимо создать пароль оператора приложения

-
- -
- handleChange(id, value)} - /> - - handleChange(id, value)} - style={{ marginTop: '10px' }} - /> - {!passwordsMatch && ( -

- Пароли не совпадают! -

- )} -
- -
- - - - -
-
+ + + <IconComponent name={buttonNames[3]} width="52" height="52"></IconComponent> + <h3>Настройка оператора узла</h3> + {ipAddress != 'localhost' && + <p>{ipAddress}</p> + } + + + + + +
+

Перед началом работы необходимо создать пароль оператора приложения

+
+ +
+ handleChange(id, value)} + /> + + handleChange(id, value)} + style={{ marginTop: '10px' }} + /> + {!passwordsMatch && ( +

+ Пароли не совпадают! +

+ )} +
+ +
+ + + + + + +
+ + + + {ipAddress != 'localhost' && + + } + + + + + ): ( + setAbout(false)} /> + )} + + ) } \ No newline at end of file diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/about.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/about.tsx index 344c733..57207b9 100644 --- a/FRONT/DEV/src/components/workSpace/modules/appModules/about.tsx +++ b/FRONT/DEV/src/components/workSpace/modules/appModules/about.tsx @@ -1,69 +1,213 @@ -import styled from "styled-components" -import { useNavigate } from "react-router-dom" -const Container = styled.div` - height: 100%; +import { MainButton } from "../../../UI/buttons/mainButton" +import { workSpaceNames } from "../../../../const/workspace/names" +import { useWorkSpaceStore } from "../../../../store" +import styled from "styled-components" +import ThemeButton from "../../../UI/buttons/toggletheme" + +type Props = { + from: 'reg' | 'helloWindow' | 'workSpace' + onBack?: () => void +} + +const Wrapper = styled.div` display: flex; flex-direction: column; align-items: center; justify-content: center; - gap: 25px; - text-align: center; - - h1, h2, h3, p{ - margin: 0; - } + height: 100%; + width: 100%; + background: ${(props => props.theme.background)}; + padding: 50px; + box-sizing: border-box; ` const Content = styled.div` - max-width: 450px; + max-width: 1200px; display: flex; flex-direction: column; + justify-content: space-between; + height: 100%; + box-sizing: border-box; + padding: 25px 50px; + background: ${(props => props.theme.secondary)}; + border-radius: 18px; +` + +const Listing = styled.div` + display: flex; + flex-direction: column; + gap: 15px; + +` + +const List = styled.div` + display: flex; + flex-direction: column; + gap: 15px; + margin-left: 10px; +` + +const Title = styled.div` + font-size: 20px; +` + +const SubTitle = styled.div` + display: flex; + flex-direction: column; + gap: 10px; + + p{ + font-size: 18px; + } + +` + +const Links = styled.div` + display: flex; + flex-direction: column; + gap: 20px; +` + +const LinkColumn = styled.div` + display: flex; + flex-direction: column; + gap: 10px; +` + + +const LinkBlock = styled.div` + display: flex; + gap: 5px; +` + +const Copyright = styled.div` + width: 100%; + text-align: center; + margin-top: 50px; +` + +const ButtonContainer = styled.div` + width: 100%; + display: flex; + justifi-content: flex-start; align-items: center; - gap: 100px; - button{ - width: 250px; +` + +const MainTitle = styled.div` + width: 100%; + display: flex; + gap: 15px; + align-items: center; + justify-content: center; + padding-top: 50px; + box-sizing: border-box; + + img{ + width: 40px; + height: 40px; } ` - -const Row = styled.div` - display: flex; - gap: 10px; - align-items: flex-end; - justify-content: center; - margin-left: 50px; -` - -const Column = styled.div` +const Text = styled.div` display: flex; flex-direction: column; - gap: 10px; + gap: 30px; ` -export default function About() { - const navigate = useNavigate() - const redirect = (path: string) => { - navigate(path) +export default function About({ from, onBack }: Props) { + const { setWorkModule } = useWorkSpaceStore() + + const getBackHandler = () => { + switch (from) { + case 'reg': + return onBack + case 'helloWindow': + return () => setWorkModule(workSpaceNames[1]) + case 'workSpace': + return () => setWorkModule(workSpaceNames[3]) + default: + return undefined + } } + const backHandler = getBackHandler() + const showBackButton = from !== 'reg' || (from === 'reg' && onBack) return ( - + - -

GoNetMan

-

v 0.0.1

-
- -

О программе

- GoNetMan - Это приложение представляющее собой панель управления сетевыми настройками в удобном и понятном графическом виде. -
+ + +

О приложении

+ +
-

Copyright © ООО "Гибридные офисные системы"

- + + + + <p>Приложение ГоNets является минимально работоспособным кроссплатформенным продуктом (MVP) для демонстрации нашего подхода к управлению настройками ПК в корпоративной среде.</p> + + + + +

Данное приложение умеет:

+ +

- Отображать в графическом виде активные сетевые ресурсы

+

- Настраивать сетевые параметры локального и удаленных сетевых ресурсов

+

- Отображать и изменять взаимодействие сетевых ресурсов с установленным Клиентом ГоNets

+
+
+ + +

Программное обеспечение исключительно для ознакомления и домашнего использования.

+

Для коммерческого использования приобретите версию ГоNets-PRO (Подробная информация на странице продукта)

+
+ + + +

Ссылка на домашнюю страницу:

+ https://gossys.dev/pages/projects/gonets +
+ + +

Инструкции и информация:

+ https://wiki.gossys.dev/ru/gonets +
+ + +

Распространяется под лицензией GosysPUB текущей версии.

+ +

Полный текст актуальной лицензии:

+ https://wiki.gossys.dev/ru/lic/GosysPUB +
+
+ +
+ + +

Разработано и поддерживается ООО «Гибридные Офисные Системы» 2024-2025 г. Все права защищены.

+
+ +
+ + + + {showBackButton && backHandler && ( + + )} + + {from == 'reg' && + + } +
-
+ + ) } \ No newline at end of file diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/helloWindow.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/helloWindow.tsx index b4b80d9..0a09826 100644 --- a/FRONT/DEV/src/components/workSpace/modules/appModules/helloWindow.tsx +++ b/FRONT/DEV/src/components/workSpace/modules/appModules/helloWindow.tsx @@ -1,81 +1,82 @@ -import styled from "styled-components" -import { MainButton } from "../../../UI/buttons/mainButton" -import { useWorkSpaceStore } from "../../../../store" -import { workSpaceNames } from "../../../../const/workspace/names" -import { buttonNames } from "../../../../const/buttons/names" + import styled from "styled-components" + import { MainButton } from "../../../UI/buttons/mainButton" + import { useWorkSpaceStore } from "../../../../store" + import { workSpaceNames } from "../../../../const/workspace/names" + import { buttonNames } from "../../../../const/buttons/names" -const Wrapper = styled.div` - width: 100%; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - background-color: ${(props => props.theme.background)}; - background-image: url('./assets/images/back1.svg'); - background-size: 100%; - background-repeat: no-repeat; - background-position: bottom; -` + const Wrapper = styled.div` + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + background-color: ${(props => props.theme.background)}; + background-image: url('./assets/images/back1.svg'); + background-size: 100%; + background-repeat: no-repeat; + background-position: bottom; + ` -const Container = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 52px; - margin-bottom: 100px; -` + const Container = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 52px; + margin-bottom: 100px; + ` -const ButtonWrapper = styled.div` - display: flex; - gap: 75px; - flex-direction: column; -` + const ButtonWrapper = styled.div` + display: flex; + gap: 75px; + flex-direction: column; + ` -const ButtonContainer = styled.div` - display: flex; - gap: 15px; - flex-direction: column; -` + const ButtonContainer = styled.div` + display: flex; + gap: 15px; + flex-direction: column; + ` -const Title = styled.div` - display: flex; - align-items: flex-end; - gap: 10px; + const Title = styled.div` + display: flex; + align-items: flex-end; + gap: 10px; + + h1{ + font-size: 52px; + } + ` - h1{ - font-size: 52px; - } -` + export const HelloWindow = () => { -export const HelloWindow = () => { + const { setWorkModule } = useWorkSpaceStore() - const { setWorkModule } = useWorkSpaceStore() - - return ( - - - - <img src="./assets/images/logo.svg" alt="" style={{width: '152px'}}/> - + return ( + + + + <img src="./assets/images/logo.svg" alt="" style={{width: '152px'}}/> + - - - setWorkModule(workSpaceNames[3])} - /> - - - - - - ) -} \ No newline at end of file + + + setWorkModule(workSpaceNames[3])} + /> + setWorkModule(workSpaceNames[4])} + /> + + + + + ) + } \ No newline at end of file diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/ListUsers.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/ListUsers.tsx new file mode 100644 index 0000000..6215b08 --- /dev/null +++ b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/ListUsers.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import styled from 'styled-components'; +import DeviceCard from "../../../../../UI/Dropdown/DeviceCard"; +import {DeviceUser} from "../../../../../../store/useDeviceStore"; + +const ScanBtn = styled.button` + position: relative; + padding: 14px 22px; + background-color: #4CAF50; + color: white; + border: none; + border-radius: 12px; + font-size: 18px; + transition: .4s ease; + &:hover{ + transform: scale(1.03); + } +` + +interface ListUsersProps { + loading: boolean; + handleScan: ()=>void; + devices: DeviceUser[]; + scanInitiated: boolean; +} + +const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps) => { + + // Фильтр по статусу + const activeDevices = devices.filter(device => device.status === 'online'); + const inactiveDevices = devices.filter(device => device.status !== 'online'); + + return ( + <> + + {devices.length > 0 ? 'Обновить список' : 'Сканировать сеть'} + + + {devices.length > 0 ? ( +
+ {/* Колонка активных устройств */} +
+

Активные устройства ({activeDevices.length})

+ {activeDevices.map((device, index) => ( + + ))} +
+ {/* Колонка неактивных устройств */} +
+

Неактивные устройства ({inactiveDevices.length})

+ {inactiveDevices.map((device, index) => ( + + ))} +
+
+ ) : scanInitiated ? ( +

Устройства не найдены

+ ) : null} + + ); +}; + +export default ListUsers; \ No newline at end of file diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/TableUsers.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/TableUsers.tsx new file mode 100644 index 0000000..ebe71a9 --- /dev/null +++ b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/modules/TableUsers.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import styled from 'styled-components'; +import DeviceCard from "../../../../../UI/Dropdown/DeviceCard"; +import {DeviceUser} from "../../../../../../store/useDeviceStore"; +import TableScanner from "../../../../../UI/tables/TableScanner"; + +const ScanBtn = styled.button` + position: relative; + padding: 14px 22px; + background-color: #4CAF50; + color: white; + border: none; + border-radius: 12px; + font-size: 18px; + transition: .4s ease; + &:hover{ + transform: scale(1.03); + } +` + +interface ListUsersProps { + loading: boolean; + handleScan: ()=>void; + devices: DeviceUser[]; + scanInitiated: boolean; +} + +const ListUsers = ({loading, handleScan, devices, scanInitiated}: ListUsersProps) => { + + return ( + <> + + {devices.length > 0 ? 'Обновить список' : 'Сканировать сеть'} + + + {devices.length > 0 ? ( + + ) : scanInitiated ? ( +

Устройства не найдены

+ ) : null} + + ); +}; + +export default ListUsers; \ No newline at end of file diff --git a/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/userScanner.tsx b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/userScanner.tsx new file mode 100644 index 0000000..84fc936 --- /dev/null +++ b/FRONT/DEV/src/components/workSpace/modules/appModules/usersScanner/userScanner.tsx @@ -0,0 +1,185 @@ +import { useIpStore } from "../../../../../store"; +import styled, { keyframes } from "styled-components"; +import { useState } from "react"; +import { NetworkService } from "../../../../../api/request"; +import ListUsers from "./modules/ListUsers"; +import TableUsers from "./modules/TableUsers"; +import DropdownType from "../../../../UI/Dropdown/DropdownType"; +import useNetworkStore from "../../../../../store/useDeviceStore"; + +type Option ={ + value: number, + label: string, +} + +// Анимации +const ring1 = keyframes` + 0% { width: 250px; height: 250px; } + 50% { width: 280px; height: 280px; } + 100% { width: 250px; height: 250px; } +`; + +const ring2 = keyframes` + 0% { width: 400px; height: 400px; } + 50% { width: 430px; height: 430px; } + 100% { width: 400px; height: 400px; } +`; + +const ring3 = keyframes` + 0% { width: 550px; height: 550px; } + 50% { width: 580px; height: 580px; } + 100% { width: 550px; height: 550px; } +`; +const dotAnimation = keyframes` + 0% { content: '.'; } + 33% { content: '..'; } + 66% { content: '...'; } + 100% { content: '.'; } +`; + +// Стили +const Wrapper = styled.div<{ scanInitiated: boolean, loading: boolean}>` + display: flex; + flex-direction: column; + align-items: center; + justify-content:${({scanInitiated, loading})=>(scanInitiated && !loading ? 'start' : 'center')}; + height: 100%; + width: 100%; + background: ${(props) => props.theme.background}; + padding: 50px; + box-sizing: border-box; + gap: 24px; + transition: justify-content .4s ease; + overflow-y: auto; + overflow-x: hidden; + position: relative; +`; +const TitleWrapper = styled.h2` + position: absolute; + top: 20px; + left: 50%; + transform: translateX(-50%); + font-size: 32px; +` + +const UserLocate = styled.div<{ loading: boolean, scanInitiated: boolean }>` + width: 60px; + height: 60px; + border-radius: 50%; + position: relative; + transition: .4s ease; + transform: ${({scanInitiated,loading})=>(scanInitiated && !loading ? 'scale(0)' : 'scale(1)')}; + span { + position: absolute; + display: inline-block; + background: ${(props) => props.theme.accent}; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: 50%; + &.center{ + width: 60px; + height: 60px; + + } + &.ring1 { + opacity: 0.12; + width: 250px; + height: 250px; + animation: ${({ loading }) => (loading ? ring1 : "none")} 4s linear infinite; + } + + &.ring2 { + opacity: 0.08; + width: 400px; + height: 400px; + animation: ${({ loading }) => (loading ? ring2 : "none")} 4s linear infinite; + } + + &.ring3 { + opacity: 0.04; + width: 550px; + height: 550px; + animation: ${({ loading }) => (loading ? ring3 : "none")} 4s linear infinite; + } + } + +`; + +const LoadingText = styled.div` + position: relative; + padding: 14px 22px; + background-color: #4CAF50; + color: white; + border: none; + border-radius: 12px; + font-size: 18px; +`; +const ScanningText = styled.span` + &::after { + content: '.'; + animation: ${dotAnimation} 1.5s infinite; + } +`; + + +export default function UserScanner() { + const { ipAddress } = useIpStore(); + const [loading, setLoading] = useState(false); + const { devices, setDevices } = useNetworkStore(); + const [scanInitiated, setScanInitiated] = useState(devices.length > 0); + + // Сканирование + const handleScan = async () => { + try { + setLoading(true); + setScanInitiated(true); + const res = await NetworkService.scanUser(ipAddress); + setDevices(res.data.devices || []); + console.log(res.data.devices); + } catch (err) { + console.error("Ошибка сканирования сети:", err); + setDevices([]) + } finally { + setLoading(false); + } + }; + + // Способ отображения + const viewOptions: Option[] = [ + { value: 1, label: 'Таблица' }, + { value: 2, label: 'Список' }, + ]; + const [selectedView, setSelectedView] = useState