default-mobile.handlebars 485 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783
  1. <!DOCTYPE html>
  2. <html lang="en" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  6. <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
  7. <meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0" />
  8. <meta name="format-detection" content="telephone=no" />
  9. <meta name="robots" content="noindex,nofollow">
  10. <link rel="manifest" href="{{{domainurl}}}manifest.json">
  11. <link rel="shortcut icon" href="{{{domainurl}}}favicon.ico" />
  12. <link rel="icon" type="image/png" sizes="16x16" href="{{{domainurl}}}favicon-16x16.png">
  13. <link rel="icon" type="image/png" sizes="32x32" href="{{{domainurl}}}favicon-32x32.png">
  14. <link rel="apple-touch-icon" href="/favicon-303x303.png" />
  15. <link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
  16. {{{customCSSTags}}}
  17. <meta name="apple-mobile-web-app-capable" content="yes">
  18. <meta name="apple-mobile-web-app-status-bar-style" content="#ffffff">
  19. <meta name="apple-mobile-web-app-title" content="{{{title}}}">
  20. <script type="text/javascript" src="scripts/common-0.0.1{{{min}}}.js"></script>
  21. <script type="text/javascript" src="scripts/meshcentral{{{min}}}.js"></script>
  22. <script type="text/javascript" src="scripts/agent-redir-ws-0.1.1{{{min}}}.js"></script>
  23. <script type="text/javascript" src="scripts/agent-desktop-0.0.2{{{min}}}.js"></script>
  24. <script type="text/javascript" src="scripts/amt-0.2.0{{{min}}}.js"></script>
  25. <script type="text/javascript" src="scripts/amt-redir-ws-0.1.0{{{min}}}.js"></script>
  26. <script type="text/javascript" src="scripts/amt-desktop-0.0.2{{{min}}}.js"></script>
  27. <script type="text/javascript" src="scripts/xterm{{{min}}}.js"></script>
  28. <script type="text/javascript" src="scripts/xterm-addon-fit{{{min}}}.js"></script>
  29. <script type="text/javascript" src="scripts/zlib{{{min}}}.js"></script>
  30. <script type="text/javascript" src="scripts/zlib-inflate{{{min}}}.js"></script>
  31. <script type="text/javascript" src="scripts/zlib-adler32{{{min}}}.js"></script>
  32. <script type="text/javascript" src="scripts/zlib-crc32{{{min}}}.js"></script>
  33. <script keeplink=1 type="text/javascript" src="scripts/filesaver.min.js"></script>
  34. {{{customJSTags}}}
  35. <meta name="msapplication-TileColor" content="#00aba9">
  36. <meta name="theme-color" content="#ffffff">
  37. <title>{{{title}}}</title>
  38. <style>
  39. body {
  40. background-color: white;
  41. }
  42. .night body {
  43. background-color: black;
  44. }
  45. #MxMESH {
  46. color: black;
  47. }
  48. .night #MxMESH {
  49. color: lightgray;
  50. }
  51. .textOverGray { color: black; }
  52. #dialog {
  53. z-index:1000;
  54. background-color:#EEE;
  55. box-shadow:0px 0px 15px #666;
  56. font-family:Arial,Helvetica,sans-serif;
  57. border-radius:5px;
  58. position:fixed;
  59. top:90px;
  60. width:300px;
  61. }
  62. .night #dialog {
  63. color: black;
  64. background-color:#AAA;
  65. }
  66. :focus {
  67. outline: 0;
  68. }
  69. a {
  70. color: #036;
  71. text-decoration: underline;
  72. }
  73. .night a {
  74. color: #99F;
  75. }
  76. #footer a {
  77. color: #fff;
  78. text-decoration: underline;
  79. }
  80. #footer a:hover {
  81. text-decoration: none;
  82. }
  83. .night #footer {
  84. color: gray;
  85. }
  86. .i1 {
  87. background: url(../images/icons50.png) 0px 0px;
  88. background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
  89. height: 50px;
  90. width: 50px;
  91. border: none;
  92. }
  93. .i2 {
  94. background: url(../images/icons50.png) -50px 0px;
  95. background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
  96. height: 50px;
  97. width: 50px;
  98. border: none;
  99. }
  100. .i3 {
  101. background: url(../images/icons50.png) -100px 0px;
  102. background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
  103. height: 50px;
  104. width: 50px;
  105. border: none;
  106. }
  107. .i4 {
  108. background: url(../images/icons50.png) -150px 0px;
  109. background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
  110. height: 50px;
  111. width: 50px;
  112. border: none;
  113. }
  114. .i5 {
  115. background: url(../images/icons50.png) -200px 0px;
  116. background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
  117. height: 50px;
  118. width: 50px;
  119. border: none;
  120. }
  121. .i6 {
  122. background: url(../images/icons50.png) -250px 0px;
  123. background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
  124. height: 50px;
  125. width: 50px;
  126. border: none;
  127. }
  128. .i7 {
  129. background: url(../images/icons50.png) -300px 0px;
  130. background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
  131. height: 50px;
  132. width: 50px;
  133. border: none;
  134. }
  135. .i8 {
  136. background: url(../images/icons50.png) -350px 0px;
  137. background-image: image-set(url(../images/icons50.png) 1x, url(../images/icons100.png) 2x);
  138. height: 50px;
  139. width: 50px;
  140. border: none;
  141. }
  142. .m0 {
  143. background: url(../images/images16.png) -32px 0px;
  144. height: 16px;
  145. width: 16px;
  146. border: none;
  147. float: left;
  148. }
  149. .m1 {
  150. background: url(../images/images16.png) -16px 0px;
  151. height: 16px;
  152. width: 16px;
  153. border: none;
  154. float: left;
  155. }
  156. .m2 {
  157. background: url(../images/images16.png) -96px 0px;
  158. height: 16px;
  159. width: 16px;
  160. border: none;
  161. float: left;
  162. }
  163. .m3 {
  164. background: url(../images/images16.png) -112px 0px;
  165. height: 16px;
  166. width: 16px;
  167. border: none;
  168. float: left;
  169. }
  170. .m4 {
  171. background: url(../images/images16.png) -128px 0px;
  172. height: 16px;
  173. width: 16px;
  174. border: none;
  175. float: left;
  176. }
  177. .NotifyIconSmall1 { width:24px; height:24px; background: url(../images/notify24.png) 0px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
  178. .NotifyIconSmall2 { width:24px; height:24px; background: url(../images/notify24.png) -24px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
  179. .NotifyIconSmall3 { width:24px; height:24px; background: url(../images/notify24.png) -48px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
  180. .NotifyIconSmall4 { width:24px; height:24px; background: url(../images/notify24.png) -72px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
  181. .NotifyIconSmall5 { width:24px; height:24px; background: url(../images/notify24.png) -96px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
  182. .NotifyIconSmall6 { width:24px; height:24px; background: url(../images/notify24.png) -120px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
  183. .NotifyIconSmall7 { width:24px; height:24px; background: url(../images/notify24.png) -144px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
  184. .NotifyIconSmall8 { width:24px; height:24px; background: url(../images/notify24.png) -168px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
  185. .NotifyIconSmall9 { width:24px; height:24px; background: url(../images/notify24.png) -192px 0px; background-image: image-set(url(../images/notify24.png) 1x, url(../images/notify48.png) 2x); }
  186. .gray {
  187. /*filter: url("data:image/svg+xml;utf8,&lt;svg xmlns=\'http://www.w3.org/2000/svg\'&gt;&lt;filter id=\'grayscale\'&gt;&lt;feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/&gt;&lt;/filter&gt;&lt;/svg&gt;#grayscale");*/ /* Firefox 10+, Firefox on Android */
  188. filter: gray; /* IE6-9 */
  189. -webkit-filter: grayscale(100%) opacity(60%); /* Chrome 19+, Safari 6+, Safari 6+ iOS */
  190. }
  191. .DevSt {
  192. padding-left: 5px;
  193. border-bottom-style: solid;
  194. border-bottom-width: 1px;
  195. border-bottom-color: #DDDDDD;
  196. }
  197. .noselect {
  198. -webkit-touch-callout: none;
  199. -webkit-user-select: none;
  200. -khtml-user-select: none;
  201. -moz-user-select: none;
  202. -ms-user-select: none;
  203. user-select: none;
  204. }
  205. .fileIcon1 {
  206. background: url(data:image/gif;base64,R0lGODlhEAAQAJEDAPb49Y2Sj9LT2f///yH5BAEAAAMALAAAAAAQABAAAAImnI+py+1vhJwyUYAzHTL4D3qdlJWaIFJqmKod607sDKIiDUP63hQAOw==);
  207. height: 16px;
  208. width: 16px;
  209. cursor: pointer;
  210. border: none;
  211. float: left;
  212. margin-top: 1px;
  213. }
  214. .fileIcon2 {
  215. background: url(data:image/gif;base64,R0lGODlhEAAQAJEDAM2xV/Xur+XPgP///yH5BAEAAAMALAAAAAAQABAAAAJD3ISZIGHWUGihznesYDYATFVM+D2hJ4lgN1olxALAtAlmPCJvuMmJd6PJckDYwicrHhTD5o7plJmg0Uc0asNMkphHAQA7);
  216. height: 16px;
  217. width: 16px;
  218. cursor: pointer;
  219. border: none;
  220. float: left;
  221. margin-top: 1px;
  222. }
  223. .fileIcon3 {
  224. background: url(data:image/gif;base64,R0lGODlhEAAQAJEDAPb19IGBgbq6uv///yH5BAEAAAMALAAAAAAQABAAAAIy3ISpxgcPH2ouQgFEw1YmxnUXKEaaEZZnVWZk66JwzKpvuwZzwOgwb/C1gIOA8Yg8DgoAOw==);
  225. height: 16px;
  226. width: 16px;
  227. cursor: pointer;
  228. border: none;
  229. float: left;
  230. margin-top: 1px;
  231. }
  232. .fileIcon4 {
  233. background: url(../images/meshicon16.png);
  234. height: 16px;
  235. width: 16px;
  236. cursor: pointer;
  237. border: none;
  238. float: left;
  239. margin-top: 1px;
  240. }
  241. .filelist {
  242. -moz-user-select: none;
  243. -khtml-user-select: none;
  244. -webkit-user-select: none;
  245. -o-user-select: none;
  246. cursor: default;
  247. -khtml-user-drag: element;
  248. clear: both;
  249. }
  250. .deviceNotifyDot {
  251. position:absolute;
  252. right:10px;
  253. top:0px;
  254. height:16px;
  255. }
  256. .deviceNotifyDotSub {
  257. text-align:center;
  258. color:#FFF;
  259. width:16px;
  260. background-color:#00F;
  261. padding:2px;
  262. border-radius:10px;
  263. box-shadow: 2px 2px 10px black;
  264. cursor:pointer;
  265. margin-left:3px;
  266. float:left;
  267. }
  268. .deviceNotifyDotSub:hover {
  269. background-color:#44F;
  270. }
  271. .deviceNotifySmallDot {
  272. position:absolute;
  273. right:10px;
  274. top:0px;
  275. height:10px;
  276. }
  277. .deviceNotifySmallDotSub {
  278. text-align:center;
  279. color:#FFF;
  280. width:10px;
  281. padding:2px;
  282. background-color:#00F;
  283. border-radius:10px;
  284. box-shadow: 2px 2px 10px black;
  285. cursor:pointer;
  286. margin-left:2px;
  287. float:left;
  288. }
  289. .deviceNotifySmallDotSub:hover {
  290. background-color:#44F;
  291. }
  292. .deviceNotifyLargeDot {
  293. position:absolute;
  294. right:10px;
  295. top:10px;
  296. height:40px;
  297. }
  298. .deviceNotifyLargeDotSub {
  299. text-align:center;
  300. width:35px;
  301. height:35px;
  302. color:#FFF;
  303. padding:2px;
  304. background-color:#00F;
  305. border-radius:20px;
  306. box-shadow: 2px 2px 10px black;
  307. cursor:pointer;
  308. margin-left:4px;
  309. font-size:30px;
  310. float:left;
  311. }
  312. .deviceNotifyLargeDotSub:hover {
  313. background-color:#44F;
  314. }
  315. .style10 {
  316. background-color: #C9C9C9;
  317. color: #000;
  318. }
  319. .night .style10 {
  320. background-color: #888;
  321. }
  322. .deviceBatteryLarge {
  323. position:absolute;
  324. right:10px;
  325. top:0px;
  326. width:28px;
  327. height:48px;
  328. border:none;
  329. box-shadow:none;
  330. }
  331. .deviceBatteryLarge1 { background: url(../images/batteries48.png) 0px 0px; }
  332. .deviceBatteryLarge2 { background: url(../images/batteries48.png) -28px 0px; }
  333. .deviceBatteryLarge3 { background: url(../images/batteries48.png) -56px 0px; }
  334. .deviceBatteryLarge4 { background: url(../images/batteries48.png) -84px 0px; }
  335. .deviceBatteryLarge5 { background: url(../images/batteries48.png) -112px 0px; }
  336. .deviceBatteryLarge6 { background: url(../images/batteries48.png) -140px 0px; }
  337. .deviceBatteryLarge7 { background: url(../images/batteries48.png) -168px 0px; }
  338. .deviceBatteryLarge8 { background: url(../images/batteries48.png) -196px 0px; }
  339. .deviceBatteryLarge9 { background: url(../images/batteries48.png) -224px 0px; }
  340. .deviceBatteryLarge10 { background: url(../images/batteries48.png) -252px 0px; }
  341. .deviceBatteryLarge11 { background: url(../images/batteries48.png) -280px 0px; }
  342. .deviceBatterySmall {
  343. position:absolute;
  344. left:6px;
  345. top:22px;
  346. width:14px;
  347. height:24px;
  348. border:none;
  349. box-shadow:none;
  350. }
  351. .deviceBatterySmall1 { background: url(../images/batteries24.png) 0px 0px; }
  352. .deviceBatterySmall2 { background: url(../images/batteries24.png) -14px 0px; }
  353. .deviceBatterySmall3 { background: url(../images/batteries24.png) -28px 0px; }
  354. .deviceBatterySmall4 { background: url(../images/batteries24.png) -42px 0px; }
  355. .deviceBatterySmall5 { background: url(../images/batteries24.png) -56px 0px; }
  356. .deviceBatterySmall6 { background: url(../images/batteries24.png) -70px 0px; }
  357. .deviceBatterySmall7 { background: url(../images/batteries24.png) -84px 0px; }
  358. .deviceBatterySmall8 { background: url(../images/batteries24.png) -98px 0px; }
  359. .deviceBatterySmall9 { background: url(../images/batteries24.png) -112px 0px; }
  360. .deviceBatterySmall10 { background: url(../images/batteries24.png) -126px 0px; }
  361. .deviceBatterySmall11 { background: url(../images/batteries24.png) -140px 0px; }
  362. .meshList {
  363. width:auto;
  364. height:40px;
  365. background-color:lightgray;
  366. margin-top:5px;
  367. margin-bottom:5px;
  368. margin-left:60px;
  369. padding-top:5px;
  370. padding-bottom:5px;
  371. border-radius:8px 0px 0px 8px;
  372. }
  373. .night .meshList {
  374. background-color: gray;
  375. }
  376. .devList1 {
  377. height: 50px;
  378. cursor: pointer;
  379. position: relative;
  380. margin-top: 5px;
  381. margin-bottom: 5px;
  382. }
  383. .devList2 {
  384. float: left;
  385. margin-left: 4px
  386. }
  387. .devList3 {
  388. width: auto;
  389. height: 40px;
  390. background-color: lightgray;
  391. margin-left: 60px;
  392. padding-top: 5px;
  393. padding-bottom: 5px;
  394. border-radius: 8px 0px 0px 8px;
  395. }
  396. .night .devList3 {
  397. background-color: gray;
  398. }
  399. .devList4 {
  400. padding-left: 12px;
  401. padding-top: 2px;
  402. color: black;
  403. }
  404. .devList5 {
  405. padding-left: 12px;
  406. padding-top: 3px;
  407. color: #444
  408. }
  409. .night .devList5 {
  410. color: black;
  411. }
  412. .deskButton {
  413. box-shadow: 0px 0px 10px #000;
  414. border-radius:20px;
  415. position:absolute;
  416. right:10px;
  417. top:10px;
  418. cursor:pointer;
  419. background-color:#AAA;
  420. z-index:1000;
  421. }
  422. .menuButton{
  423. box-shadow: 0px 0px 10px #000;
  424. border-radius:10px;
  425. display:inline-block;
  426. width:120px;
  427. background-color:#AAA;
  428. text-align:center;
  429. padding:8px;
  430. cursor:pointer;
  431. margin:10px;
  432. z-index:1000;
  433. }
  434. #notificationCount {
  435. min-width: 28px;
  436. font-size: 20px;
  437. background-color: orange;
  438. text-align: center;
  439. cursor: pointer;
  440. color: black;
  441. }
  442. .notifiyBox {
  443. font-size: 16px;
  444. position: absolute;
  445. z-index: 1000;
  446. top: 60px;
  447. right: 76px;
  448. width: 300px;
  449. text-align: left;
  450. background-color: #F0ECCD;
  451. border: 4px solid #666;
  452. -webkit-border-radius: 10px;
  453. -moz-border-radius: 10px;
  454. border-radius: 10px;
  455. -webkit-box-shadow: 2px 2px 4px #888;
  456. -moz-box-shadow: 2px 2px 4px #888;
  457. box-shadow: 2px 2px 4px #888;
  458. max-height: 200px;
  459. }
  460. .night .notifiyBox {
  461. color: black;
  462. }
  463. .notifiyBox:before {
  464. content: ' ';
  465. position: absolute;
  466. width: 0;
  467. height: 0;
  468. right: 5px;
  469. top: -30px;
  470. border: 15px solid;
  471. border-color: transparent #666 #666 transparent;
  472. }
  473. .notifiyBox:after {
  474. content: ' ';
  475. position: absolute;
  476. width: 0;
  477. height: 0;
  478. right: 7px;
  479. top: -24px;
  480. border: 12px solid;
  481. border-color: transparent #F0ECCD #F0ECCD transparent;
  482. }
  483. #p15statetext {
  484. padding: 4px;
  485. height: 15px;
  486. }
  487. #p15agentConsole {
  488. background: black;
  489. margin: 0;
  490. padding: 0;
  491. color: lightgray;
  492. width: 100%;
  493. position: relative;
  494. }
  495. #p15coreName {
  496. padding: 4px;
  497. display: inline-block;
  498. }
  499. #p15agentConsoleText {
  500. position:absolute;
  501. margin: 0;
  502. padding: 0;
  503. top: 0;
  504. bottom: 0;
  505. left:0;
  506. right: 0;
  507. overflow-y: scroll;
  508. overflow-x: auto;
  509. }
  510. .areaHead {
  511. padding-top: 2px;
  512. padding-bottom: 2px;
  513. background: #C0C0C0;
  514. }
  515. .night .areaHead {
  516. color: #CCC;
  517. background: #333;
  518. }
  519. .areaFoot {
  520. padding-top: 2px;
  521. padding-bottom: 2px;
  522. background: #C0C0C0;
  523. }
  524. .night .areaFoot {
  525. color: #CCC;
  526. background: #333;
  527. }
  528. .toright2 {
  529. float: right;
  530. text-align: right;
  531. }
  532. #consoleTable {
  533. width: 100%;
  534. height: 100%;
  535. padding: 0px;
  536. margin-top: 0px;
  537. }
  538. .night #consoleTable {
  539. color: black;
  540. }
  541. .menucurve {
  542. background-color:white;
  543. width:10px;
  544. height:10px;
  545. border-radius:10px 0 0 0;
  546. border-right:1px solid white;
  547. border-bottom:1px solid white;
  548. }
  549. .night .menucurve {
  550. background-color:black;
  551. border-right:1px solid black;
  552. border-bottom:1px solid black;
  553. }
  554. #termTable {
  555. width: 100%;
  556. padding: 0px;
  557. margin-top: 0px;
  558. }
  559. .fulldesk #termTable {
  560. position: absolute;
  561. top: 0;
  562. bottom: 0;
  563. left: 0;
  564. right: 0;
  565. }
  566. #termarea3x {
  567. background: black;
  568. text-align: center;
  569. height: 400px;
  570. position: relative;
  571. }
  572. .viewSelector10 {
  573. margin-left: 2px;
  574. margin-top: 2px;
  575. background: url(../images/views.png) -476px 0px;
  576. height: 28px;
  577. width: 28px;
  578. }
  579. .viewSelector11 {
  580. margin-left: 2px;
  581. margin-top: 2px;
  582. background: url(../images/views.png) -504px 0px;
  583. height: 28px;
  584. width: 28px;
  585. }
  586. .tagSpan {
  587. background-color: lightgray;
  588. padding: 3px;
  589. border-radius: 5px;
  590. }
  591. .night .tagSpan {
  592. color: black;
  593. }
  594. #d3serveraction, #d2serveraction {
  595. width: 100%;
  596. background-color: #d3d9d6;
  597. text-align: left;
  598. padding: 3px;
  599. }
  600. #d3serverfiles, #d2serverfiles {
  601. width: 100%;
  602. height: 150px;
  603. background-color: white;
  604. padding: 2px;
  605. border: 1px solid gray;
  606. overflow-y: scroll;
  607. }
  608. </style>
  609. </head>
  610. <body id="body" onload="if (typeof(startup) !== 'undefined') startup();" style="overflow-y:hidden;margin:0;padding:0;border:0;font-size:13px;font-family:\'Trebuchet MS\', Arial, Helvetica, sans-serif">
  611. <div id=container>
  612. <div id="notifiyBox" class="notifiyBox" style="display:none"></div>
  613. <div id=mastheadx></div>
  614. <div id=masthead style="background:url(logo.png) 0px 0px;background-size:341px 50px;background-color:#036;background-repeat:no-repeat;height:50px;width:100%;overflow:hidden">
  615. <div style="width:calc(100% - 50px);overflow:hidden">
  616. <div style="float:left;height:66px;color:#c8c8c8;padding-left:10px;padding-top:6px" onclick="go(2)">
  617. <strong><font style="font-size:36px;font-family:Arial,Helvetica,sans-serif;text-shadow: 1px 1px 2px #000;">{{{title1}}}</font></strong>
  618. </div>
  619. <div style="float:left;height:66px;color:#c8c8c8;padding-left:5px;padding-top:10px">
  620. <strong><font style="font-size:12px;font-family:Arial,Helvetica,sans-serif;text-shadow: 1px 1px 2px #000;">{{{title2}}}</font></strong>
  621. </div>
  622. </div>
  623. <div id="devViewPageState" class=noselect style="position:absolute;right:160px;top:10px;height:30px;line-height:30px;color:#c8c8c8;font-size:16px;display:none"></div>
  624. <img id="devViewPageButton2" class=noselect style="position:absolute;right:130px;top:10px;cursor:pointer;display:none" onclick=onDeviceViewPageChange(2) src="/images/left-30.png" width=20 height=30 />
  625. <img id="devViewPageButton3" class=noselect style="position:absolute;right:100px;top:10px;cursor:pointer;display:none" onclick=onDeviceViewPageChange(3) src="/images/right-30.png" width=20 height=30 />
  626. <div id=notificationCount onclick="clickNotificationIcon()" class="unselectable" style="position:absolute;right:50px;top:0px;font-size:28px;width:50px;height:50px;cursor:pointer;display:none" title="Click to view current notifications"><div id="notificationCount2" style="padding-top:8px">0</div></div>
  627. <img id="topMenuIcon" class=noselect style="position:absolute;right:0;top:10px;color:#c8c8c8;font-size:44px;margin-right:8px;cursor:pointer;display:none" onclick=topMenu() src="/images/3bars-30.png" width=30 height=30 />
  628. </div>
  629. <div id=page_content style="position:absolute;bottom:32px;top:50px;width:100%">
  630. <div id=column_l style="width:100%;padding:0;position:absolute;bottom:0px;top:0px">
  631. <div id=p0 style=display:none;width:100%;height:100%>
  632. <div style="display:flex;align-items:center;width:100%;height:100%">
  633. <div id=p0message style=text-align:center;width:100%><span id="p0span">Server disconnected</span>, <href onclick=reload() style=cursor:pointer><u>click to reconnect</u></href>.</div>
  634. </div>
  635. </div>
  636. <div id=p1 style=display:none;width:100%;height:100%>
  637. <div style="display:flex;align-items:center;width:100%;height:100%">
  638. <div id=p1message style=text-align:center;width:100%></div>
  639. </div>
  640. </div>
  641. <div id=p2 style="display:none;position:absolute;top:0;left:0;right:0;bottom:0">
  642. <div id=xdevices style="position:absolute;overflow-y:auto;top:0;left:0;right:0;bottom:34px" onscroll="onDevicesScroll()" ontouchstart="onDeviceTouch(true)" ontouchend="onDeviceTouch(false)"></div>
  643. <div id=xdevicesBar style="position:absolute;overflow-y:auto;height:34px;left:0;right:0;bottom:0px;background-color:#aaa;color:black">
  644. <div style="margin:4px">
  645. <span style="width:20px;display:inline-block;text-align:center;cursor:pointer;font-size:16px" onclick=clearSearchInput()><b>X</b></span>
  646. <input id=SearchInput autocomplete=off type=search placeholder=Filter onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) style="padding:2px;margin:0;height:20px;background-color:#FFF" />&nbsp;
  647. <label class=noselect><input type=checkbox id=RealNameCheckBox onclick=onRealNameCheckBox() />OS Name</label>
  648. <label class=noselect><input type=checkbox id=OnlineCheckBox onclick=onOnlineCheckBox(event) />Online</label>
  649. </div>
  650. </div>
  651. </div>
  652. <div id=p3 style=display:none;position:absolute;bottom:0;top:0;width:100%>
  653. <table cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0">
  654. <tr style=padding:0>
  655. <td style="padding:0;color:#c8c8c8;text-align:center;cursor:pointer" width=60px valign=top onclick=goBack()>
  656. <div style="padding:0;background-color:#036;width:10px;height:10px;float:right;border:0">
  657. <div class="menucurve"></div>
  658. </div>
  659. <div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">&#9664;</div>
  660. </td>
  661. <td>
  662. <div style=margin-left:5px>
  663. <strong style="font-size:large"><span id=p3userName></span></strong><br />
  664. </div>
  665. </td>
  666. </tr>
  667. </table>
  668. <div id=p3info style="overflow-y:auto;position:absolute;top:55px;bottom:0px;width:100%">
  669. <img id="p2AccountImage" alt="" width="128" height="128" onclick="account_manageImage(0)" src="images/user-256.png" style="position:absolute;right:8px;top:7px;border-radius:8px;box-shadow:0px 0px 7px #000" />
  670. <div style="margin-left:8px">
  671. <div id="p3AccountActions">
  672. <div id="p2AccountSecurity" style="display:none">
  673. <p><strong>Account Security</strong></p>
  674. <div style="margin-left:9px;margin-bottom:8px">
  675. <div id="managePhoneNumber1" style="margin-top:5px;display:none"><a onclick="account_managePhone()" style="cursor:pointer">Manage phone number</a> <span id="authPhoneNumberCheck"><strong>&#x2713;</strong></span></div>
  676. <div id="manageEmail2FA" style="margin-top:5px;display:none"><a onclick="account_manageAuthEmail()" style="cursor:pointer">Manage email authentication</a> <span id="authEmailSetupCheck"><strong>&#x2713;</strong></span></div>
  677. <div style="margin-top:5px"><a href=# onclick="account_showLocalizationSettings()">Localization Settings</a></div>
  678. <div id="manageAuthApp" style="margin-top:5px;display:none"><a onclick="account_manageAuthApp()" style="cursor:pointer">Manage authenticator app</a> <span id="authAppSetupCheck"><strong>&#x2713;</strong></span></div>
  679. <div id="manageOtp" style="margin-top:5px;display:none"><a onclick="account_manageOtp(0)" style="cursor:pointer">Manage backup codes</a> <span id="authCodesSetupCheck"><strong>&#x2713;</strong></span></div>
  680. </div>
  681. </div>
  682. <div id="p2AccountActions" style="display:none">
  683. <p><strong>Account Actions</strong></p>
  684. <div style="margin-left:9px;margin-bottom:8px">
  685. <div style="margin-top:5px"><span id="viewPreviousLogins"><a onclick="return account_viewPreviousLogins()" style="cursor:pointer">View previous logins</a></span></div>
  686. <div style="margin-top:5px"><span id="managePhoneNumber2" style="display:none"><a onclick="account_managePhone()" style="cursor:pointer">Manage phone number</a></span></div>
  687. <div style="margin-top:5px"><span id="verifyEmailId" style="display:none"><a onclick="account_showVerifyEmail()" style="cursor:pointer">Verify email</a></span></div>
  688. <span id="p2AccountPassActions">
  689. <div style="margin-top:5px"><span id="changeEmailId" style="display:none"><a onclick="account_showChangeEmail()" style="cursor:pointer">Change email address</a></span></div>
  690. <div style="margin-top:5px"><a onclick="account_showChangePassword()" style="cursor:pointer">Change password</a><span id="p2nextPasswordUpdateTime"></span></div>
  691. <div style="margin-top:5px"><a onclick="account_showDeleteAccount()" style="cursor:pointer">Delete account</a></div>
  692. </span>
  693. <div style="margin-top:5px" id="setDarkModeLink"><a onclick="toggleNightMode()" style="cursor:pointer">Set dark mode</a></div>
  694. <div style="margin-top:5px"><a onclick="showNotes(false)" style="cursor:pointer">Personal notes</a></div>
  695. </div>
  696. <br style=clear:both />
  697. </div>
  698. </div>
  699. <strong>Device Groups</strong>
  700. <span id="p3createMeshLink1">( <a onclick=account_createMesh() style=cursor:pointer><img src="images/icon-addnew.png" width=12 height=12 border=0 /> New</a> )</span>
  701. <br /><br />
  702. <div id=p3meshes></div>
  703. <div id=p3noMeshFound style=margin-left:9px;display:none>No device groups.<span id="p3createMeshLink2"> <a onclick=account_createMesh() style=cursor:pointer><strong>Get started here!</strong></a></span></div>
  704. <br style=clear:both />
  705. </div>
  706. </div>
  707. </div>
  708. <div id=p5 style=display:none>
  709. <table cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0;">
  710. <tr style=padding:0>
  711. <td style="padding:0;color:#c8c8c8;text-align:center;cursor:pointer" width=60px valign=top onclick=goBack()>
  712. <div style="padding:0;background-color:#036;width:10px;height:10px;float:right;border:0">
  713. <div class="menucurve"></div>
  714. </div>
  715. <div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">&#9664;</div>
  716. </td>
  717. <td>
  718. <img src="/images/user-50.png" width=50 height=50 />
  719. </td>
  720. <td>
  721. <div style=margin-left:5px>
  722. <strong style="font-size:large">My Files</strong><br />
  723. </div>
  724. </td>
  725. </tr>
  726. </table>
  727. <div id=p5myfiles style="position:absolute;top:55px;bottom:0px;width:100%">
  728. <table id="p5toolbar" style="width:100%;height:78px" cellpadding="0" cellspacing="0">
  729. <tr>
  730. <td style="width:100%;background-color:#d3d9d6;text-align:left;padding:4px" valign=bottom>
  731. <div style="width:100%;text-align:center">
  732. <input type=button style="width:calc(100%/5 - 5px)" id=p5FolderUp disabled="disabled" onclick="p5folderup()" value="Up" />
  733. <input type=button style="width:calc(100%/5 - 5px)" id=p5SelectAllButton disabled="disabled" onclick="p5selectallfile()" value="SelectAll" onkeypress="return false" onkeydown="return false" />
  734. <input type=button style="width:calc(100%/5 - 5px)" id=p5RenameFileButton disabled="disabled" value="Rename" onclick="p5renamefile()" onkeypress="return false" onkeydown="return false" />
  735. <input type=button style="width:calc(100%/5 - 5px)" id=p5DeleteFileButton disabled="disabled" value="Delete" onclick="p5deletefile()" onkeypress="return false" onkeydown="return false" />
  736. <input type=button style="width:calc(100%/5 - 5px)" id=p5NewFolderButton disabled="disabled" value="Folder" onclick="p5createfolder()" onkeypress="return false" onkeydown="return false" />
  737. </div>
  738. <div style="width:100%;text-align:center">
  739. <input type=button style="width:calc(100%/5 - 5px)" id=p5UploadButton disabled="disabled" value="Upload" onclick="p5uploadFile()" onkeypress="return false" onkeydown="return false" />
  740. <input type=button style="width:calc(100%/5 - 5px)" id=p5CutButton disabled="disabled" value="Cut" onclick="p5copyFile(1)" onkeypress="return false" onkeydown="return false" />
  741. <input type=button style="width:calc(100%/5 - 5px)" id=p5CopyButton disabled="disabled" value="Copy" onclick="p5copyFile(0)" onkeypress="return false" onkeydown="return false" />
  742. <input type=button style="width:calc(100%/5 - 5px)" id=p5PasteButton disabled="disabled" value="Paste" onclick="p5pasteFile()" onkeypress="return false" onkeydown="return false" />
  743. <input type=button style="width:calc(100%/5 - 5px)" id=p5RefreshButton value="Refresh" onclick="p5refreshFiles()" onkeypress="return false" onkeydown="return false" />
  744. </div>
  745. </td>
  746. </tr>
  747. <tr>
  748. <td style="background-color:#E4E9E7;height:28px">
  749. <table style="width:100%">
  750. <tr>
  751. <td id=p5currentpath style="overflow:hidden;padding-left:4px;padding-top:2px;color:black"></td>
  752. <td style="text-align:right;padding-right:4px">
  753. <select id=p5sortdropdown onchange=updateFiles()>
  754. <option value=1 selected="selected">Sort by name</option>
  755. <option value=2>Sort by size</option>
  756. <option value=3>Sort by date</option>
  757. <option value=4>Descend by name</option>
  758. <option value=5>Descend by size</option>
  759. <option value=6>Descend by date</option>
  760. </select>
  761. </td>
  762. </tr>
  763. </table>
  764. </td>
  765. </tr>
  766. </table>
  767. <div id="p5filetable" style="width:100%;height:calc(100% - 102px);overflow:auto;-webkit-user-select:none">
  768. <!--
  769. <div id="p5bigok" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&checkmark;</b></div>
  770. <div id="p5bigfail" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&#10007;</b></div>
  771. -->
  772. <span id="p5files"></span>
  773. </div>
  774. <table id="p5toolbarBottom" style="width:100%;height:22px;position:absolute;bottom:0px;background-color:#D3D9D6" cellpadding=0 cellspacing=0>
  775. <tr>
  776. <td style="text-align:left;padding:3px">&nbsp;<span id="p5bottomstatus"></span></td>
  777. <td id="p5rightOfButtons" style="text-align:right;padding:3px"></td>
  778. </tr>
  779. </table>
  780. </div>
  781. </div>
  782. <div id=p10 style=display:none;position:absolute;bottom:0;top:0;width:100%;overflow:hidden>
  783. <table id=p10deskTopTable cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0;position:absolute;top:0">
  784. <tr style=padding:0>
  785. <td style="padding:0;color:#c8c8c8;text-align:center;cursor:pointer" width=60px valign=top onclick=goBack()>
  786. <div style="padding:0;background-color:#036;width:10px;height:10px;float:right;border:0">
  787. <div class="menucurve"></div>
  788. </div>
  789. <div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">&#9664;</div>
  790. </td>
  791. <td>
  792. <a id=MainComputerImage style=cursor:pointer onclick=p10showiconselector()></a>
  793. </td>
  794. <td>
  795. <div style=margin-left:5px>
  796. <strong><span id=p10deviceName></span></strong><br />
  797. <span id=MainComputerState></span>
  798. </div>
  799. </td>
  800. </tr>
  801. </table>
  802. <div id=p10dialog style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial,Helvetica,sans-serif;border-radius:5px;position:fixed;top:30px;width:300px;left:30px;display:none">
  803. <div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
  804. <div style=padding:5px>Keyboard Shortcuts Customization</div>
  805. <div style=width:100%;margin:6px></div>
  806. </div>
  807. <div style="margin-right:16px;margin-left:8px"><div id=p10dialog2 style="margin:auto;margin:3px"></div></div>
  808. <div style="width:100%;padding:2px;text-align:center"><input type=button value="Restore Default Keyboard Shortcuts" onclick=restoreDeskCustomizeKey() /></div>
  809. <div style="padding:10px;margin-bottom:20px"><input type="button" value="OK" style="float:right;width:80px" onclick="deskCustomizeKeysEx()"></div>
  810. </div>
  811. <div id=p10general style="overflow-y:scroll;position:absolute;top:55px;bottom:0px;width:100%">
  812. <div class="deviceNotifyLargeDot">
  813. <img id="p10deviceStar" class=deviceNotifyLargeDotSub src=images/icon-star-notify-40.png width=35 height=35>
  814. <div id="p10deviceMsg" onclick=showDeviceMessages(null,null,event) class=deviceNotifyLargeDotSub></div>
  815. <img id="p10deviceNotify" onclick=showDeviceSessions() class=deviceNotifyLargeDotSub src=images/icon-relay-notify-40.png width=35 height=35>
  816. <img id="p10deviceHelp" onclick=showDeviceHelpRequests(null,null,event) class=deviceNotifyLargeDotSub src=images/icon-help-notify-40.png width=35 height=35>
  817. </div>
  818. <div id="p10deviceBattery" class="deviceBatteryLarge deviceBatteryLarge1"></div>
  819. <div id=p10html style="margin-left:8px;margin-right:8px"></div>
  820. <div id=p10html2></div>
  821. <div id=p10html3 style="margin-left:8px"></div>
  822. </div>
  823. <img id="deskkeybutton1" src="images/mobile-desk-exit.png" class="deskButton" style="top:10px;display:none" onclick="exitButton()" />
  824. <img id="deskkeybutton3a" src="images/mobile-desk-menu-open.png" class="deskButton" style="top:60px;display:none" onclick="toggleMenu(false)" />
  825. <img id="deskkeybutton3b" src="images/mobile-desk-menu-close.png" class="deskButton" style="top:60px;display:none" onclick="toggleMenu(true)" />
  826. <img id="deskkeybutton4a" src="images/mobile-desk-mouse-left.png" class="deskButton" style="top:110px;display:none" onclick="deskChangeMouseButton(0)" />
  827. <img id="deskkeybutton4b" src="images/mobile-desk-mouse-right.png" class="deskButton" style="top:110px;display:none" onclick="deskChangeMouseButton(1)" />
  828. <img id="deskkeybutton5a" src="images/mobile-desk-scale-out.png" class="deskButton" style="top:160px;display:none" onclick="deskChangeFullscreenZoom()" />
  829. <img id="deskkeybutton5b" src="images/mobile-desk-scale-in.png" class="deskButton" style="top:160px;display:none" onclick="deskChangeFullscreenZoom()" />
  830. <img id="deskkeybutton2a" src="images/mobile-desk-keyboard-open.png" class="deskButton" style="top:210px;display:none" onclick="toggleKeyboard()" />
  831. <img id="deskkeybutton2b" src="images/mobile-desk-keyboard-close.png" class="deskButton" style="top:210px;display:none" onclick="toggleKeyboard()" />
  832. <div style="position:absolute;top:0;left:0;z-index:200;opacity:0;width:1px;height:1px">
  833. <input id="softKeyboard" autocapitalize="off" autocomplete="off" type="text" inputmode="text" spellcheck="false" style="z-index:200;opacity:0;width:1px;height:1px" onfocus="keyboardFocusChange()" onblur="keyboardFocusChange()" />
  834. </div>
  835. <div id="deskButtonMenu" style="display:none;position:absolute;top:10px;left:10px;right:55px;bottom:10px;z-index:1000"></div>
  836. <div id=p10desktop style="overflow:hidden;position:absolute;top:55px;bottom:0px;width:100%;display:none">
  837. <div id=deskarea1 style="position:absolute;top:0px;width:100%;height:32px">
  838. <div style="padding-top:2px;padding-bottom:2px;background:#C0C0C0;height:32px">
  839. <div style="float:right;text-align:right">
  840. <span id="p14power"></span>&nbsp;
  841. <input type=button id=deskFullScreen value="Full Screen" onclick=deskToggleFull(event) onkeypress="return false" onkeydown="return false" disabled="disabled" style="height:28px;margin-right:3px;">
  842. </div>
  843. <div style="margin-left:3px">
  844. <input type=button id=connectbutton1 value="Connect" onclick=connectDesktop(event,3) onkeypress="return false" onkeydown="return false" disabled="disabled" style="height:28px">
  845. <input type=button id=connectbutton1h value="HW Connect" onclick=connectDesktop(event,2) onkeypress="return false" onkeydown="return false" disabled="disabled" style="height:28px">
  846. <input type=button id=disconnectbutton1 value="Disconnect" onclick=connectDesktop(event,0) onkeypress="return false" onkeydown="return false" style="height:28px">
  847. <span id="deskstatus" style="color:black">Disconnected</span>
  848. </div>
  849. </div>
  850. </div>
  851. <div id=deskarea3 style="position:absolute;top:32px;width:100%;height:calc(100% - 64px);background-color:#000;text-align:center">
  852. <div id=DeskParent style="height:100%">
  853. <canvas id=Desk width=640 height=200 style="width:100%;-ms-touch-action:none;margin-left:0px" oncontextmenu="return false" onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event) onmousewheel=dmousewheel(event)></canvas>
  854. </div>
  855. <div id=p11DeskConsoleMsg style="display:none;cursor:pointer;position:absolute;left:30px;top:17px;color:yellow;background-color:rgba(0,0,0,0.6);padding:10px;border-radius:5px;text-align:left" onclick=p11clearConsoleMsg()></div>
  856. <div id=p11DeskSessionSelector style="display:none;position:absolute;left:30px;top:17px;right:30px;bottom:17px;overflow-y:auto"></div>
  857. </div>
  858. <div id=deskarea4 style="position:absolute;bottom:0px;width:100%;height:32px">
  859. <div style=padding-top:2px;padding-bottom:2px;background:#C0C0C0>
  860. <div style=float:right;text-align:right;padding-right:2px>
  861. <span id=DeskLockButton><img src='images/icon-lock.png' onclick=deviceLockFunction() height=16 width=16 style=padding-top:5px;cursor:pointer /></span>
  862. <span id=DeskChatButton><img src='images/icon-chat.png' onclick=deviceChat(event) height=16 width=16 style=padding-top:5px;cursor:pointer /></span>&nbsp;
  863. <span id=DeskToastButton><img src='images/icon-notify.png' onclick=deviceToastFunction() height=16 width=16 style=padding-top:5px;cursor:pointer /></span>&nbsp;
  864. <span id=DeskOpenWebButton><img src='images/icon-url2.png' onclick=deviceUrlFunction() height=16 width=16 style=padding-top:5px;cursor:pointer /></span>&nbsp;
  865. <span id=DeskRunButton><img src='images/icon-play.png' onclick=runDeviceCmd() height=16 width=16 style=padding-top:5px;cursor:pointer /></span>
  866. <!--<input id=DeskToolsButton type=button value=Tools onkeypress="return false" onkeydown="return false" onclick="toggleDeskTools()">&nbsp;-->
  867. </div>
  868. <div>
  869. <input id="deskActionsBtn" type=button style="margin-left:3px;height:28px" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() />
  870. <input type="button" value="Settings" onkeypress="return false" onkeydown="return false" onclick="showDesktopSettings()" style="height:28px">
  871. <input type="button" onkeypress="return false" onkeydown="return false" value="Power Actions..." onclick="showPowerActionDlg()" style="display:none;height:28px">
  872. <!--<input type="button" id="DeskSpecialKeys" value="Keys" onkeypress="return false" onkeydown="return false" onclick="sendSpecialKeys()" style="height:28px">-->
  873. <input type="button" id="DeskScreens" value="Screens" onkeypress="return false" onkeydown="return false" onclick="deskSelectScreens()" style="display:none;height:28px">
  874. <label><span id="DeskControlSpan" style="display:none"><input id="DeskControl" type="checkbox" onkeypress="return false" onkeydown="return false">Input</span></label>
  875. </div>
  876. </div>
  877. </div>
  878. </div>
  879. <div id="termButtonMenu" style="display:none;position:absolute;top:10px;left:10px;right:55px;bottom:10px;z-index:1000"></div>
  880. <div id=p10terminal style="overflow:hidden;position:absolute;top:55px;bottom:0px;width:100%;display:none;background-color:#333">
  881. <div id=termTable style="position:absolute;top:0;bottom:0;left:0;right:0">
  882. <div id="termarea1">
  883. <div class="areaHead" style="line-height:24px">
  884. <div class="toright2">
  885. <input type=button id=termFullScreen value="Full Screen" onclick=deskToggleFull(event) onkeypress="return false" onkeydown="return false" disabled="disabled" style="height:28px;margin-right:3px;">
  886. <div id="terminalCustomUpperRight" style="float:left;margin-right:6px"></div>
  887. </div>
  888. <div>
  889. <span id="connectbutton2span" style="margin-left:3px"><input type="button" id="connectbutton2" value="Connect" style="height:28px" onclick=connectTerminal(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span>
  890. <span id="connectbutton2sspan" style="margin-right:4px"><input type="button" id="connectbutton2s" value="SSH Connect" style="height:28px" onclick=connectTerminal(event,3) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span>
  891. <span id="disconnectbutton2span" style="margin-left:3px"><input type="button" id="disconnectbutton2" value="Disconnect" style="height:28px" onclick=connectTerminal(event,0) onkeypress="return false" onkeydown="return false" /></span>
  892. <span id="termstatus" style="line-height:22px">Disconnected</span><span id="termtitle"></span>
  893. </div>
  894. </div>
  895. </div>
  896. <div id="termarea3" style="width:100%;height: calc(100% - 60px);" cellpadding=0 cellspacing=0>
  897. <div id="termarea3x" style="width:100%;height:100%">
  898. <div style="width:100%;height:100%;text-align:left" id="termarea3xdiv"></div>
  899. </div>
  900. </div>
  901. <div id="termarea4" style="position:relative;height:32px;">
  902. <div class="areaFoot">
  903. <div class="toright2"></div>
  904. <div style="height:28px">
  905. <input id="termActionsBtn" style="margin-left:3px;height:28px" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() />
  906. <input id="ctrlcbutton" style="margin-left:3px;height:28px" type=button onkeypress="return false" onkeydown="return false" value="Ctl-C" onclick="termSendKey(3,'ctrlcbutton')" />
  907. <input id="ctrlxbutton" style="margin-left:3px;height:28px" type=button onkeypress="return false" onkeydown="return false" value="Ctl-X" onclick="termSendKey(24,'ctrlxbutton')" />
  908. <input id="escbutton" style="margin-left:3px;height:28px" type=button onkeypress="return false" onkeydown="return false" value="ESC" onclick="termSendKey(27,'escbutton')" />
  909. </div>
  910. </div>
  911. </div>
  912. <div id=p12TermConsoleMsg style="display:none;cursor:pointer;position:absolute;left:30px;top:45px;color:yellow;background-color:rgba(0,0,0,0.6);padding:10px;border-radius:5px" onclick=p12clearConsoleMsg()></div>
  913. </div>
  914. </div>
  915. <div id=p10files style="position:absolute;top:55px;bottom:0px;width:100%;display:none">
  916. <table id="p13toolbar" style="width:100%;height:111px" cellpadding="0" cellspacing="0">
  917. <tr>
  918. <td style="background-color:#C0C0C0;border-bottom:2px solid black;padding:2px;line-height:24px">
  919. <div style="float:right;text-align:right">
  920. <input id="filesActionsBtn" type=button onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() style=margin-right:2px />
  921. <div id="filesCustomUpperRight" style="float:left;margin-right:6px"></div>
  922. </div>
  923. <div style="margin-left:2px">
  924. <input id=p13AutoConnect value="AutoConnect" onclick=autoConnectFiles(event) onkeypress="return false" onkeydown="return false" type="button" style="display:none">
  925. <input id=p13Connect value="Connect" onclick=connectFiles(event,1) onkeypress="return false" onkeydown="return false" type="button" />
  926. <input id=p13Connects value="SFTP Connect" onclick=connectFiles(event,2) onkeypress="return false" onkeydown="return false" type="button" />
  927. <input id=p13Disconnect value="Disconnect" onclick=connectFiles(event) onkeypress="return false" onkeydown="return false" type="button" />
  928. <span class=textOverGray id=p13Status>Disconnected</span>
  929. </div>
  930. </td>
  931. </tr>
  932. <tr>
  933. <td style="width:100%;background-color:#d3d9d6;text-align:left;padding:4px" valign=bottom>
  934. <div style="width:100%;text-align:center">
  935. <input type=button style="width:calc(100%/5 - 5px)" id=p13FolderUp disabled="disabled" onclick="p13folderup()" value="Up" />
  936. <input type=button style="width:calc(100%/5 - 5px)" id=p13SelectAllButton disabled="disabled" onclick="p13selectallfile()" value="SelectAll" onkeypress="return false" onkeydown="return false" />
  937. <input type=button style="width:calc(100%/5 - 5px)" id=p13RenameFileButton disabled="disabled" value="Rename" onclick="p13renamefile()" onkeypress="return false" onkeydown="return false" />
  938. <input type=button style="width:calc(100%/5 - 5px)" id=p13DeleteFileButton disabled="disabled" value="Delete" onclick="p13deletefile()" onkeypress="return false" onkeydown="return false" />
  939. <input type=button style="width:calc(100%/5 - 5px)" id=p13NewFolderButton disabled="disabled" value="Folder" onclick="p13createfolder()" onkeypress="return false" onkeydown="return false" />
  940. </div>
  941. <div style="width:100%;text-align:center">
  942. <input type=button style="width:calc(100%/5 - 5px)" id=p13UploadButton disabled="disabled" value="Upload" onclick="p13uploadFile()" onkeypress="return false" onkeydown="return false" />
  943. <input type=button style="width:calc(100%/5 - 5px)" id=p13CutButton disabled="disabled" value="Cut" onclick="p13copyFile(1)" onkeypress="return false" onkeydown="return false" />
  944. <input type=button style="width:calc(100%/5 - 5px)" id=p13CopyButton disabled="disabled" value="Copy" onclick="p13copyFile(0)" onkeypress="return false" onkeydown="return false" />
  945. <input type=button style="width:calc(100%/5 - 5px)" id=p13PasteButton disabled="disabled" value="Paste" onclick="p13pasteFile()" onkeypress="return false" onkeydown="return false" />
  946. <input type=button style="width:calc(100%/5 - 5px)" id=p13RefreshButton disabled="disabled" value="Refresh" onclick="p13folderup(9999)" onkeypress="return false" onkeydown="return false" />
  947. </div>
  948. </td>
  949. </tr>
  950. <tr>
  951. <td style="background-color:#E4E9E7;height:28px">
  952. <table style="width:100%">
  953. <tr>
  954. <td id=p13currentpath style="overflow:hidden;padding-left:4px;padding-top:2px;color:black"></td>
  955. <td style="text-align:right;padding-right:4px">
  956. <select id=p13sortdropdown onchange=p13updateFiles()>
  957. <option value=1 selected="selected">Sort by name</option>
  958. <option value=2>Sort by size</option>
  959. <option value=3>Sort by date</option>
  960. <option value=4>Descend by name</option>
  961. <option value=5>Descend by size</option>
  962. <option value=6>Descend by date</option>
  963. </select>
  964. </td>
  965. </tr>
  966. </table>
  967. </td>
  968. </tr>
  969. </table>
  970. <div id=p13FilesConsoleMsg style="display:none;cursor:pointer;position:absolute;left:30px;top:165px;color:yellow;background-color:rgba(0,0,0,0.6);padding:10px;border-radius:5px" onclick=p13clearConsoleMsg()></div>
  971. <div id="p13filetable" style="width:100%;height:calc(100% - 133px);overflow:auto;-webkit-user-select:none">
  972. <!--
  973. <div id="p13bigok" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&checkmark;</b></div>
  974. <div id="p13bigfail" style="width:256px;overflow:hidden;position:absolute;left:337px;top:200px;text-align:center;font-size:1600%;color:#AAAAAA;display:none"><b>&#10007;</b></div>
  975. -->
  976. <span id="p13files"></span>
  977. </div>
  978. <table id="p13toolbarBottom" style="width:100%;height:22px;position:absolute;bottom:0px" cellpadding=0 cellspacing=0>
  979. <tr><td style="text-align:left;padding:3px;text-align:center;background-color:#D3D9D6;color:black">&nbsp;<span id="p13bottomstatus"></span></td></tr>
  980. </table>
  981. </div>
  982. <div id=p10details style="overflow-y:scroll;position:absolute;top:55px;bottom:0px;width:100%">
  983. <div id=p10detailshtml style="margin-left:-3px"></div>
  984. </div>
  985. <div id=p10console style="overflow:hidden;position:absolute;top:55px;bottom:0px;width:100%">
  986. <table id="consoleTable" cellpadding=0 cellspacing=0>
  987. <tr style="height:28px">
  988. <td class="areaHead">
  989. <div class="toright2">
  990. <div id=p15coreName></div>
  991. <input type=button id=p15uploadCore value="Agent Action" onclick=p15uploadCore(event) />
  992. </div>
  993. <div id="p15statetext"></div>
  994. </td>
  995. </tr>
  996. <tr>
  997. <td id=p15agentConsole style="position:relative">
  998. <pre id=p15agentConsoleText></pre>
  999. </td>
  1000. </tr>
  1001. <tr style="height:28px">
  1002. <td class="areaFoot">
  1003. <table style="width:100%">
  1004. <tr>
  1005. <td style="width:99%">
  1006. <input id=p15consoleText style=width:100%;box-sizing:border-box onkeyup=p15consoleSend(event) />
  1007. </td>
  1008. <td id="p15outputselecttd">
  1009. <select id=p15outputselect onchange="setupConsole()">
  1010. <option id="p15outputselect1" value=1>Agent</option>
  1011. <option id="p15outputselect3" value=3>Push</option>
  1012. <option id="p15outputselect2" value=2>MQTT</option>
  1013. </select>
  1014. </td>
  1015. <td style="width:1%"><input id="id_p15consoleClear" type="button" class="bottombutton" value="Clear" onclick="p15consoleClear()" /></td>
  1016. </tr>
  1017. </table>
  1018. </td>
  1019. </tr>
  1020. </table>
  1021. </div>
  1022. </div>
  1023. <div id=p20 style="display:none;position:absolute;bottom:0;top:0;width:100%">
  1024. <table cellspacing=0 style="margin:0;padding:0;border-spacing:0;border:0;position:absolute;top:0">
  1025. <tr style=padding:0>
  1026. <td style="padding:0;color:#c8c8c8;text-align:center;cursor:pointer" width=60px valign=top onclick=goBack()>
  1027. <div style="padding:0;background-color:#036;width:10px;height:10px;float:right;border:0">
  1028. <div class="menucurve"></div>
  1029. </div>
  1030. <div style="padding:0;font-size:25px;background-color:#036;width:50px;border-radius:0 0 10px 0;height:36px">&#9664;</div>
  1031. </td>
  1032. <td onclick="p20editmesh(1)">
  1033. <img src="/images/meshicon50.png" width=50 height=50 />
  1034. </td>
  1035. <td onclick="p20editmesh(1)">
  1036. <div style=margin-left:5px>
  1037. <strong style="font-size:large"><span id=p20meshName></span></strong><br />
  1038. </div>
  1039. </td>
  1040. </tr>
  1041. </table>
  1042. <div style="overflow-y:auto;position:absolute;top:55px;bottom:0px;left:0px;right:0px">
  1043. <div id=p20info style="margin-left:8px;margin-right:8px"></div>
  1044. </div>
  1045. </div>
  1046. </div>
  1047. </div>
  1048. <div id=footer style="height:32px;width:100%;text-align:center;background-color:#113962;position:absolute;bottom:0px">
  1049. <table id=footerMenu cellpadding=0 cellspacing=0 style="height:32px;width:100%;color:white;cursor:pointer;table-layout:fixed"></table>
  1050. </div>
  1051. </div>
  1052. <div id=dialog style="display:none">
  1053. <div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
  1054. <div id=id_dialogclose style=float:right;padding:5px;cursor:pointer onclick=setDialogMode()><b>X</b></div>
  1055. <div id=id_dialogtitle style=padding:5px></div>
  1056. <div style=width:100%;margin:6px></div>
  1057. </div>
  1058. <div style="margin-right:16px;margin-left:8px">
  1059. <div id=dialog1 style="margin:auto;text-align:center;margin:3px">
  1060. <div id=id_dialogMessage style="padding:10px"></div>
  1061. </div>
  1062. <div id=dialog2 style="margin:auto;margin:3px">
  1063. <div id=id_dialogOptions></div>
  1064. </div>
  1065. <div id=dialog3 style="margin:auto;margin:3px">
  1066. <select id="deskkeys" style="width:100%">
  1067. <option value=10>Ctrl+Alt+Del</option>
  1068. <option value=11>Tab</option>
  1069. <option value=5>Win</option>
  1070. <option value=0>Win+Down</option>
  1071. <option value=1>Win+Up</option>
  1072. <option value=2>Win+L</option>
  1073. <option value=3>Win+M</option>
  1074. <option value=4>Shift+Win+M</option>
  1075. <option value=6>Win+R</option>
  1076. <option value=7>Alt-F4</option>
  1077. <option value=8>Ctrl-W</option>
  1078. <option value=9>Alt-Tab</option>
  1079. <option value=12>Shift-F10</option>
  1080. </select>
  1081. </div>
  1082. <div id=dialog4 style="margin:auto;margin:3px">
  1083. <div id=d3upload>
  1084. <div>File Selection</div>
  1085. <select id=d3uploadMode onchange=d3modechange()>
  1086. <option value=1>Local file upload</option>
  1087. <option value=2>Server file selection</option>
  1088. </select>
  1089. </div>
  1090. <div id=d3localmode style="display:none">
  1091. <div>Upload File</div>
  1092. <form id=d3localmodeform method=post enctype=multipart/form-data action=uploadfile.ashx target=fileUploadFrame>
  1093. <input type=text id=d3auth name=auth style="display:none" />
  1094. <input type=text id=d3filter name=filter style="display:none" />
  1095. <input type=text id=d3attrib name=attrib style="display:none" />
  1096. <input type=file id=d3localFile name=files onchange=d3setActions() />
  1097. <input type=submit id=d3submit style="display:none" />
  1098. </form>
  1099. </div>
  1100. <div id=d3servermode>
  1101. <div id=d3serveraction valign=bottom>
  1102. <input type=button id=p3FolderUp disabled="disabled" onclick=d3folderup() value="Up" />&nbsp;<span id=p3CurrentFolder></span>
  1103. </div>
  1104. <div id=d3serverfiles></div>
  1105. </div>
  1106. </div>
  1107. <div id=dialog7 style="margin:auto;margin:3px">
  1108. <div id="d7meshkvm">
  1109. <h4 style="width:100%;border-bottom:1px solid gray">Agent Remote Desktop</h4>
  1110. <table style="width:100%">
  1111. <tr>
  1112. <td>
  1113. Quality
  1114. </td>
  1115. <td style="width:100px">
  1116. <select id="d7bitmapquality" style="float:right;width:200px" dir="rtl"></select>
  1117. </td>
  1118. </tr>
  1119. <tr>
  1120. <td>
  1121. Scaling
  1122. </td>
  1123. <td style="width:100px">
  1124. <select id="d7bitmapscaling" style="float:right;width:200px" dir="rtl">
  1125. <option selected=selected value=1024>100%</option>
  1126. <option value=896>87.5%</option>
  1127. <option value=768>75%</option>
  1128. <option value=640>62.5%</option>
  1129. <option value=512>50%</option>
  1130. <option value=384>37.5%</option>
  1131. <option value=256>25%</option>
  1132. <option value=128>12.5%</option>
  1133. </select>
  1134. </td>
  1135. </tr>
  1136. <tr>
  1137. <td>
  1138. Rate
  1139. </td>
  1140. <td style="width:100px">
  1141. <select id="d7framelimiter" style="float:right;width:200px" dir="rtl">
  1142. <option selected=selected value=50>Fast</option>
  1143. <option value=100>Medium</option>
  1144. <option value=400>Slow</option>
  1145. <option value=1000>Very slow</option>
  1146. </select>
  1147. </td>
  1148. </tr>
  1149. <tr>
  1150. <td>
  1151. Encoding
  1152. </td>
  1153. <td style="width:100px">
  1154. <select id="d7encoding" style="float:right;width:200px" dir="rtl">
  1155. <option value=1>JPEG</option>
  1156. <option value=2>PNG</option>
  1157. <option value=3>TIFF</option>
  1158. <option selected=selected value=4>WEBP</option>
  1159. </select>
  1160. </td>
  1161. </tr>
  1162. <tr>
  1163. <td></td>
  1164. <td>
  1165. <label style="display:block" id="d7deskAutoLockLabel"><input type="checkbox" id="d7deskAutoLock" />Lock on Disconnect</label>
  1166. </td>
  1167. </tr>
  1168. </table>
  1169. </div>
  1170. <div id="d7amtkvm">
  1171. <h4 style="width:100%;border-bottom:1px solid gray">Intel&reg; AMT Hardware KVM</h4>
  1172. <table style="width:100%">
  1173. <tr>
  1174. <td>Encoding</td>
  1175. <td style="width:100px">
  1176. <select id="d7desktopmode" style="float:right;width:200px">
  1177. <option value="1">RLE8, Fastest</option>
  1178. <option value="2">RLE16, Recommended</option>
  1179. <option value="3">RAW8, Slow</option>
  1180. <option value="4">RAW16, Very Slow</option>
  1181. </select>
  1182. </td>
  1183. </tr>
  1184. </table>
  1185. </div>
  1186. </div>
  1187. </div>
  1188. <div id="idx_dlgButtonBar" style="padding:10px;margin-bottom:20px">
  1189. <input id="idx_dlgCancelButton" type="button" value="Cancel" style="float:right;width:80px;margin-left:5px" onclick="dialogclose(0)">
  1190. <input id="idx_dlgOkButton" type="button" value="OK" style="float:right;width:80px" onclick="dialogclose(1)">
  1191. <div><input id="idx_dlgDeleteButton" type="button" value="Delete" style="display:none" onclick="dialogclose(2)" /></div>
  1192. </div>
  1193. </div>
  1194. <div id=topMenu style="z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666;font-family:Arial,Helvetica,sans-serif;border-radius:0px 0px 5px 5px;position:fixed;top:50px;right:5px;width:170px;display:none">
  1195. <div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer" onclick=topMenu(2)>My Files</div>
  1196. <div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer" onclick=topMenu(1)>My Account</div>
  1197. <div id="logoutMenuOption"><a id="logoutMenuOptionRef" href=/logout><div style="padding:12px;border-top:1px solid gray;color:black;cursor:pointer">Logout</div></a></div>
  1198. </div>
  1199. <audio id="chimes"><source src="sounds/chimes.mp3" type="audio/mp3" /></audio>
  1200. <iframe name="fileUploadFrame" style=display:none></iframe>
  1201. <script>
  1202. 'use strict';
  1203. var random = '{{{randomlength}}}' // Random length string for BREACH mitigation
  1204. // Process server-side web state
  1205. var webState = '{{{webstate}}}';
  1206. if (webState != '') { webState = JSON.parse(decodeURIComponent(webState)); }
  1207. for (var i in webState) { try { localStorage.setItem(i, webState[i]); } catch (ex) { } }
  1208. if (webState && !webState.loctag) { try { delete localStorage.removeItem('loctag'); } catch (ex) { } }
  1209. // Fetch URL arguments & do sanitation
  1210. var urlargs = parseUriArgs();
  1211. if (urlargs.key != null) { urlargs.key = "" + urlargs.key; }
  1212. if (urlargs.key && (isAlphaNumeric(urlargs.key) == false)) { delete urlargs.key; }
  1213. if (urlargs.locale && (isAlphaNumeric(urlargs.locale) == false)) { delete urlargs.locale; }
  1214. delete urlargs.user;
  1215. delete urlargs.pass;
  1216. delete urlargs.viewmode;
  1217. delete urlargs.gotonode;
  1218. delete urlargs.gotodevicename;
  1219. delete urlargs.gotodevicername;
  1220. delete urlargs.gotodeviceip;
  1221. delete urlargs.gotomesh;
  1222. delete urlargs.panel;
  1223. // Check if we are in debug mode
  1224. var args = parseUriArgs();
  1225. if (args.key && (isAlphaNumeric(args.key) == false)) { delete args.key; }
  1226. if (args.locale && (isAlphaNumeric(args.locale) == false)) { delete args.locale; }
  1227. var debugLevel = parseInt('{{{debuglevel}}}');
  1228. var features = parseInt('{{{features}}}');
  1229. var features2 = parseInt('{{{features2}}}');
  1230. var sessionTime = parseInt('{{{sessiontime}}}');
  1231. var sessionRefreshTimer = null;
  1232. var domain = '{{{domain}}}';
  1233. var domainUrl = '{{{domainurl}}}';
  1234. var authCookie = '{{{authCookie}}}';
  1235. var authRelayCookie = '{{{authRelayCookie}}}';
  1236. var logoutControls = JSON.parse(decodeURIComponent('{{{logoutControls}}}'));
  1237. var authCookieRenewTimer = null;
  1238. var webRelayPort = parseInt('{{{webRelayPort}}}');
  1239. var hidePowerTimeline = '{{{hidePowerTimeline}}}';
  1240. var webRelayDns = '{{{webRelayDns}}}';
  1241. var meshserver = null;
  1242. var xdr = null;
  1243. var usergroups = null;
  1244. var stars = {}; // Devices that have been "stared" by the user.
  1245. var serverinfo = null;
  1246. var nodes = [];
  1247. var meshes = {};
  1248. var filetree = {};
  1249. var userinfo = null;
  1250. var serverinfo = null;
  1251. var users = null;
  1252. var nodeShortIdent = 0;
  1253. var serverPublicNamePort = '{{{serverDnsName}}}:{{{serverPublicPort}}}';
  1254. var debugmode = false;
  1255. var attemptWebRTC = ((features & 128) != 0);
  1256. var webrtcconfiguration = '{{{webrtcconfig}}}';
  1257. if (webrtcconfiguration == '') { webrtcconfiguration = null; } else { try { webrtcconfiguration = JSON.parse(decodeURIComponent(webrtcconfiguration)); } catch (ex) { console.log('Invalid WebRTC config: "' + webrtcconfiguration + '".'); webrtcconfiguration = null; } }
  1258. var StatusStrs = ["Disconnected", "Connecting...", "Setup...", "Connected", "Intel&reg; AMT Connected"];
  1259. var agentsStr = ["Unknown", "Windows 32bit console", "Windows 64bit console", "Windows 32bit service", "Windows 64bit service", "Linux 32bit", "Linux 64bit", "MIPS", "XENx86", "Android", "Linux ARM", "macOS x86-32bit", "Android x86", "PogoPlug ARM", "Android", "Linux Poky x86-32bit", "macOS x86-64bit", "ChromeOS", "Linux Poky x86-64bit", "Linux NoKVM x86-32bit", "Linux NoKVM x86-64bit", "Windows MinCore console", "Windows MinCore service", "NodeJS", "ARM-Linaro", "ARMv6l / ARMv7l", "ARMv8 64bit", "ARMv6l / ARMv7l / NoKVM", "MIPS24KC (OpenWRT)", "Apple Silicon", "FreeBSD x86-64", "Unknown", "Linux ARM 64 bit (glibc/2.24 NOKVM)", "Alpine Linux x86 64 Bit (MUSL)", "Assistant (Windows)", "Armada370 - ARM32/HF (libc/2.26)", "OpenWRT x86-64", "OpenBSD x86-64", "Unknown", "Unknown", "MIPSEL24KC (OpenWRT)", "ARMADA/CORTEX-A53/MUSL (OpenWRT)", "Windows ARM 64bit console", "Windows ARM 64bit service", "ARMVIRT32 (OpenWRT)", "RISC-V x86-64"];
  1260. var files;
  1261. var terminal;
  1262. var passRequirements = '{{{passRequirements}}}';
  1263. if (passRequirements != '') { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); }
  1264. var sessionActivity = Date.now();
  1265. var deskPinchZoom;
  1266. var deskKeyboardShortcuts = [];
  1267. var nightMode = setNightMode();
  1268. var xterm = null;
  1269. var xtermfit = null;
  1270. var xtermResizeTimer = null;
  1271. var devicePagingState = null;
  1272. // Console Message Display Timers
  1273. var p11DeskConsoleMsgTimer = null;
  1274. var p12TermConsoleMsgTimer = null;
  1275. var p13FilesConsoleMsgTimer = null;
  1276. // Check if WebP is supported
  1277. var webpSupport = false;
  1278. check_webp_feature('lossy', function (f, x) {
  1279. webpSupport = x;
  1280. if (!x) {
  1281. d7encoding.options[1].disabled = true;
  1282. d7encoding.value = 1;
  1283. }
  1284. });
  1285. function startup() {
  1286. if ((features & 32) == 0) {
  1287. // Guard against other site's top frames (web bugs).
  1288. var loc = null;
  1289. try { loc = top.location.toString().toLowerCase(); } catch (e) { }
  1290. if (top != self && (loc == null || top.active == false)) { top.location = self.location; return; }
  1291. }
  1292. if (!args.locale) { var x = getstore('loctag', 0); if ((x != null) && (x != '*')) { args.locale = x; } }
  1293. window.onresize = center;
  1294. center();
  1295. QV('changeEmailId', (features & 0x200000) == 0);
  1296. QH('p1message', "Connecting...");
  1297. go(1);
  1298. // Document keys
  1299. document.onkeypress = ondeskkeypress;
  1300. document.onkeydown = ondeskkeydown;
  1301. document.onkeyup = ondeskkeyup;
  1302. document.onclick = function (e) { if ((xxdialogMode == 999) && (e.target.id != 'topMenuIcon')) { QV('topMenu', false); xxdialogMode = 0; } }
  1303. // Connect to the mesh server
  1304. meshserver = MeshServerCreateControl(domainUrl);
  1305. meshserver.onStateChanged = onStateChanged;
  1306. meshserver.onMessage = onMessage;
  1307. meshserver.trace = args.trace;
  1308. meshserver.Start();
  1309. // Setup stared devices
  1310. try { stars = JSON.parse(getstore('stars', '{}')); } catch (ex) { }
  1311. // Setup logout control
  1312. if (logoutControls && logoutControls.logoutUrl) { Q('logoutMenuOptionRef').href = logoutControls.logoutUrl; }
  1313. // Load desktop settings
  1314. var t = localStorage.getItem('desktopsettings');
  1315. if (t != null) { desktopsettings = JSON.parse(t); }
  1316. applyDesktopSettings();
  1317. //attemptWebRTC = false; // For now, default WebRTC off unless we set it in the URL.
  1318. if (args.webrtc != null) { attemptWebRTC = (args.webrtc == 1); }
  1319. // Session Refresh Timer
  1320. if (sessionTime >= 10) { sessionRefreshTimer = setTimeout(refreshCookieSession, Math.round((sessionTime * 60000) * 0.8)); }
  1321. // Hide night mode button if needed
  1322. QV('setDarkModeLink', (features2 & 0x00300000) == 0);
  1323. // Set the user's desktop shortcut keys
  1324. deskKeyboardShortcuts = [];
  1325. var deskKeyboardShortcutsStr = getstore('deskKeyShortcuts', '0x0A002E,0x100000,0x100028,0x100026,0x10004C,0x10004D,0x11004D,0x100052,0x020073,0x080057,0x020009,0x100025,0x100027').split(',');
  1326. for (var i in deskKeyboardShortcutsStr) { deskKeyboardShortcuts.push(parseInt(deskKeyboardShortcutsStr[i])); }
  1327. updateDeskShortcutKeys();
  1328. updateTermShortcutKeys();
  1329. }
  1330. function refreshCookieSession() {
  1331. var xdr = null;
  1332. try { xdr = new XDomainRequest(); } catch (e) { }
  1333. if (!xdr) xdr = new XMLHttpRequest();
  1334. xdr.open('GET', window.location.origin + domainUrl + 'refresh.ashx');
  1335. xdr.timeout = 15000;
  1336. xdr.onload = function () { sessionRefreshTimer = setTimeout(refreshCookieSession, Math.round((sessionTime * 60000) * 0.8)); };
  1337. xdr.onerror = xdr.ontimeout = function () { sessionRefreshTimer = null; };
  1338. xdr.send();
  1339. }
  1340. function onStateChanged(server, state, prevState, errorCode) {
  1341. if (state == 0) {
  1342. // Control web socket disconnected
  1343. setDialogMode(0); // Close any dialog boxes if present
  1344. go(0); // Go to disconnection panel
  1345. deleteAllNotifications(); // Close and clear notifications if present
  1346. if (errorCode == 'noauth') { QH('p0span', "Unable to perform authentication"); return; }
  1347. if (prevState == 2) { setTimeout(serverPoll, 5000); } else { QH('p0span', "Unable to connect web socket"); }
  1348. // Clean up here
  1349. if (authCookieRenewTimer != null) { clearInterval(authCookieRenewTimer); authCookieRenewTimer = null; }
  1350. devicePagingState = null;
  1351. updateDevicePageState();
  1352. } else if (state == 2) {
  1353. // Fetch list of meshes, nodes, files
  1354. meshserver.send({ action: 'usergroups' });
  1355. meshserver.send({ action: 'meshes' });
  1356. meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip });
  1357. meshserver.send({ action: 'files' });
  1358. authCookieRenewTimer = setInterval(function () { meshserver.send({ action: 'authcookie' }); }, 1800000); // Request a cookie refresh every 30 minutes.
  1359. }
  1360. QV('topMenuIcon', state == 2);
  1361. }
  1362. // Poll the server, if it responds, refresh the page.
  1363. function serverPoll() {
  1364. xdr = null;
  1365. try { xdr = new XDomainRequest(); } catch (e) { }
  1366. if (!xdr) xdr = new XMLHttpRequest();
  1367. xdr.open('HEAD', window.location.href);
  1368. xdr.timeout = 15000;
  1369. // Make sure there isn't a reverse proxy in front that may just be returning 5xx codes
  1370. // Status code 4xx should still be allowed, since a page could potentially be removed, etc
  1371. xdr.onload = function () { if (xdr.status < 500) reload(); else setTimeout(serverPoll, 10000); };
  1372. xdr.onerror = xdr.ontimeout = function () { setTimeout(serverPoll, 10000); };
  1373. xdr.send();
  1374. }
  1375. function updateSelf() {
  1376. var accountSettingsLocked = ((features2 & 0x100) != 0);
  1377. if (userinfo) { accountSettingsLocked = ((userinfo.siteadmin != 0xFFFFFFFF) && ((userinfo.siteadmin & 1024) != 0)) || ((features2 & 0x100) != 0); } // Not admin and have account features locked, or using a loginToken
  1378. QV('p3AccountActions', ((features & 4) == 0) && (serverinfo.domainauth == false) && (accountSettingsLocked == false)); // Hide Account Actions if in single user mode or domain authentication
  1379. QV('logoutMenuOption', ((features & 4) == 0) && (serverinfo.domainauth == false)); // Hide logout if in single user mode or domain authentication
  1380. QV('p2AccountSecurity', ((features & 4) == 0) && (serverinfo.domainauth == false) && ((features & 4096) != 0) && (accountSettingsLocked == false)); // Hide Account Security if in single user mode or domain authentication, 2 factor auth not supported.
  1381. QV('p2AccountImage', !accountSettingsLocked);
  1382. QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
  1383. QV('manageAuthApp', (serverinfo.lock2factor != true) && (features & 4096) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0)));
  1384. QV('manageOtp', (serverinfo.lock2factor != true) && ((features2 & 0x40000) == 0) && (features & 4096) && ((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0)));
  1385. QV('authPhoneNumberCheck', (userinfo.phone != null));
  1386. QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
  1387. QV('authAppSetupCheck', userinfo.otpsecret == 1);
  1388. //QV('authKeySetupCheck', userinfo.otphkeys > 0);
  1389. QV('authCodesSetupCheck', userinfo.otpkeys > 0);
  1390. QV('p2AccountActions', ((features & 4) == 0) && (serverinfo.domainauth == false) && (userinfo != null));
  1391. QV('p2AccountPassActions', ((features & 4) == 0) && (serverinfo.domainauth == false) && (userinfo != null) && (userinfo._id.split('/')[2].startsWith('~') == false)); // Hide Account Actions if in single user mode or domain authentication
  1392. // On the mobile app, don't allow group creation (for now).
  1393. QV('p3createMeshLink1', false);
  1394. QV('p3createMeshLink2', false);
  1395. // Update user image
  1396. if ((userinfo.flags != null) && (userinfo.flags & 1)) {
  1397. if (userinfo.accountImageRnd == null) { userinfo.accountImageRnd = Math.floor(Math.random() * 9999999999); }
  1398. Q('p2AccountImage').src = 'userimage.ashx?rnd=' + userinfo.accountImageRnd;
  1399. } else {
  1400. Q('p2AccountImage').src = 'images/user-256.png';
  1401. }
  1402. if (typeof userinfo.passchange == 'number') {
  1403. if (userinfo.passchange == -1) { QH('p2nextPasswordUpdateTime', " - Reset on next login."); }
  1404. else if ((passRequirements != null) && (typeof passRequirements.reset == 'number')) {
  1405. var seconds = (userinfo.passchange) + (passRequirements.reset * 86400) - Math.floor(Date.now() / 1000);
  1406. if (seconds < 0) { QH('p2nextPasswordUpdateTime', " - Reset on next login."); }
  1407. else if (seconds < 3600) { var secs = Math.floor(seconds / 60); QH('p2nextPasswordUpdateTime',format((secs == 1)?" - Reset in 1 minute.":" - Reset in {0} minutes.", secs)); }
  1408. else if (seconds < 86400) { var hours = Math.floor(seconds / 3600); QH('p2nextPasswordUpdateTime', format((hours == 1) ? " - Reset in 1 hour." : " - Reset in {0} hours.", hours)); }
  1409. else { var days = Math.floor(seconds / 86400); QH('p2nextPasswordUpdateTime', format((hours == 1) ? " - Reset in 1 day." : " - Reset in {0} days.", days)); }
  1410. }
  1411. }
  1412. }
  1413. function setSessionActivity() { sessionActivity = Date.now(); }
  1414. function checkIdleSessionTimeout() {
  1415. var delta = (Date.now() - sessionActivity);
  1416. if (delta > serverinfo.timeout) {
  1417. if (desktop != null) { // Disconnect remote desktop
  1418. desktop.Stop();
  1419. desktopNode = desktop = null;
  1420. }
  1421. if (terminal != null) { // Disconnect terminal
  1422. terminal.Stop();
  1423. terminal = null;
  1424. }
  1425. if (files != null) { // Disconnect files
  1426. files.Stop();
  1427. files = null;
  1428. }
  1429. if (serverinfo.logoutonidlesessiontimeout) {
  1430. if (urlargs.key) { window.location.href = 'logout?key=' + urlargs.key; }
  1431. else { window.location.href = 'logout'; }
  1432. }
  1433. }
  1434. }
  1435. function onMessage(server, message) {
  1436. switch (message.action) {
  1437. case 'serverinfo': {
  1438. serverinfo = message.serverinfo;
  1439. if (serverinfo.timeout) { setInterval(checkIdleSessionTimeout, 10000); checkIdleSessionTimeout(); }
  1440. if (userinfo != null) updateSelf();
  1441. if (serverinfo.certExpire != null) {
  1442. var days = Math.floor((serverinfo.certExpire - Date.now()) / 86400000);
  1443. if ((days >= 0) && (days < 20)) {
  1444. addNotification({ text: format("Certificate expires in {0} day(s)", days) });
  1445. }
  1446. }
  1447. // Arrange the user interface
  1448. QV('manageEmail2FA', (features & 0x00800000) && (serverinfo.lock2factor != true));
  1449. QV('managePhoneNumber1', (features & 0x02000000) && (features & 0x04000000) && (serverinfo.lock2factor != true));
  1450. QV('managePhoneNumber2', (features & 0x02000000) && !(features & 0x04000000) && (serverinfo.lock2factor != true));
  1451. break;
  1452. }
  1453. case 'authcookie': {
  1454. // Got an authentication cookie refresh
  1455. authCookie = message.cookie;
  1456. authRelayCookie = message.rcookie;
  1457. break;
  1458. }
  1459. case 'userinfo': {
  1460. userinfo = message.userinfo;
  1461. QH('p3userName', userinfo.name);
  1462. //updateSiteAdmin();
  1463. if (serverinfo != null) updateSelf();
  1464. break;
  1465. }
  1466. case 'users': {
  1467. users = {};
  1468. for (var m in message.users) { users[message.users[m]._id] = message.users[m]; }
  1469. if (currentUser != null) { currentUser = users[currentUser._id]; }
  1470. updateUsers();
  1471. break;
  1472. }
  1473. case 'wssessioncount': {
  1474. wssessions = message.wssessions;
  1475. updateUsers();
  1476. break;
  1477. }
  1478. case 'meshes': {
  1479. meshes = {};
  1480. for (var m in message.meshes) { meshes[message.meshes[m]._id] = message.meshes[m]; }
  1481. if (currentMesh != null) { currentMesh = meshes[currentMesh._id]; }
  1482. updateMeshes();
  1483. mainUpdate(4);
  1484. break;
  1485. }
  1486. case 'usergroups': {
  1487. var groupCount = 0;
  1488. if (Array.isArray(message.ugroups)) {
  1489. usergroups = {};
  1490. for (var i in message.ugroups) { groupCount++; usergroups[message.ugroups[i]._id] = message.ugroups[i]; }
  1491. if (groupCount == 0) { usergroups = null; }
  1492. } else {
  1493. usergroups = message.ugroups;
  1494. for (var i in message.ugroups) { groupCount++; }
  1495. if (groupCount == 0) { usergroups = null; }
  1496. }
  1497. //mainUpdate(8192);
  1498. break;
  1499. }
  1500. case 'files': {
  1501. filetree = setupBackPointers(message.filetree);
  1502. updateFiles();
  1503. //d3updatefiles();
  1504. break;
  1505. }
  1506. case 'nodes': {
  1507. nodes = [];
  1508. for (var m in message.nodes) {
  1509. for (var n in message.nodes[m]) {
  1510. message.nodes[m][n].namel = message.nodes[m][n].name.toLowerCase();
  1511. if (message.nodes[m][n].rname) { message.nodes[m][n].rnamel = message.nodes[m][n].rname.toLowerCase(); } else { message.nodes[m][n].rnamel = message.nodes[m][n].namel; }
  1512. message.nodes[m][n].meshnamel = meshes[m]?meshes[m].name.toLowerCase():'*';
  1513. message.nodes[m][n].meshid = m;
  1514. message.nodes[m][n].state = (message.nodes[m][n].state) ? (message.nodes[m][n].state) : 0;
  1515. message.nodes[m][n].desc = message.nodes[m][n].desc;
  1516. if (!message.nodes[m][n].icon) message.nodes[m][n].icon = 1;
  1517. message.nodes[m][n].ident = ++nodeShortIdent;
  1518. nodes.push(message.nodes[m][n]);
  1519. }
  1520. }
  1521. // If we are currently looking at a node this is now gone, change the view.
  1522. if ((currentNode != null) && (IsNodeViewable(currentNode) == false)) { currentNode = null; go(2); }
  1523. // Change the reference to the current node
  1524. if (currentNode != null) { currentNode = getNodeFromId(currentNode._id); if (currentNode != null) { gotoDevice(currentNode._id, xxcurrentView, true); } else { go(2); } }
  1525. // Update device paging
  1526. devicePagingState = (message.totalcount == null) ? null : { total: message.totalcount, skip: message.skip, limit: message.limit };
  1527. updateDevicePageState();
  1528. //onSortSelectChange();
  1529. //onSearchInputChanged();
  1530. mainUpdate(4);
  1531. //refreshMap(false, true);
  1532. if (xxcurrentView == 0) { if ('{{viewmode}}' != '') { go(parseInt('{{viewmode}}')); } else { setDialogMode(0); go(2); } }
  1533. if ('{{currentNode}}' != '') { gotoDevice('{{currentNode}}', parseInt('{{viewmode}}')); }
  1534. break;
  1535. }
  1536. case 'powertimeline': {
  1537. if (message.nodeid != powerTimelineReq) break;
  1538. powerTimelineNode = message.nodeid;
  1539. powerTimeline = message.timeline;
  1540. powerTimelineUpdate = Date.now() + 300000; // Update every 5 minutes
  1541. for (var i in powerTimeline) { if (i % 2 == 1) { powerTimeline[i] = powerTimeline[i] * 1000; } } // Decompress time
  1542. if (currentNode._id == message.nodeid) { drawDeviceTimeline(); }
  1543. break;
  1544. }
  1545. case 'getsysinfo': {
  1546. if (message.nodeid != powerTimelineReq) break;
  1547. if (message.noinfo === true) {
  1548. updateDeviceDetails(getNodeFromId(message.nodeid));
  1549. } else {
  1550. message.hardware.time = message.time;
  1551. updateDeviceDetails(getNodeFromId(message.nodeid), message.hardware);
  1552. }
  1553. break;
  1554. }
  1555. case 'lastconnect': {
  1556. var node = getNodeFromId(message.nodeid);
  1557. if (node != null) {
  1558. node.lastconnect = message.time;
  1559. node.lastaddr = message.addr;
  1560. }
  1561. break;
  1562. }
  1563. case 'msg': {
  1564. // Check if this is a message from a node
  1565. if (message.nodeid != null) {
  1566. var index = -1;
  1567. if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == message.nodeid) { index = i; break; } } }
  1568. if (index != -1) {
  1569. if (message.type == 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message.
  1570. else if (message.type == 'notify') { // This is a notification message.
  1571. var n = getstore('notifications', 0);
  1572. if (((n & 8) == 0) && (message.amtMessage != null)) { break; } // Intel AMT desktop & terminal messages should be ignored.
  1573. var n = { text: message.value, title: message.title, icon: message.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
  1574. if (message.id != null) { n.id = message.id; }
  1575. if (message.nodeid != null) { n.nodeid = message.nodeid; }
  1576. if (message.tag != null) { n.tag = message.tag; }
  1577. if (message.url != null) { n.url = message.url; }
  1578. if (message.username != null) { n.username = message.username; }
  1579. if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
  1580. addNotification(n);
  1581. } else if ((message.type == 'userSessions') && (currentNode != null) && (currentNode._id == message.nodeid) && (desktop == null)) {
  1582. // Got list of user sessions
  1583. var userSessions = [];
  1584. if (message.data != null) { for (var i in message.data) { if ((message.data[i].State == 'Active') || (message.data[i].StationName == 'Console') || (debugmode == 3)) { userSessions.push(message.data[i]); } } }
  1585. if (userSessions.length == 0) { connectDesktop(null, 1, null, message.tag); } // No active sessions, do a normal connection.
  1586. else if (userSessions.length == 1) { connectDesktop(null, 1, userSessions[0].SessionId, message.tag); } // One active session, connect to it
  1587. else {
  1588. var x = '';
  1589. var sortBy = "{{{userSessionsSort}}}";
  1590. if (sortBy != '') {
  1591. userSessions.sort(function(a, b) {
  1592. if (!a[sortBy]) return -1; // a comes before b
  1593. if (!b[sortBy]) return 1; // b comes before a
  1594. if (a[sortBy] < b[sortBy]) return -1;
  1595. if (a[sortBy] > b[sortBy]) return 1;
  1596. return 0;
  1597. });
  1598. }
  1599. for (var i in userSessions) {
  1600. x += '<div style="text-align:left;cursor:pointer;background-color:gray;margin:5px;padding:5px;border-radius:5px" onclick=connectDesktop(event,1,' + userSessions[i].SessionId + ',' + message.tag + ')>' + userSessions[i].State + ', ' + userSessions[i].StationName;
  1601. if (userSessions[i].Username) { if (userSessions[i].Domain) { x += ' - ' + userSessions[i].Domain + '/' + userSessions[i].Username; } else { x += ' - ' + userSessions[i].Username; } }
  1602. x += '</div>';
  1603. }
  1604. QH('p11DeskSessionSelector', x);
  1605. QV('p11DeskSessionSelector', true);
  1606. }
  1607. }
  1608. }
  1609. } else {
  1610. if (message.type == 'notify') { // This is a notification message.
  1611. var n = { text: message.value, title: message.title, icon: message.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
  1612. if (message.id != null) { n.id = message.id; }
  1613. if (message.tag != null) { n.tag = message.tag; }
  1614. if (message.url != null) { n.url = message.url; }
  1615. if (message.username != null) { n.username = message.username; }
  1616. if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
  1617. addNotification(n);
  1618. }
  1619. }
  1620. break;
  1621. }
  1622. case 'getnetworkinfo': {
  1623. if (currentNode._id != message.nodeid) return;
  1624. updateDeviceDetails(getNodeFromId(message.nodeid), null, message);
  1625. break;
  1626. }
  1627. case 'getNotes': {
  1628. var n = Q('d2devNotes');
  1629. if (n && (message.id == decodeURIComponent(n.attributes['noteid'].value))) {
  1630. if (message.notes) { QH('d2devNotes', decodeURIComponent(message.notes)); } else { QH('d2devNotes', ''); }
  1631. var ro = (n.attributes['ro'].value == 'true');
  1632. if (ro == false) { // If we have permissions, set read/write on this note.
  1633. n.removeAttribute('readonly');
  1634. QE('idx_dlgOkButton', true);
  1635. QV('idx_dlgOkButton', true);
  1636. focusTextBox('d2devNotes');
  1637. }
  1638. }
  1639. break;
  1640. }
  1641. case 'otpauth-request': {
  1642. if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-request')) {
  1643. if (message.err != null) {
  1644. var otpauthErrors = ['', "2FA is locked", "Backup codes are locked", "Login token in use", "OTP 2FA not allowed", "Account is locked", "Unable to load OTPLIB"];
  1645. if ((message.err > 0) && (message.err < otpauthErrors.length)) { QH('d2optinfo', otpauthErrors[message.err]); } else { QH('d2optinfo', format("Error #{0}", message.err)); }
  1646. } else {
  1647. var secret = message.secret;
  1648. if (secret.length == 52) { secret = secret.split(/(.............)/).filter(Boolean).join(' '); }
  1649. else if (secret.length == 32) { secret = secret.split(/(....)/).filter(Boolean).join(' '); secret = secret.substring(0, 20) + '<br/>' + secret.substring(20) }
  1650. QH('d2optinfo', format("Install" + ' <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2" rel="noreferrer noopener" target=_blank>' + "Google Authenticator" + '</a> ' + "or a compatible application, use <a href=\"{0}\" rel=\"noreferrer noopener\" target=_blank> this link</a> or enter the secret below. Then, enter the current 6 digit token to activate 2-Step login.", message.url) + '<br /><br /><div style=width:100%;text-align:center><tt id=d2optsecret secret="' + message.secret + '" style=font-size:15px>' + secret + '</tt><br /><br />Token: <input type=text autocomplete="one-time-code" inputmode="numeric" pattern="[0-9]*" onkeypress=\"return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)\" onkeyup=account_addOtpCheck(event) onkeydown=account_addOtpCheck() maxlength=6 id=d2otpauthinput type=text></div>');
  1651. QV('idx_dlgOkButton', true);
  1652. QE('idx_dlgOkButton', false);
  1653. Q('d2otpauthinput').focus();
  1654. }
  1655. }
  1656. break;
  1657. }
  1658. case 'otpauth-setup': {
  1659. if (xxdialogMode) return;
  1660. setDialogMode(2, "Authenticator App", 1, null, message.success ? ('<b style=color:green>' + "Authenticator app activation successful." + '</b> ' + "You will now need a valid token to login again.") : ('<b style=color:red>' + "2-step login activation failed." + '</b> ' + "Clear the secret from the application and try again. You only have a few minutes to enter the proper code."));
  1661. break;
  1662. }
  1663. case 'otpauth-clear': {
  1664. if (xxdialogMode) return;
  1665. setDialogMode(2, "Authenticator App", 1, null, message.success ? ('<b>' + "Authenticator application removed." + '</b> ' + "You can reactivate this feature at any time.") : ('<b style=color:red>' + "2-step login activation removal failed." + '</b> ' + "Try again."));
  1666. break;
  1667. }
  1668. case 'otpauth-getpasswords': {
  1669. if (xxdialogMode) return;
  1670. var x = "One time tokens can be used as secondary authentication. Generate a set, print them and keep them in a safe place.";
  1671. x += '<div style=\'border-radius:6px;border: 2px dashed #888;width:100%;margin-top:8px\'><div style=\'padding:8px;font-family:Arial, Helvetica, sans-serif;font-size:20px;font-weight:bold\'><table style=width:100%;text-align:center>';
  1672. if (message.passwords) {
  1673. var j = 0;
  1674. for (var i in message.passwords) {
  1675. if (++j % 2) { x += '<tr>'; }
  1676. var p = '' + message.passwords[i].p;
  1677. while (p.length < 8) { p = '0' + p; }
  1678. if (message.passwords[i].u === true) { x += '<td>' + p.substring(0, 4) + '&nbsp;' + p.substring(4); } else { x += '<td><strike style=color:#BBB>' + p.substring(0, 4) + '&nbsp;' + p.substring(4); + '</strike>'; }
  1679. }
  1680. } else {
  1681. x += '<tr><td>' + "No Active Tokens";
  1682. }
  1683. x += '</table></div></div><br />';
  1684. x += '<div><input type=button value=\'' + "Close" + '\' onclick=setDialogMode(0) style=float:right></input>';
  1685. x += '<input type=button value=\'' + "New Tokens" + '\' onclick=\'account_manageOtp(1);\'></input>';
  1686. if (message.passwords != null) { x += '<input type=button value=\'' + "Clear" + '\' onclick=\'account_manageOtp(2);\'></input>'; }
  1687. x += '</div><br />';
  1688. setDialogMode(2, "Manage Backup Codes", 8, null, x, 'otpauth-manage');
  1689. break;
  1690. }
  1691. case 'verifyPhone': {
  1692. if (xxdialogMode && (xxdialogTag != 'verifyPhone')) return;
  1693. var x = '<table><tr><td><img src="images/phone80.png" style=padding:8px>';
  1694. x += '<td>Check your phone and enter the verification code.';
  1695. x += '<br /><br /><div style=width:100%;text-align:center>' + "Verification code:" + ' <input type=tel pattern="[0-9]" inputmode="number" maxlength=6 id=d2phoneCodeInput onKeyUp=account_managePhoneCodeValidate() onkeypress="if (event.key==\'Enter\') account_managePhoneCodeValidate(1)"></div></table>';
  1696. setDialogMode(2, "Phone Notifications", 3, account_managePhoneConfirm, x, message.cookie);
  1697. Q('d2phoneCodeInput').focus();
  1698. account_managePhoneCodeValidate();
  1699. break;
  1700. }
  1701. case 'previousLogins': {
  1702. if ((xxdialogMode == 2) && (xxdialogTag == 'previousLogins')) {
  1703. var x = '', c = 'BBB', xx = '';
  1704. if (message.events.length == 0) {
  1705. x += 'No previous login.';
  1706. } else {
  1707. x += '<div style=max-height:260px;overflow-y:scroll;overflow-x:hidden>';
  1708. for (var i in message.events) {
  1709. var m = message.events[i].m;
  1710. if (m == 107) { m = "Valid login"; c = 'BBD1BB'; xx = ''; }
  1711. else if (m == 108) { m = "Invalid 2FA"; c = 'DD9DC3'; xx = 'x'; }
  1712. else if (m == 109) { m = "Locked account"; c = 'E1BBBB'; xx = 'x'; }
  1713. else if (m == 110) { m = "Invalid password"; c = 'E1BBBB'; xx = 'x'; }
  1714. x += '<div style=width:260px;background-color:#' + c + ';border-radius:6px;margin-bottom:4px;padding:4px><div><b>' + EscapeHtml(m) + '</b><br />' + printDateTime(new Date(message.events[i].t)) + '</div><div style=font-size:x-small>' + EscapeHtml(message.events[i].a.join(', ')) + '</div></div></tr>';
  1715. }
  1716. x += '</div>';
  1717. }
  1718. setDialogMode(2, "Previous Logins", 1, null, x);
  1719. }
  1720. break;
  1721. }
  1722. case 'event': {
  1723. /*
  1724. if (!message.event.nolog) {
  1725. events.unshift(message.event);
  1726. var eventLimit = parseInt(p3limitdropdown.value);
  1727. while (events.length > eventLimit) { events.pop(); } // Remove element(s) at the end
  1728. events_update();
  1729. }
  1730. */
  1731. if (message.event.noact) break; // Take no action on this event
  1732. switch (message.event.action) {
  1733. case 'serverinfochange': {
  1734. if (message.event.lock2factor != null) { serverinfo.lock2factor = message.event.lock2factor; updateSelf(); }
  1735. break;
  1736. }
  1737. case 'userWebState': {
  1738. // New user web state, update the web page as needed
  1739. if (localStorage != null) {
  1740. var webstate = JSON.parse(message.event.state);
  1741. for (var i in webstate) { localStorage.setItem(i, webstate[i]); }
  1742. // Update stars
  1743. if (webstate.stars != null) { stars = JSON.parse(webstate.stars); }
  1744. // Update the web page
  1745. if ((webstate.loctag != null) && (webstate.loctag != oldLoctag)) {
  1746. if (webstate.loctag != null) { args.locale = webstate.loctag; } else { delete args.locale; }
  1747. mainUpdate(4 + 128);
  1748. } else if (webstate.stars != null) {
  1749. mainUpdate(4);
  1750. if (Q('SearchInput').value == '*') { onSearchInputChanged(); }
  1751. }
  1752. if (currentNode) { refreshDevice(currentNode._id); }
  1753. // Set the user's desktop shortcut keys
  1754. if (webstate.deskKeyShortcuts != null) {
  1755. deskKeyboardShortcuts = [];
  1756. var deskKeyboardShortcutsStr = webstate.deskKeyShortcuts.split(',');
  1757. for (var i in deskKeyboardShortcutsStr) { deskKeyboardShortcuts.push(parseInt(deskKeyboardShortcutsStr[i])); }
  1758. updateDeskShortcutKeys();
  1759. }
  1760. }
  1761. break;
  1762. }
  1763. case 'accountchange': {
  1764. // An account was created or changed
  1765. if ((typeof message.event.account != 'object') || (message.event.account == null)) { console.log(message.event); return; };
  1766. if (userinfo.name == message.event.account.name) {
  1767. var newsiteadmin = message.event.account.siteadmin ? message.event.account.siteadmin : 0;
  1768. var oldsiteadmin = userinfo.siteadmin ? userinfo.siteadmin : 0;
  1769. if ((message.event.account.quota != userinfo.quota) || (((userinfo.siteadmin & 8) == 0) && ((message.event.account.siteadmin & 8) != 0))) { meshserver.send({ action: 'files' }); }
  1770. userinfo = message.event.account;
  1771. //if (oldsiteadmin != newsiteadmin) updateSiteAdmin();
  1772. updateSelf();
  1773. // If our list of nodes may have changes, request the new list now.
  1774. if (message.event.nodeListChange == userinfo._id) { meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip }); }
  1775. }
  1776. break;
  1777. }
  1778. case 'createusergroup':
  1779. case 'usergroupchange': {
  1780. // User group changed
  1781. if (usergroups == null) { usergroups = {}; }
  1782. var ugroup = usergroups[message.event.ugrpid];
  1783. if (ugroup == null) {
  1784. // This is a new user group for us
  1785. usergroups[message.event.ugrpid] = { _id: message.event.ugrpid, name: message.event.name, desc: message.event.desc, domain: message.event.domain, links: message.event.links };
  1786. } else {
  1787. // This is an existing user group
  1788. ugroup.name = message.event.name;
  1789. ugroup.desc = message.event.desc;
  1790. ugroup.links = message.event.links;
  1791. ugroup.flags = message.event.flags;
  1792. }
  1793. //mainUpdate(8192 + 16384);
  1794. // Group update, refresh all our device groups and nodes. TODO: Optimize this to only do this when needed.
  1795. meshserver.send({ action: 'meshes' });
  1796. meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip });
  1797. break;
  1798. }
  1799. case 'deleteusergroup': {
  1800. // User group removed
  1801. if ((usergroups != null) && (usergroups[message.event.ugrpid] != null)) {
  1802. delete usergroups[message.event.ugrpid];
  1803. var c = 0;
  1804. for (var i in usergroups) { c++; }
  1805. if (c == 0) { usergroups = null; } // If user groups is empty, set it to null.
  1806. //mainUpdate(8192 + 16384);
  1807. }
  1808. break;
  1809. }
  1810. case 'createmesh': {
  1811. // A new mesh was created
  1812. if ((meshes[message.event.meshid] == null) && ((userinfo.manageAllDeviceGroups) || (message.event.mesh.links[userinfo._id] != null))) { // Check if this is a mesh create for a mesh we own. If site administrator, we get all messages so need to ignore some.
  1813. meshes[message.event.meshid] = message.event.mesh;
  1814. mainUpdate(4 + 128);
  1815. meshserver.send({ action: 'files' });
  1816. }
  1817. break;
  1818. }
  1819. case 'meshchange': {
  1820. // Update mesh information
  1821. if (meshes[message.event.meshid] == null) {
  1822. // Check if we have any access to this device group
  1823. var add = false;
  1824. if (message.event.links[userinfo._id] != null) { add = true; }
  1825. if (userinfo.links[message.event.meshid] != null) { add = true; }
  1826. for (var i in userinfo.links) { if ((i.startsWith('ugrp/')) && (message.event.links[i] != null)) { add = true; } }
  1827. // This is a new mesh for us
  1828. if (add) {
  1829. meshes[message.event.meshid] = { _id: message.event.meshid, name: message.event.name, mtype: message.event.mtype, desc: message.event.desc, links: message.event.links, relayid: message.event.relayid };
  1830. meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip }); // Request a refresh of all nodes (TODO: We could optimize this to only request nodes for the new mesh).
  1831. }
  1832. } else {
  1833. // This is an existing mesh
  1834. if (meshes[message.event.meshid].name != message.event.name) {
  1835. meshes[message.event.meshid].name = message.event.name;
  1836. for (var i in nodes) { if (nodes[i].meshid == message.event.meshid) { nodes[i].meshnamel = message.event.name.toLowerCase(); } }
  1837. }
  1838. meshes[message.event.meshid].desc = message.event.desc;
  1839. meshes[message.event.meshid].links = message.event.links;
  1840. if (message.event.relayid != null) { meshes[message.event.meshid].relayid = message.event.relayid; }
  1841. // Check if we lost rights to this mesh in this change.
  1842. if (IsMeshViewable(message.event.meshid) == false) {
  1843. if ((xxcurrentView == 20) && (currentMesh == meshes[message.event.meshid])) go(2);
  1844. delete meshes[message.event.meshid];
  1845. // Delete all nodes in that mesh, except ones with direct links
  1846. var newnodes = [];
  1847. for (var i in nodes) { if ((nodes[i].meshid != message.event.meshid) || ((userinfo.links != null) && (userinfo.links[nodes[i]._id] != null))) { newnodes.push(nodes[i]); } }
  1848. nodes = newnodes;
  1849. // If we are looking at a node in the deleted mesh, move back to "My Devices"
  1850. if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && !IsNodeViewable(currentNode)) { setDialogMode(0); go(2); currentNode = null; }
  1851. }
  1852. }
  1853. mainUpdate(4 + 128);
  1854. meshserver.send({ action: 'files' });
  1855. // If we are looking at a mesh that is now deleted, move back to "My Account"
  1856. if (xxcurrentView == 20 && currentMesh._id == message.event.meshid) { p20updateMesh(); }
  1857. break;
  1858. }
  1859. case 'deletemesh': {
  1860. // Delete the mesh
  1861. if (meshes[message.event.meshid]) {
  1862. delete meshes[message.event.meshid];
  1863. updateMeshes();
  1864. meshserver.send({ action: 'files' });
  1865. }
  1866. // Delete all nodes in that mesh
  1867. var newnodes = [];
  1868. for (var i in nodes) { if (nodes[i].meshid != message.event.meshid) { newnodes.push(nodes[i]); } }
  1869. nodes = newnodes;
  1870. mainUpdate(4);
  1871. // If we are looking at a mesh that is now deleted, move back to "My Account"
  1872. if (xxcurrentView >= 20 && xxcurrentView < 30 && currentMesh._id == message.event.meshid) { setDialogMode(0); go(2); }
  1873. // If we are looking at a node in the deleted mesh, move back to "My Devices"
  1874. if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && !IsNodeViewable(currentNode)) { setDialogMode(0); go(2); }
  1875. break;
  1876. }
  1877. case 'addnode': {
  1878. var node = message.event.node;
  1879. if (!meshes[node.meshid]) break; // This is a node for a mesh we don't know. Happens when we are site administrator, we get all messages.
  1880. if (getNodeFromId(node._id) != null) break; // This node is already known.
  1881. node.namel = node.name.toLowerCase();
  1882. if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
  1883. node.meshnamel = meshes[node.meshid]?meshes[node.meshid].name.toLowerCase():'*';
  1884. node.state = 0;
  1885. if (!node.icon) node.icon = 1;
  1886. node.ident = ++nodeShortIdent;
  1887. nodes.push(node);
  1888. //onSortSelectChange();
  1889. //onSearchInputChanged();
  1890. mainUpdate(4);
  1891. //updateMapMarkers();
  1892. break;
  1893. }
  1894. case 'removenode': {
  1895. var index = -1;
  1896. for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
  1897. if (index != -1) {
  1898. var node = nodes[index];
  1899. if (currentNode == node) {
  1900. if (xxcurrentView >= 10 && xxcurrentView < 20) { setDialogMode(0); go(2); }
  1901. currentNode = null;
  1902. // TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
  1903. }
  1904. nodes.splice(index, 1);
  1905. mainUpdate(4);
  1906. //updateMapMarkers();
  1907. }
  1908. break;
  1909. }
  1910. case 'changenode': {
  1911. var index = -1;
  1912. for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
  1913. if (index != -1) {
  1914. var node = nodes[index];
  1915. // Change the node
  1916. node.name = message.event.node.name;
  1917. node.rname = message.event.node.rname;
  1918. node.lusers = message.event.node.lusers;
  1919. node.users = message.event.node.users;
  1920. node.host = message.event.node.host;
  1921. node.desc = message.event.node.desc;
  1922. node.publicip = message.event.node.publicip;
  1923. node.iploc = message.event.node.iploc;
  1924. node.wifiloc = message.event.node.wifiloc;
  1925. node.gpsloc = message.event.node.gpsloc;
  1926. node.tags = message.event.node.tags;
  1927. node.ssh = message.event.node.ssh;
  1928. node.rdp = message.event.node.rdp;
  1929. node.userloc = message.event.node.userloc;
  1930. node.rdpport = message.event.node.rdpport;
  1931. node.rfbport = message.event.node.rfbport;
  1932. node.sshport = message.event.node.sshport;
  1933. node.httpport = message.event.node.httpport;
  1934. node.httpsport = message.event.node.httpsport;
  1935. node.consent = message.event.node.consent;
  1936. node.pmt = message.event.node.pmt;
  1937. if (message.event.node.agent != null) {
  1938. if (node.agent == null) node.agent = {};
  1939. if (message.event.node.agent.ver != null) { node.agent.ver = message.event.node.agent.ver; }
  1940. if (message.event.node.agent.id != null) { node.agent.id = message.event.node.agent.id; }
  1941. if (message.event.node.agent.caps != null) { node.agent.caps = message.event.node.agent.caps; }
  1942. if (message.event.node.agent.root != null) { node.agent.root = message.event.node.agent.root; }
  1943. if (message.event.node.agent.core != null) { node.agent.core = message.event.node.agent.core; } else { if (node.agent.core) { delete node.agent.core; } }
  1944. node.agent.tag = message.event.node.agent.tag;
  1945. }
  1946. if (message.event.node.intelamt != null) {
  1947. if (node.intelamt == null) node.intelamt = {};
  1948. if (message.event.node.intelamt.state != null) { node.intelamt.state = message.event.node.intelamt.state; }
  1949. if (message.event.node.intelamt.host != null) { node.intelamt.user = message.event.node.intelamt.host; }
  1950. if (message.event.node.intelamt.user != null) { node.intelamt.user = message.event.node.intelamt.user; }
  1951. if (message.event.node.intelamt.tls != null) { node.intelamt.tls = message.event.node.intelamt.tls; }
  1952. if (message.event.node.intelamt.ver != null) { node.intelamt.ver = message.event.node.intelamt.ver; }
  1953. if (message.event.node.intelamt.tag != null) { node.intelamt.tag = message.event.node.intelamt.tag; }
  1954. if (message.event.node.intelamt.uuid != null) { node.intelamt.uuid = message.event.node.intelamt.uuid; }
  1955. if (message.event.node.intelamt.realm != null) { node.intelamt.realm = message.event.node.intelamt.realm; }
  1956. if (message.event.node.intelamt.flags != null) { node.intelamt.flags = message.event.node.intelamt.flags; }
  1957. if (message.event.node.intelamt.warn != null) { node.intelamt.warn = message.event.node.intelamt.warn; } else { delete node.intelamt.warn; }
  1958. }
  1959. if (message.event.node.av != null) { node.av = message.event.node.av; }
  1960. if (message.event.node.wsc != null) { node.wsc = message.event.node.wsc; }
  1961. node.namel = node.name.toLowerCase();
  1962. if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
  1963. if (message.event.node.icon) { node.icon = message.event.node.icon; }
  1964. if (message.event.node.lastbootuptime != null) { node.lastbootuptime = message.event.node.lastbootuptime; }
  1965. //onSortSelectChange(true);
  1966. //drawNotifications();
  1967. refreshDevice(node._id);
  1968. updateDeviceViewDevice(node);
  1969. if (currentNode == node) { updateDeviceDetails(); }
  1970. //if ((currentNode == node) && (xxdialogMode != null) && (xxdialogTag == '@xxmap')) { p10showNodeLocationDialog(); }
  1971. }
  1972. break;
  1973. }
  1974. case 'nodemeshchange': {
  1975. var index = -1;
  1976. for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
  1977. if (index != -1) {
  1978. var node = nodes[index];
  1979. if ((meshes[message.event.newMeshId] == null) && ((userinfo.links == null) || (userinfo.links[node._id] == null))) {
  1980. // We don't see the new mesh, remove this device
  1981. // TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
  1982. if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && !IsNodeViewable(currentNode)) { setDialogMode(0); go(2); currentNode = null; }
  1983. nodes.splice(index, 1);
  1984. } else {
  1985. // We see the new mesh, move this device
  1986. node.meshid = message.event.newMeshId;
  1987. node.meshnamel = meshes[message.event.newMeshId]?meshes[message.event.newMeshId].name.toLowerCase():'*';
  1988. }
  1989. mainUpdate(4);
  1990. refreshDevice(message.event.nodeid);
  1991. } else {
  1992. // This is a new device, add it.
  1993. var node = message.event.node;
  1994. if (!meshes[node.meshid]) break; // This is a node for a mesh we don't know. Happens when we are site administrator, we get all messages.
  1995. node.namel = node.name.toLowerCase();
  1996. if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
  1997. node.meshnamel = meshes[node.meshid]?meshes[node.meshid].name.toLowerCase():'*';
  1998. node.state = 0;
  1999. if (!node.icon) node.icon = 1;
  2000. node.ident = ++nodeShortIdent;
  2001. if (nodes == null) { }
  2002. nodes.push(node);
  2003. // Web page update
  2004. //mainUpdate(1 | 2 | 4 | 16);
  2005. mainUpdate(4);
  2006. }
  2007. break;
  2008. }
  2009. case 'nodeconnect': {
  2010. // Indicated a node has changed connectivity state
  2011. var index = -1;
  2012. for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
  2013. if (index != -1) {
  2014. var node = nodes[index];
  2015. // Change the node connection state
  2016. node.conn = message.event.conn;
  2017. node.pwr = message.event.pwr;
  2018. // Clear sesssion information if needed
  2019. if ((node.conn & 1) == 0) { delete node.sessions; }
  2020. refreshDevice(node._id);
  2021. updateDeviceViewDevice(node);
  2022. }
  2023. break;
  2024. }
  2025. case 'login': {
  2026. // Update the last login time
  2027. if (users != null && users['user/' + domain + '/' + message.event.username.toLowerCase()]) { users['user/' + domain + '/' + message.event.username.toLowerCase()].login = message.event.time; }
  2028. break;
  2029. }
  2030. case 'notify': {
  2031. var n = { text: message.event.value, title: message.event.title, icon: message.event.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
  2032. if (message.id != null) { n.id = message.id; }
  2033. if (message.event.tag != null) { n.tag = message.event.tag; }
  2034. if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
  2035. addNotification(n);
  2036. break;
  2037. }
  2038. case 'sysinfohash': {
  2039. // If the sysinfo document has changed and we are looking at it, request an update.
  2040. if ((currentNode != null) && (message.event.nodeid == powerTimelineReq)) { meshserver.send({ action: 'getsysinfo', nodeid: message.event.nodeid }); }
  2041. break;
  2042. }
  2043. case 'ifchange': {
  2044. // Network interface changed for a device, if we are currently viewing this device, ask for an update.
  2045. if ((currentNode != null) && (currentNode._id == message.event.nodeid)) { meshserver.send({ action: 'getnetworkinfo', nodeid: currentNode._id }); }
  2046. break;
  2047. }
  2048. case 'devicesessions': {
  2049. // List of sessions for a given device
  2050. var node = getNodeFromId(message.event.nodeid);
  2051. if (node == null) break; // Unknown node
  2052. node.sessions = message.event.sessions;
  2053. if (node.sessions != null) {
  2054. for (var i in node.sessions) { if (Object.keys(node.sessions[i]).length == 0) { delete node.sessions[i]; } }
  2055. if (Object.keys(node.sessions).length == 0) { delete node.sessions; }
  2056. }
  2057. refreshDevice(message.event.nodeid);
  2058. updateDeviceViewDevice(node);
  2059. //if ((currentNode != null) && (currentNode._id == message.event.nodeid)) { gotoDevice(currentNode._id, xxcurrentView, true); }
  2060. // If we are looking at the sessions dialog box for this device now, update it
  2061. if (xxdialogTag == ('SESSIONS-' + message.event.nodeid)) { showDeviceSessions(message.event.nodeid, true); }
  2062. //if (xxdialogTag == ('MESSAGES-' + message.event.nodeid)) { showDeviceMessages(message.event.nodeid, true); }
  2063. if (xxdialogTag == ('HELPREQ-' + message.event.nodeid)) { showDeviceHelpRequests(message.event.nodeid, true); }
  2064. break;
  2065. }
  2066. case 'stopped': { // Server is stopping.
  2067. // TODO: Disconnect
  2068. break;
  2069. }
  2070. default:
  2071. //console.log('Unknown message.event.action', message.event.action);
  2072. break;
  2073. }
  2074. break;
  2075. }
  2076. case 'getcookie': {
  2077. if (message.tag == 'novnc') {
  2078. var vncurl = window.location.origin + domainUrl + 'novnc/vnc.html?ws=wss%3A%2F%2F' + window.location.host + encodeURIComponentEx(domainUrl) + (message.localRelay?'local':'mesh') + 'relay.ashx%3Fauth%3D' + message.cookie + '&show_dot=1' + (urlargs.key?('&key=' + urlargs.key):'') + '&l={{{lang}}}';
  2079. var node = getNodeFromId(message.nodeid);
  2080. if (node != null) { vncurl += '&name=' + encodeURIComponentEx(node.name); }
  2081. safeNewWindow(vncurl, 'mcnovnc/' + message.nodeid);
  2082. } else if (message.tag == 'mstsc') {
  2083. var rdpurl = window.location.origin + domainUrl + 'mstsc.html?ws=' + message.cookie + (urlargs.key?('&key=' + urlargs.key):'');
  2084. var node = getNodeFromId(message.nodeid);
  2085. if (node != null) { rdpurl += '&name=' + encodeURIComponentEx(node.name); }
  2086. if (message.localRelay) { rdpurl += '&local=1'; }
  2087. safeNewWindow(rdpurl, 'mcmstsc/' + message.nodeid);
  2088. } else if (message.tag == 'ssh') {
  2089. var sshurl = window.location.origin + domainUrl + 'ssh.html?ws=' + message.cookie + (urlargs.key?('&key=' + urlargs.key):'');
  2090. var node = getNodeFromId(message.nodeid);
  2091. if (node != null) { sshurl += '&name=' + encodeURIComponentEx(node.name); }
  2092. if (message.localRelay) { sshurl += '&local=1'; }
  2093. safeNewWindow(sshurl, 'mcssh/' + message.nodeid);
  2094. }
  2095. break;
  2096. }
  2097. default:
  2098. //console.log('Unknown message.action', message.action);
  2099. break;
  2100. }
  2101. }
  2102. // To boost the speed of the web page when even floods occur, this method perform a delayed update on the web page.
  2103. var updateNaggleTimer = null;
  2104. var updateNaggleFlags = 0;
  2105. function mainUpdate(flags) {
  2106. updateNaggleFlags |= flags;
  2107. if (updateNaggleTimer == null) {
  2108. updateNaggleTimer = setTimeout(function () {
  2109. if (updateNaggleFlags & 1) { onSearchInputChanged(); }
  2110. if (updateNaggleFlags & 4) { updateDevices(); updateDeviceDetails(); }
  2111. if (updateNaggleFlags & 128) { updateMeshes(); }
  2112. updateNaggleTimer = null;
  2113. updateNaggleFlags = 0;
  2114. gotoStartViewPage();
  2115. }, 150);
  2116. }
  2117. }
  2118. // Go to the correct starting view page
  2119. function gotoStartViewPage() {
  2120. var xviewmode = parseInt('{{viewmode}}');
  2121. if (xxcurrentView > 1) return;
  2122. if ('{{currentNode}}'.toLowerCase() != '') { // The .toLowerCase here is the minifier will not optimize this out.
  2123. if (getNodeFromId('{{currentNode}}') == null) return; // This node is not loaded yet
  2124. gotoDevice('{{currentNode}}', xviewmode);
  2125. } else if (args.gotonode != null) {
  2126. if (args.gotonode.length == 96) { args.gotonode = btoa(hex2rstr(args.gotonode)).split('+').join('@').split('/').join('$'); } // This is a HEX encoded NodeID, convert it to Base64
  2127. if (getNodeFromId('node/' + domain + '/' + args.gotonode) == null) return; // This node is not loaded yet
  2128. if (args.panel) { currentDevicePanel = parseInt(args.panel); }
  2129. gotoDevice('node/' + domain + '/' + args.gotonode, xviewmode);
  2130. } else if (args.gotodevicename != null) {
  2131. var foundNode = null;
  2132. if (nodes != null) { for (var i in nodes) { if (nodes[i].name == args.gotodevicename) { foundNode = nodes[i]._id; } } }
  2133. if (foundNode) { gotoDevice(foundNode, xviewmode); go(xviewmode); }
  2134. } else if (args.gotodevicername != null) {
  2135. var foundNode = null;
  2136. if (nodes != null) { for (var i in nodes) { if (nodes[i].rname == args.gotodevicername) { foundNode = nodes[i]._id; } } }
  2137. if (foundNode) { gotoDevice(foundNode, xviewmode); goBackStack.push(1); }
  2138. } else if (args.gotodeviceip != null) {
  2139. var foundNode = null;
  2140. if (nodes != null) { for (var i in nodes) { if (nodes[i].ip == args.gotodeviceip) { foundNode = nodes[i]._id; } } }
  2141. if (foundNode) { gotoDevice(foundNode, xviewmode); go(xviewmode); }
  2142. } else if (args.gotomesh != null) {
  2143. if (meshes['mesh/' + domain + '/' + args.gotomesh] == null) return; // This device group is not loaded yet
  2144. gotoMesh('mesh/' + domain + '/' + args.gotomesh);
  2145. go(xviewmode);
  2146. } else if (!isNaN(xviewmode)) {
  2147. go(xviewmode);
  2148. } else {
  2149. setDialogMode(0);
  2150. go(1);
  2151. }
  2152. delete args.gotonode;
  2153. delete args.gotomesh;
  2154. delete args.panel;
  2155. if (xxcurrentView < 2) { go(2); }
  2156. }
  2157. //
  2158. // Menu System
  2159. //
  2160. function topMenu(select) {
  2161. if ((xxdialogMode != null) && (xxdialogMode != 0) && (xxdialogMode != 999)) return;
  2162. if (select === undefined) {
  2163. var x = (QS('topMenu').display == 'none');
  2164. if (x == true) { if ((xxdialogMode == 0) || (xxdialogMode == null)) { QV('topMenu', true); xxdialogMode = 999; } } else { QV('topMenu', false); xxdialogMode = 0; }
  2165. } else {
  2166. QV('topMenu', false);
  2167. xxdialogMode = 0;
  2168. if ((select == 1) && (xxcurrentView != 3)) { goForward('account'); } // My Account
  2169. if ((select == 2) && (xxcurrentView != 5)) { goForward('files'); } // My Files
  2170. }
  2171. }
  2172. var backStack = [];
  2173. function goBack() { if (xxdialogMode) return; if (backStack.length > 0) { backStack.pop(); } goStack(); }
  2174. function goForward(id) { if (xxdialogMode) return; backStack.push(id); goStack(); }
  2175. function goStack() {
  2176. if (backStack.length == 0) { go(2); return; }
  2177. var id = backStack[backStack.length - 1], idtype = id.split('/')[0];
  2178. if (idtype == 'node') { setupDeviceMenu(0); gotoDevice(id); }
  2179. if (idtype == 'mesh') { gotoMesh(id); }
  2180. if (idtype == 'account') { go(3); }
  2181. if (idtype == 'devices') { go(2); }
  2182. if (idtype == 'files') {
  2183. // Remind the user to add two factor authentication
  2184. if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || (userinfo.otpduo > 0) || (userinfo.otpdev > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access this feature until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
  2185. go(5);
  2186. }
  2187. }
  2188. function updateFooterMenu(options) {
  2189. while (options != null && options.length < 3) { options.push({ n: '' }); }
  2190. var x = '', prev = '';
  2191. if (options != null) { for (var i in options) { x += '<td style="cursor:pointer' + ((prev == '') ? '' : ';border-left:solid 1px white') + '" onclick="' + options[i].f + '">' + options[i].n; prev = options[i].n; } }
  2192. QH('footerMenu', '<tr>' + x);
  2193. }
  2194. //
  2195. // MY ACCOUNT
  2196. //
  2197. function account_viewPreviousLogins() {
  2198. if (xxdialogMode) return;
  2199. setDialogMode(2, "Previous Logins", 1, null, "Loading...", 'previousLogins');
  2200. meshserver.send({ action: 'previousLogins' });
  2201. }
  2202. function account_manageImage(mode) {
  2203. if (xxdialogMode) return;
  2204. var user = (mode == 0) ? userinfo : currentUser;
  2205. var x = '<input id=p2file type=file style=width:100% accept="image/*" onchange=account_manageImageEx()><div style=width:100%><canvas id=p2canvas width=256 height=256 style="width:256px;height:256px;margin-left:12px;margin-top:8px;border-radius:16px;box-shadow: 0px 0px 15px #000" onclick=account_canvasClick() /></div>';
  2206. setDialogMode(2, "Manage Account Image", 7, account_manageImageEx2, x, user._id);
  2207. var ctx = Q('p2canvas').getContext('2d');
  2208. if (user.accountImageRnd == null) { user.accountImageRnd = Math.floor(Math.random() * 9999999999); }
  2209. var arg = '';
  2210. if (mode == 1) { arg = '&id=' + user._id.split('/')[2]; }
  2211. var myImg = new Image();
  2212. myImg.onload = function () { ctx.clearRect(0, 0, 256, 256); ctx.drawImage(myImg, 0, 0); };
  2213. myImg.src = ((user.flags != null) && (user.flags & 1)) ? ('userimage.ashx?rnd=' + user.accountImageRnd + arg) : 'images/user-256.png';
  2214. QE('idx_dlgDeleteButton', (user.flags != null) && (user.flags & 1));
  2215. QE('idx_dlgOkButton', false);
  2216. }
  2217. function account_canvasClick() { Q('p2file').click(); }
  2218. function account_manageImageEx() {
  2219. var file = Q('p2file').files[0];
  2220. var img = new Image;
  2221. img.onload = function () {
  2222. var cx = 0, cy = 0, min = Math.min(img.width, img.height);
  2223. if (img.width > min) { cx = (img.width - min) / 2; }
  2224. if (img.height > min) { cy = (img.height - min) / 2; }
  2225. var ctx = Q('p2canvas').getContext('2d');
  2226. ctx.imageSmoothingEnabled = true;
  2227. ctx.webkitImageSmoothingEnabled = true;
  2228. ctx.mozImageSmoothingEnabled = true;
  2229. ctx.clearRect(0, 0, 256, 256);
  2230. ctx.drawImage(img, cx, cy, min, min, 0, 0, 256, 256);
  2231. QE('idx_dlgOkButton', true);
  2232. }
  2233. img.src = URL.createObjectURL(file);
  2234. }
  2235. function account_manageImageEx2(b, userid) {
  2236. // Send updated image, or 0 if we pressed the delete button
  2237. meshserver.send({ action: 'updateUserImage', userid: userid, image: (b == 2) ? 0 : Q('p2canvas').toDataURL('image/jpeg', 0.8) });
  2238. //meshserver.send({ action: 'updateUserImage', image: (b == 2)?0:Q('p2canvas').toDataURL('image/png', 0.8) });
  2239. }
  2240. function toggleNightMode() {
  2241. if (xxdialogMode) return;
  2242. var cNightMode = getstore('nightMode', '0');
  2243. var x = '<input type=radio id=night0 name=nightmoderadio value=0 ' + ((cNightMode == 0) ? 'checked' : '') + '><label for=night0>' + "Browser default" + '</label><br>';
  2244. x += '<input type=radio id=night2 name=nightmoderadio value=2 ' + ((cNightMode == 2) ? 'checked' : '') + '><label for=night2>' + "Light mode" + '</label><br>';
  2245. x += '<input type=radio id=night1 name=nightmoderadio value=1 ' + ((cNightMode == 1) ? 'checked' : '') + '><label for=night1>' + "Dark mode" + '</label><br>';
  2246. setDialogMode(2, "Night Mode", 3, toggleNightModeEx, x);
  2247. QV('uiMenu', false);
  2248. }
  2249. function toggleNightModeEx() {
  2250. // Save new night mode
  2251. var nNightMode = '0';
  2252. if (Q('night1').checked) { nNightMode = '1'; }
  2253. if (Q('night2').checked) { nNightMode = '2'; }
  2254. putstore('nightMode', nNightMode);
  2255. setNightMode();
  2256. }
  2257. function setNightMode() {
  2258. // Set night mode
  2259. var nNightMode = getstore('nightMode', '0')
  2260. nightMode = false;
  2261. if ((features2 & 0x00100000) != 0) { nNightMode = '1'; }
  2262. if ((features2 & 0x00200000) != 0) { nNightMode = '2'; }
  2263. if (nNightMode == '1') { nightMode = true; }
  2264. else if ((nNightMode == '0') && (window.matchMedia)) { nightMode = window.matchMedia('(prefers-color-scheme: dark)').matches }
  2265. if (nightMode) { QC('body').add('night'); QS('body')['background-color'] = '#000'; QS('body')['color'] = 'lightgray'; } else { QC('body').remove('night'); QS('body')['background-color'] = '#FFF'; QS('body')['color'] = 'black'; }
  2266. return nightMode;
  2267. }
  2268. function account_managePhone() {
  2269. if (xxdialogMode || ((features & 0x02000000) == 0)) return;
  2270. var x;
  2271. if (userinfo.phone != null) {
  2272. x = '<table style=width:100%><tr><td style=width:56px><img src="images/phone80.png" style=padding:8px>';
  2273. x += '<td style=text-align:center><div style=padding:6px>' + "Verified phone number" + '</div><div style=font-size:20px>' + userinfo.phone + '</div>';
  2274. x += '<div style=margin:10px><label><input id=d2delPhone type=checkbox onclick=account_managePhoneRemoveValidate() />' + "Remove phone number" + '</label></div>';
  2275. setDialogMode(2, "Phone Notifications", 3, account_managePhoneRemove, x);
  2276. account_managePhoneRemoveValidate();
  2277. } else {
  2278. x = '<table style=width:100%><tr><td style=width:56px><img src="images/phone80.png" style=padding:8px>';
  2279. x += '<td>Enter your SMS capable phone number. Once verified, the number may be used for login verification and other notifications.';
  2280. x += '<br /><br /><div style=width:100%;text-align:center>' + "Phone number:" + ' <input type=tel pattern="[0-9]" autocomplete="tel" inputmode="tel" maxlength=18 id=d2phoneinput onKeyUp=account_managePhoneValidate() onkeypress="if (event.key==\'Enter\') account_managePhoneValidate(1)"></div></table>';
  2281. setDialogMode(2, "Phone Notifications", 3, account_managePhoneAdd, x, 'verifyPhone');
  2282. Q('d2phoneinput').focus();
  2283. account_managePhoneValidate();
  2284. }
  2285. }
  2286. function isPhoneNumber(x) { return x.match(/^\(?([0-9]{3,4})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/) }
  2287. function account_managePhoneValidate(x) { var ok = isPhoneNumber(Q('d2phoneinput').value); QE('idx_dlgOkButton', ok); if ((x == 1) && ok) { dialogclose(1); } }
  2288. function account_managePhoneCodeValidate(x) { var ok = (Q('d2phoneCodeInput').value.length == 6) && Q('d2phoneCodeInput').value.match(/[0-9]/); QE('idx_dlgOkButton', ok); if ((x == 1) && ok) { dialogclose(1); } }
  2289. function account_managePhoneConfirm(b, tag) { meshserver.send({ action: 'confirmPhone', code: Q('d2phoneCodeInput').value, cookie: tag }); }
  2290. function account_managePhoneAdd() { if (isPhoneNumber(Q('d2phoneinput').value) == false) return; QE('d2phoneinput', false); meshserver.send({ action: 'verifyPhone', phone: Q('d2phoneinput').value }); }
  2291. function account_managePhoneRemove() { if (Q('d2delPhone').checked) { meshserver.send({ action: 'removePhone' }); } }
  2292. function account_managePhoneRemoveValidate() { QE('idx_dlgOkButton', Q('d2delPhone').checked); }
  2293. function account_manageAuthEmail() {
  2294. if (xxdialogMode || ((features & 0x00800000) == 0)) return;
  2295. var emailU2Fenabled = ((userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
  2296. setDialogMode(2, "Email Authentication", 1, function () {
  2297. if (emailU2Fenabled != Q('email2facheck').checked) { meshserver.send({ action: 'otpemail', enabled: Q('email2facheck').checked }); }
  2298. }, "When enabled, on each login, you will be given the option to receive a login token to you email account for added security." + '<br /><br /><label><input id=email2facheck type=checkbox ' + (emailU2Fenabled ? 'checked' : '') + '/>' + "Enable email two-factor authentication." + '</label>');
  2299. }
  2300. var loclist = { 'af': "Afrikaans", 'sq': "Albanian", 'ar': "Arabic (Standard)", 'ar-dz': "Arabic (Algeria)", 'ar-bh': "Arabic (Bahrain)", 'ar-eg': "Arabic (Egypt)", 'ar-iq': "Arabic (Iraq)", 'ar-jo': "Arabic (Jordan)", 'ar-kw': "Arabic (Kuwait)", 'ar-lb': "Arabic (Lebanon)", 'ar-ly': "Arabic (Libya)", 'ar-ma': "Arabic (Morocco)", 'ar-om': "Arabic (Oman)", 'ar-qa': "Arabic (Qatar)", 'ar-sa': "Arabic (Saudi Arabia)", 'ar-sy': "Arabic (Syria)", 'ar-tn': "Arabic (Tunisia)", 'ar-ae': "Arabic (U.A.E.)", 'ar-ye': "Arabic (Yemen)", 'an': "Aragonese", 'hy': "Armenian", 'as': "Assamese", 'ast': "Asturian", 'az': "Azerbaijani", 'eu': "Basque", 'bg': "Bulgarian", 'be': "Belarusian", 'bn': "Bengali", 'bs': "Bosnian", 'br': "Breton", 'my': "Burmese", 'ca': "Catalan", 'ch': "Chamorro", 'ce': "Chechen", 'zh': "Chinese", 'zh-hk': "Chinese (Hong Kong)", 'zh-cn': "Chinese (PRC)", 'zh-sg': "Chinese (Singapore)", 'zh-tw': "Chinese (Taiwan)", 'cv': "Chuvash", 'co': "Corsican", 'cr': "Cree", 'hr': "Croatian", 'cs': "Czech", 'da': "Danish", 'nl': "Dutch (Standard)", 'nl-be': "Dutch (Belgian)", 'en': "English", 'en-au': "English (Australia)", 'en-bz': "English (Belize)", 'en-ca': "English (Canada)", 'en-ie': "English (Ireland)", 'en-jm': "English (Jamaica)", 'en-nz': "English (New Zealand)", 'en-ph': "English (Philippines)", 'en-za': "English (South Africa)", 'en-tt': "English (Trinidad & Tobago)", 'en-gb': "English (United Kingdom)", 'en-us': "English (United States)", 'en-zw': "English (Zimbabwe)", 'eo': "Esperanto", 'et': "Estonian", 'fo': "Faeroese", 'fa': "Farsi (Persian)", 'fj': "Fijian", 'fi': "Finnish", 'fr': "French (Standard)", 'fr-be': "French (Belgium)", 'fr-ca': "French (Canada)", 'fr-fr': "French (France)", 'fr-lu': "French (Luxembourg)", 'fr-mc': "French (Monaco)", 'fr-ch': "French (Switzerland)", 'fy': "Frisian", 'fur': "Friulian", 'gd': "Gaelic (Scots)", 'gd-ie': "Gaelic (Irish)", 'gl': "Galacian", 'ka': "Georgian", 'de': "German (Standard)", 'de-at': "German (Austria)", 'de-de': "German (Germany)", 'de-li': "German (Liechtenstein)", 'de-lu': "German (Luxembourg)", 'de-ch': "German (Switzerland)", 'el': "Greek", 'gu': "Gujurati", 'ht': "Haitian", 'he': "Hebrew", 'hi': "Hindi", 'hu': "Hungarian", 'is': "Icelandic", 'id': "Indonesian", 'iu': "Inuktitut", 'ga': "Irish", 'it': "Italian (Standard)", 'it-ch': "Italian (Switzerland)", 'ja': "Japanese", 'kn': "Kannada", 'ks': "Kashmiri", 'kk': "Kazakh", 'km': "Khmer", 'ky': "Kirghiz", 'tlh': "Klingon", 'ko': "Korean", 'ko-kp': "Korean (North Korea)", 'ko-kr': "Korean (South Korea)", 'la': "Latin", 'lv': "Latvian", 'lt': "Lithuanian", 'lb': "Luxembourgish", 'mk': "FYRO Macedonian", 'ms': "Malay", 'ml': "Malayalam", 'mt': "Maltese", 'mi': "Maori", 'mr': "Marathi", 'mo': "Moldavian", 'nv': "Navajo", 'ng': "Ndonga", 'ne': "Nepali", 'no': "Norwegian", 'nb': "Norwegian (Bokmal)", 'nn': "Norwegian (Nynorsk)", 'oc': "Occitan", 'or': "Oriya", 'om': "Oromo", 'fa-ir': "Persian/Iran", 'pl': "Polish", 'pt': "Portuguese", 'pt-br': "Portuguese (Brazil)", 'pa': "Punjabi", 'pa-in': "Punjabi (India)", 'pa-pk': "Punjabi (Pakistan)", 'qu': "Quechua", 'rm': "Rhaeto-Romanic", 'ro': "Romanian", 'ro-mo': "Romanian (Moldavia)", 'ru': "Russian", 'ru-mo': "Russian (Moldavia)", 'sz': "Sami (Lappish)", 'sg': "Sango", 'sa': "Sanskrit", 'sc': "Sardinian", 'sd': "Sindhi", 'si': "Singhalese", 'sr': "Serbian", 'sk': "Slovak", 'sl': "Slovenian", 'so': "Somani", 'sb': "Sorbian", 'es': "Spanish", 'es-ar': "Spanish (Argentina)", 'es-bo': "Spanish (Bolivia)", 'es-cl': "Spanish (Chile)", 'es-co': "Spanish (Colombia)", 'es-cr': "Spanish (Costa Rica)", 'es-do': "Spanish (Dominican Republic)", 'es-ec': "Spanish (Ecuador)", 'es-sv': "Spanish (El Salvador)", 'es-gt': "Spanish (Guatemala)", 'es-hn': "Spanish (Honduras)", 'es-mx': "Spanish (Mexico)", 'es-ni': "Spanish (Nicaragua)", 'es-pa': "Spanish (Panama)", 'es-py': "Spanish (Paraguay)", 'es-pe': "Spanish (Peru)", 'es-pr': "Spanish (Puerto Rico)", 'es-es': "Spanish (Spain)", 'es-uy': "Spanish (Uruguay)", 'es-ve': "Spanish (Venezuela)", 'sx': "Sutu", 'sw': "Swahili", 'sv': "Swedish", 'sv-fi': "Swedish (Finland)", 'sv-sv': "Swedish (Sweden)", 'ta': "Tamil", 'tt': "Tatar", 'te': "Teluga", 'th': "Thai", 'tig': "Tigre", 'ts': "Tsonga", 'tn': "Tswana", 'tr': "Turkish", 'tk': "Turkmen", 'uk': "Ukrainian", 'hsb': "Upper Sorbian", 'ur': "Urdu", 've': "Venda", 'vi': "Vietnamese", 'vo': "Volapuk", 'wa': "Walloon", 'cy': "Welsh", 'xh': "Xhosa", 'ji': "Yiddish", 'zu': "Zulu" };
  2301. var loclistex = { 'zh-chs': "Chinese (Simplified)", 'zh-cht': "Chinese (Traditional)" };
  2302. function account_showLocalizationSettings() {
  2303. if (xxdialogMode) return false;
  2304. var n = getstore('loctag', 0), y = '';
  2305. var x = '<select id=d2locselect style=width:180px><option value="*">' + "User browser value" + '</option>';
  2306. for (var i in loclist) { x += '<option value="' + i + '"' + ((n == i)?' selected':'') + '>' + i + ' - ' + loclist[i] + '</option>'; }
  2307. x += '</select>';
  2308. if (serverinfo.languages && serverinfo.languages.length > 0) {
  2309. y += "Changing the language will require a refresh of the page." + '<br /><br />';
  2310. var z = '<select id=d2langselect style=width:180px><option value="*">' + "User browser value" + '</option>';
  2311. for (var i in serverinfo.languages) {
  2312. var lang = serverinfo.languages[i];
  2313. z += '<option value="' + lang + '"' + ((userinfo.lang == lang)?' selected':'') + '>' + lang + ' - ' + (loclist[lang]?loclist[lang]:loclistex[lang]) + '</option>';
  2314. }
  2315. z += '</select>';
  2316. y += addHtmlValue("Language", z);
  2317. }
  2318. y += addHtmlValue("Dates & Time", x);
  2319. if ((userinfo.siteadmin == 0xFFFFFFFF) && (domain == '')) {
  2320. y += '<br /><a rel="noreferrer noopener" target="_blank" href="translator.htm">' + "Help translate MeshCentral" + '</a>';
  2321. }
  2322. setDialogMode(2, "Localization Settings", 3, account_showLocalizationSettingsEx, y);
  2323. return false;
  2324. }
  2325. function account_showLocalizationSettingsEx() {
  2326. // Set user language
  2327. var lang = Q('d2langselect').value;
  2328. if ((lang == '*') && (userinfo.lang == null)) { lang = userinfo.lang; }
  2329. if (lang != userinfo.lang) { meshserver.send({ action: 'changelang', lang: lang }); }
  2330. // Set date localization
  2331. var n = getstore('loctag', 0);
  2332. var m = Q('d2locselect').value;
  2333. if (n != m) {
  2334. if (m != '*') { args.locale = m; } else { delete args.locale; }
  2335. putstore('loctag', args.locale);
  2336. mainUpdate(0xFFFFFFFF); // Refresh everything.
  2337. }
  2338. }
  2339. function account_manageAuthApp() {
  2340. if (xxdialogMode || ((features & 4096) == 0)) return;
  2341. if (userinfo.otpsecret == 1) { account_removeOtp(); } else { account_addOtp(); }
  2342. }
  2343. function account_addOtp() {
  2344. if (xxdialogMode || (userinfo.otpsecret == 1) || ((features & 4096) == 0)) return;
  2345. setDialogMode(2, "Authenticator App", 2, function () { meshserver.send({ action: 'otpauth-setup', secret: Q('d2optsecret').attributes.secret.value, token: Q('d2otpauthinput').value }); }, '<div id=d2optinfo>' + "Loading..." + '</div>', 'otpauth-request');
  2346. meshserver.send({ action: 'otpauth-request' });
  2347. }
  2348. function account_addOtpCheck(e) {
  2349. var tokenIsValid = (Q('d2otpauthinput').value.length == 6);
  2350. QE('idx_dlgOkButton', tokenIsValid);
  2351. if (e && (e.keyCode == 13) && tokenIsValid) { dialogclose(1); }
  2352. }
  2353. function account_removeOtp() {
  2354. if (xxdialogMode || (userinfo.otpsecret != 1) || ((features & 4096) == 0)) return;
  2355. setDialogMode(2, "Authenticator App", 3, function () { meshserver.send({ action: 'otpauth-clear' }); }, "Confirm removal of authenticator application 2-step login?");
  2356. }
  2357. function account_manageOtp(action) {
  2358. if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-manage')) { dialogclose(0); }
  2359. if (xxdialogMode || ((features & 4096) == 0) || ((userinfo.otpsecret != 1) && (userinfo.otphkeys < 1))) return;
  2360. meshserver.send({ action: 'otpauth-getpasswords', subaction: action });
  2361. }
  2362. function account_showVerifyEmail() {
  2363. if (xxdialogMode || (userinfo.emailVerified == true) || (serverinfo.emailcheck != true)) return;
  2364. var x = "Click ok to send a verification mail to:" + '<br /><div style=padding:8px><b>' + EscapeHtml(userinfo.email) + '</b></div>' + "Please wait a few minute to receive the verification.";
  2365. setDialogMode(2, "Email Verification", 3, account_showVerifyEmailEx, x);
  2366. }
  2367. function account_showVerifyEmailEx() {
  2368. meshserver.send({ action: 'verifyemail', email: userinfo.email });
  2369. }
  2370. function account_showChangeEmail() {
  2371. if (xxdialogMode) return;
  2372. var x = addHtmlValue("Email", '<input id=dp3email style=width:170px maxlength=256 onchange=account_validateEmail() onkeyup=account_validateEmail(event) />');
  2373. setDialogMode(2, "Email Address Change", 3, account_changeEmail, x);
  2374. if (userinfo.email != null) { Q('dp3email').value = userinfo.email; }
  2375. account_validateEmail();
  2376. Q('dp3email').focus();
  2377. }
  2378. function account_validateEmail(e, email) {
  2379. QE('idx_dlgOkButton', validateEmail(Q('dp3email').value) && (Q('dp3email').value != userinfo.email));
  2380. if ((e != null) && (e.keyCode == 13)) { dialogclose(1); }
  2381. }
  2382. function account_changeEmail() {
  2383. meshserver.send({ action: 'changeemail', email: Q('dp3email').value });
  2384. }
  2385. function account_showDeleteAccount() {
  2386. if (xxdialogMode) return;
  2387. var x = '<form method=post><table style=margin-left:10px><input type=hidden name=action value=deleteaccount /><input type=hidden name=authcookie value=' + authCookie + ' /><tr>';
  2388. x += '<td align=right>' + "Password:" + '</td><td><input id=apassword1 type=password name=apassword1 autocomplete=off onchange=account_validateDeleteAccount() onkeyup=account_validateDeleteAccount() /></td>';
  2389. x += '</tr><tr><td align=right>' + "Password:" + '</td><td><input id=apassword2 type=password name=apassword2 autocomplete=off onchange=account_validateDeleteAccount() onkeyup=account_validateDeleteAccount() /></td>';
  2390. x += '</tr></table><div style=padding:10px;margin-bottom:4px>';
  2391. x += '<input id=account_dlgCancelButton type=button value="' + "Cancel" + '" style=float:right;width:80px;margin-left:5px onclick=dialogclose(0)>';
  2392. x += '<input id=account_dlgOkButton type=submit value="' + "OK" + '" style="float:right;width:80px" onclick=dialogclose(1)>';
  2393. x += '</div><br /></form>';
  2394. setDialogMode(2, "Delete Account", 0, null, x);
  2395. account_validateDeleteAccount();
  2396. Q('apassword1').focus();
  2397. }
  2398. function account_showChangePassword() {
  2399. if (xxdialogMode) return false;
  2400. var x = '<table style=margin-left:10px>';
  2401. x += '<tr><td align=right>' + nobreak("Old password:") + '</td><td><input id=apassword0 type=password name=apassword0 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /> <b></b></td></tr>';
  2402. x += '<tr><td align=right>' + nobreak("New password:") + '</td><td><input id=apassword1 type=password name=apassword1 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /> <b><span id=dxPassWarn></span></b></td></tr>';
  2403. x += '<tr><td align=right>' + nobreak("New password:") + '</td><td><input id=apassword2 type=password name=apassword2 autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /></td></tr>';
  2404. if (features & 0x00010000) { x += '<tr><td align=right>' + "Password hint:" + '</td><td><input id=apasswordhint name=apasswordhint maxlength=250 type=text autocomplete=off onchange=account_validateNewPassword() onkeyup=account_validateNewPassword() onkeydown=account_validateNewPassword() /></td></tr>'; }
  2405. x += '</table>'
  2406. if (passRequirements) {
  2407. var r = [], rc = 0;
  2408. for (var i in passRequirements) { if ((i != 'reset') && (i != 'hint')) { r.push(i + ':' + passRequirements[i]); rc++; } }
  2409. if (rc > 0) { x += '<br /><span style=font-size:x-small>' + format("Requirements: {0}.", r.join(', ')) + '</span>'; }
  2410. }
  2411. x += '<br />';
  2412. setDialogMode(2, "Change Password", 3, account_showChangePasswordEx, x);
  2413. Q('apassword0').focus();
  2414. account_validateNewPassword();
  2415. return false;
  2416. }
  2417. function account_showChangePasswordEx() {
  2418. if (Q('apassword1').value == Q('apassword2').value) {
  2419. var r = { action: 'changepassword', oldpass: Q('apassword0').value, newpass: Q('apassword1').value };
  2420. if (features & 0x00010000) { r.hint = Q('apasswordhint').value; }
  2421. meshserver.send(r);
  2422. }
  2423. }
  2424. function account_createMesh() {
  2425. if (xxdialogMode) return;
  2426. // Check if we are disallowed from creating a device group
  2427. if ((userinfo.siteadmin != 0xFFFFFFFF) && ((userinfo.siteadmin & 64) != 0)) { setDialogMode(2, "New Device Group", 1, null, "This account does not have the rights to create a new device group."); return; }
  2428. // Remind the user to verify the email address
  2429. if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until a email address is verified. This is required for password recovery. Go to the \"My Account\" to change and verify an email address."); return; }
  2430. // Remind the user to add two factor authentication
  2431. if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || (userinfo.otpduo > 0) || (userinfo.otpdev > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
  2432. // We are allowed, let's prompt to information
  2433. var x = addHtmlValue("Name", '<input id=dp3meshname style=width:170px maxlength=64 onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate() />');
  2434. x += addHtmlValue("Type", '<div style=width:170px;margin:0;padding:0><select id=dp3meshtype style=width:100% onchange=account_validateMeshCreate() ><option value=2>' + "Software Agent Group" + '</option><option value=1>' + "Intel&reg; AMT only" + '</option></select></div>');
  2435. x += addHtmlValue("Description", '<div style=width:170px;margin:0;padding:0><textarea id=dp3meshdesc maxlength=1024 style=width:100%;resize:none></textarea></div>');
  2436. setDialogMode(2, "Create Device Group", 3, account_createMeshEx, x);
  2437. account_validateMeshCreate();
  2438. Q('dp3meshname').focus();
  2439. }
  2440. function account_validateMeshCreate() {
  2441. QE('idx_dlgOkButton', Q('dp3meshname').value.length > 0);
  2442. }
  2443. function account_createMeshEx(button, tag) {
  2444. meshserver.send({ action: 'createmesh', meshname: Q('dp3meshname').value, meshtype: parseInt(Q('dp3meshtype').value), desc: Q('dp3meshdesc').value });
  2445. }
  2446. function account_validateDeleteAccount() {
  2447. QE('account_dlgOkButton', (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value));
  2448. }
  2449. function account_validateNewPassword() {
  2450. var r = '', ok = (Q('apassword0').value.length > 0) && (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value) && (Q('apassword0').value != Q('apassword1').value);
  2451. if ((features & 0x00010000) && (Q('apasswordhint').value == Q('apassword1').value)) { ok = false; }
  2452. if (Q('apassword1').value != '') {
  2453. if (passRequirements == null || passRequirements == '') {
  2454. // No password requirements, display password strength
  2455. var passStrength = checkPasswordStrength(Q('apassword1').value);
  2456. if (passStrength >= 80) { r = '<span style=color:green>Strong<span>'; } else if (passStrength >= 60) { r = '<span style=color:blue>&#9679;<span>'; } else { r = '<span style=color:red>&#9679;<span>'; }
  2457. } else {
  2458. // Password requirements provided, use that
  2459. var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements);
  2460. if (passReq == false) { ok = false; r = '<span style=color:red>' + "Policy" + '<span>' }
  2461. }
  2462. }
  2463. QH('dxPassWarn', r);
  2464. //QE('account_dlgOkButton', ok);
  2465. QE('idx_dlgOkButton', ok);
  2466. }
  2467. // Return a password strength score
  2468. function checkPasswordStrength(password) {
  2469. var r = 0, letters = {}, varCount = 0, variations = { digits: /\d/.test(password), lower: /[a-z]/.test(password), upper: /[A-Z]/.test(password), nonWords: /\W/.test(password) }
  2470. if (!password) return 0;
  2471. for (var i = 0; i < password.length; i++) { letters[password[i]] = (letters[password[i]] || 0) + 1; r += 5.0 / letters[password[i]]; }
  2472. for (var c in variations) { varCount += (variations[c] == true) ? 1 : 0; }
  2473. return parseInt(r + (varCount - 1) * 10);
  2474. }
  2475. // Check password requirements
  2476. function checkPasswordRequirements(password, requirements) {
  2477. if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true;
  2478. if (requirements.min) { if (password.length < requirements.min) return false; }
  2479. if (requirements.max) { if (password.length > requirements.max) return false; }
  2480. var numeric = 0, lower = 0, upper = 0, nonalpha = 0;
  2481. for (var i = 0; i < password.length; i++) {
  2482. if (/\d/.test(password[i])) { numeric++; }
  2483. if (/[a-z]/.test(password[i])) { lower++; }
  2484. if (/[A-Z]/.test(password[i])) { upper++; }
  2485. if (/\W/.test(password[i])) { nonalpha++; }
  2486. }
  2487. if (requirements.numeric && (numeric < requirements.numeric)) return false;
  2488. if (requirements.lower && (lower < requirements.lower)) return false;
  2489. if (requirements.upper && (upper < requirements.upper)) return false;
  2490. if (requirements.nonalpha && (nonalpha < requirements.nonalpha)) return false;
  2491. return true;
  2492. }
  2493. function updateMeshes() {
  2494. var r = '', count = 0;
  2495. for (i in meshes) {
  2496. count++;
  2497. // Mesh rights
  2498. var meshrights = GetMeshRights(meshes[i]);
  2499. var rights = "Partial Rights";
  2500. if (meshrights == 0xFFFFFFFF) rights = "Full Administrator"; else if (meshrights == 0) rights = "No Rights";
  2501. // Print the mesh information
  2502. r += '<div style=cursor:pointer onclick=goForward(\'' + i + '\')>';
  2503. r += '<div style="float:left;margin-left:4px"><img src="/images/meshicon50.png" width=50 height=50 /></div>';
  2504. r += '<div class=meshList>';
  2505. r += '<div><div style=color:black;padding-left:12px;padding-top:2px><b>' + EscapeHtml(meshes[i].name) + '</b></div><div style=padding-left:12px;padding-top:3px;color:black>' + rights + '</div></div>';
  2506. r += '</div></div>';
  2507. }
  2508. QH('p3meshes', r);
  2509. QV('p3noMeshFound', count == 0);
  2510. }
  2511. function gotoMesh(meshid) {
  2512. // Remind the user to add two factor authentication
  2513. if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || (userinfo.otpduo > 0) || (userinfo.otpdev > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access this feature until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
  2514. currentMesh = meshes[meshid];
  2515. if (currentMesh == null) { goBack(); }
  2516. p20updateMesh();
  2517. go(20);
  2518. }
  2519. //
  2520. // FILE SELECTOR, DIALOG 3
  2521. //
  2522. function d3init() {
  2523. d3fileoptions = { dialog: 1, filter: 'd3filter', files: 'd3serverfiles', folderup: 'p3FolderUp', currentFolder: 'p3CurrentFolder', func: d3setActions };
  2524. Q('d3localFile').value = '';
  2525. Q('d3localFile').accept = Q('d3filter').value;
  2526. d3modechange();
  2527. }
  2528. function d3modechange() {
  2529. var mode = Q('d3uploadMode').value;
  2530. QV('d3localmode', mode == 1);
  2531. QV('d3servermode', mode == 2);
  2532. if (mode == 1) { d3setActions(); } else { d3updatefiles(); }
  2533. }
  2534. var d3filetreelinkpath;
  2535. var d3filetreelocation = [];
  2536. var d3fileoptions = null;
  2537. function d3updatefiles() {
  2538. if (d3fileoptions == null) return;
  2539. if ((d3fileoptions.filter == 'd3filter') && (Q('d3uploadMode').value == 1)) return;
  2540. var html1 = '', html2 = '', filetreex = filetree, folderdepth = 1, publicPath = null, lastFolderName = '';
  2541. // Navigate to path location, build the paths at the same time
  2542. var d3filetreelocation2 = [], oldlinkpath = d3filetreelinkpath, checkedBoxes = [], checkboxes = document.getElementsByName('fc');
  2543. for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { checkedBoxes.push(checkboxes[i].value) }; } // Save all existing checked boxes
  2544. d3filetreelinkpath = '';
  2545. for (var i in d3filetreelocation) {
  2546. if ((filetreex.f != null) && (filetreex.f[d3filetreelocation[i]] != null)) {
  2547. d3filetreelocation2.push(d3filetreelocation[i]);
  2548. if ((folderdepth == 1)) {
  2549. var sp = d3filetreelocation[i].split('/');
  2550. publicPath = window.location.origin + domainUrl + sp[0] + 'files/' + sp[2];
  2551. if (d3filetreelocation[i] === userinfo._id) { d3filetreelinkpath += 'self'; } else { d3filetreelinkpath += (sp[0] + '/' + sp[2]); }
  2552. } else {
  2553. if (d3filetreelinkpath != '') { d3filetreelinkpath += '/' + d3filetreelocation[i]; if (folderdepth > 2) { publicPath += '/' + d3filetreelocation[i]; } }
  2554. }
  2555. filetreex = filetreex.f[d3filetreelocation[i]];
  2556. lastFolderName = filetreex.n;
  2557. folderdepth++;
  2558. } else {
  2559. break;
  2560. }
  2561. }
  2562. d3filetreelocation = d3filetreelocation2; // In case we could not go down the full path, we set the new path location here.
  2563. // Sort the files
  2564. var filetreexx = p5sort_files(filetreex.f);
  2565. // File filter
  2566. var fileFilter = '';
  2567. if (d3fileoptions.filter) { fileFilter = Q(d3fileoptions.filter).value };
  2568. // Display all files and folders at this location
  2569. for (var i in filetreexx) {
  2570. // Figure out the name and shortname
  2571. var f = filetreexx[i], name = f.n, shortname;
  2572. // Filter out files
  2573. if ((f.t == 3) && (fileFilter != '') && (f.nx.toLowerCase().endsWith(fileFilter) == false)) { continue; }
  2574. // if (name.length > 70) { shortname = '<span title="' + EscapeHtml(name) + '">' + EscapeHtml(name.substring(0, 70)) + ("..." + '</span>'); } else { shortname = EscapeHtml(name); }
  2575. // Removed redundant filename length check because we handle it in the CSS
  2576. shortname = EscapeHtml(name);
  2577. // Figure out the size
  2578. var fsize = '';
  2579. if (f.s != null) { fsize = getFileSizeStr(f.s); }
  2580. var h = '';
  2581. if (f.t != 3) {
  2582. var title = '';
  2583. h = '<div class=filelist file=999><span style=float:right title="' + title + '"></span><span title="' + shortname + '"><div class=fileIcon' + f.t + ' onclick=d3folderset("' + encodeURIComponentEx(f.nx) + '")></div>&nbsp;<a href=# style=cursor:pointer onclick=\'return d3folderset("' + encodeURIComponentEx(f.nx) + '")\'>' + shortname + '</a></span></div>';
  2584. } else {
  2585. var link = shortname;
  2586. //if (f.s > 0) { link = "<a rel=\"noreferrer noopener\" target=\"_blank\" href=\"downloadfile.ashx?link=" + encodeURIComponentEx(filetreelinkpath + '/' + f.nx) + "\">" + shortname + "</a>"; }
  2587. h = '<div class=filelist file=3><input style=float:left name=fcx class=fcb type=checkbox onchange=d3setActions() value="' + f.nx + '">&nbsp;<span style=float:right>' + EscapeHtml(fsize) + '</span><span title="' + shortname + '"><div class=fileIcon' + f.t + '></div>' + link + '</span></div>';
  2588. }
  2589. if (f.t < 3) { html1 += h; } else { html2 += h; }
  2590. }
  2591. if (d3fileoptions.currentFolder) { QH(d3fileoptions.currentFolder, lastFolderName); }
  2592. QH(d3fileoptions.files, html1 + html2);
  2593. QE(d3fileoptions.folderup, d3filetreelocation.length > 0);
  2594. if (d3fileoptions.func) { d3fileoptions.func(); }
  2595. }
  2596. function d3folderset(x) { d3filetreelocation.push(decodeURIComponent(x)); d3updatefiles(); return false; }
  2597. function d3folderup(x) { if (x == null) { d3filetreelocation.pop(); } else { while (d3filetreelocation.length > x) { d3filetreelocation.pop(); } } d3updatefiles(); }
  2598. function d3getFileSel() { var cc = []; var checkboxes = document.getElementsByName('fcx'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { cc.push(checkboxes[i].value) } } return cc; }
  2599. function d3setActions() {
  2600. if (d3fileoptions.dialog == 1) {
  2601. var mode = Q('d3uploadMode').value;
  2602. if (mode == 1) {
  2603. QE('idx_dlgOkButton', Q('d3localFile').value.length > 0);
  2604. } else {
  2605. QE('idx_dlgOkButton', d3getFileSel().length == 1);
  2606. }
  2607. } else if (d3fileoptions.dialog == 2) {
  2608. QE('idx_dlgOkButton', d3getFileSel().length == 1);
  2609. }
  2610. }
  2611. //
  2612. // MY FILES
  2613. //
  2614. var filetreelinkpath;
  2615. var filetreelocation = [];
  2616. function p5refreshFiles() { meshserver.send({ action: 'files' }); }
  2617. function updateFiles() {
  2618. QV('MainMenuMyFiles', ((features & 8) == 0));
  2619. if ((features & 8) != 0) return; // If running on a server without files, exit now.
  2620. var html1 = '', html2 = '', displayPath = '<a style=cursor:pointer;color:black onclick=p5folderup(0)>' + "Root" + '</a>', fullPath = 'Root', publicPath, filetreex = filetree, folderdepth = 1;
  2621. // Navigate to path location, build the paths at the same time
  2622. var filetreelocation2 = [], oldlinkpath = filetreelinkpath, checkedBoxes = [], checkboxes = document.getElementsByName('fc');
  2623. for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { checkedBoxes.push(checkboxes[i].value) }; } // Save all existing checked boxes
  2624. filetreelinkpath = '';
  2625. for (var i in filetreelocation) {
  2626. if ((filetreex.f != null) && (filetreex.f[filetreelocation[i]] != null)) {
  2627. filetreelocation2.push(filetreelocation[i]);
  2628. fullPath += ' / ' + filetreelocation[i];
  2629. if ((folderdepth == 1)) {
  2630. var sp = filetreelocation[i].split('/');
  2631. publicPath = window.location.origin + domainUrl + sp[0] + 'files/' + sp[2];
  2632. //if (filetreelocation[i] === userinfo._id) { filetreelinkpath += 'self'; } else { filetreelinkpath += (sp[0] + '/' + sp[2]); }
  2633. filetreelinkpath += filetreelocation[i];
  2634. } else {
  2635. if (filetreelinkpath != '') { filetreelinkpath += '/' + filetreelocation[i]; if (folderdepth > 2) { publicPath += '/' + filetreelocation[i]; } }
  2636. }
  2637. filetreex = filetreex.f[filetreelocation[i]];
  2638. displayPath += ' / <a style=cursor:pointer;color:black onclick=p5folderup(' + folderdepth + ')>' + EscapeHtml(filetreex.n != null ? filetreex.n : filetreelocation[i]) + '</a>';
  2639. folderdepth++;
  2640. } else {
  2641. break;
  2642. }
  2643. }
  2644. filetreelocation = filetreelocation2; // In case we could not go down the full path, we set the new path location here.
  2645. var publicfolder = fullPath.toLowerCase().startsWith('root / ' + userinfo._id + ' / public');
  2646. // Sort the files
  2647. var filetreexx = p5sort_files(filetreex.f);
  2648. // Display all files and folders at this location
  2649. for (var i in filetreexx) {
  2650. // Figure out the name and shortname
  2651. var f = filetreexx[i], name = f.n, shortname;
  2652. // if (name.length > 40) { shortname = EscapeHtml(name.substring(0, 40)) + "..."; } else { shortname = EscapeHtml(name); }
  2653. // Removed redundant filename length check because we handle it in the CSS
  2654. shortname = EscapeHtml(name);
  2655. // Figure out the date
  2656. //var fdatestr = '';
  2657. //if (f.d != null) { var fdate = new Date(f.d), fdatestr = (fdate.getMonth() + 1) + '/' + (fdate.getDate()) + '/' + fdate.getFullYear() + ' ' + printTime(fdate) + '&nbsp;'; }
  2658. // Figure out the size
  2659. var fsize = '';
  2660. if (f.s != null) { fsize = getFileSizeStr(f.s); }
  2661. var h = '';
  2662. if (f.t < 3 || f.t == 4) {
  2663. var right = (f.t == 1 || f.t == 4) ? p5getQuotabar(f) : '';
  2664. h = '<div class=filelist file=999><input file=999 style=float:left name=fc class=fcb type=checkbox onchange=p5setActions() value=\'' + EscapeHtml(name) + '\'>&nbsp;<span style=float:right;padding-right:4px>' + right + '</span><span title="' + shortname + '"><div class=fileIcon' + f.t + '></div><a style=cursor:pointer onclick=p5folderset("' + encodeURIComponent(f.nx) + '")>' + shortname + '</a></span></div>';
  2665. } else {
  2666. var link = shortname;
  2667. var publiclink = '';
  2668. if (publicfolder) { publiclink = ' (<a style=cursor:pointer onclick=\'p5showPublicLink("' + publicPath + '/' + f.nx + '")\'>' + "Link" + '</a>)'; }
  2669. if (f.s > 0) { link = '<a rel=\"noreferrer noopener\" target=\"_blank\" href=\"downloadfile.ashx?link=' + encodeURIComponent(filetreelinkpath + '/' + f.nx) + '\">' + shortname + '</a>' + publiclink; }
  2670. h = '<div class=filelist file=3><input file=3 style=float:left name=fc class=fcb type=checkbox onchange=p5setActions() value=\'' + f.nx + '\'>&nbsp;<span style=float:right;padding-right:4px>' + EscapeHtml(fsize) + '</span><span title="' + shortname + '"><div class=fileIcon' + f.t + '></div>' + link + '</span></div>';
  2671. }
  2672. if (f.t < 3) { html1 += h; } else { html2 += h; }
  2673. }
  2674. //if (f.parent == null) { }
  2675. QH('p5rightOfButtons', p5getQuotabar(filetreex));
  2676. QH('p5files', html1 + html2);
  2677. QH('p5currentpath', displayPath);
  2678. QE('p5FolderUp', filetreelocation.length != 0);
  2679. QV('p5PublicShare', publicfolder);
  2680. // Re-check all boxes if needed
  2681. if (oldlinkpath == filetreelinkpath) {
  2682. checkboxes = document.getElementsByName('fc');
  2683. for (var i = 0; i < checkboxes.length; i++) {
  2684. checkboxes[i].checked = (checkedBoxes.indexOf(checkboxes[i].value) >= 0);
  2685. }
  2686. }
  2687. p5setActions();
  2688. }
  2689. function getNiceSize(bytes) {
  2690. if (bytes <= 0) return "Storage exceed";
  2691. if (bytes < 2048) return format("{0}b left", bytes);
  2692. if (bytes < 2097152) return format("{0}k left", Math.round(bytes / 1024));
  2693. if (bytes < 2147483648) return format("{0}m left", Math.round(bytes / 1024 / 1024));
  2694. return format("{0}g left", Math.round(bytes / 1024 / 1024 / 1024));
  2695. }
  2696. function getNetworkSpeed(bitsPerSecond) {
  2697. if (bitsPerSecond <= 0) return "0 bps";
  2698. if (bitsPerSecond < 1000) return format("{0} bps", bitsPerSecond);
  2699. if (bitsPerSecond < 1000000) return format("{0} Kbps", Math.round(bitsPerSecond / 1000));
  2700. if (bitsPerSecond < 1000000000) return format("{0} Mbps", Math.round(bitsPerSecond / 1000000));
  2701. return format("{0} Gbps", (bitsPerSecond / 1000000000).toFixed(1));
  2702. }
  2703. function p5getQuotabar(f) {
  2704. while (f.t > 1 && f.t != 4) { f = f.parent; }
  2705. if ((f.t != 1 && f.t != 4) || (f.maxbytes == null)) return '';
  2706. return getNiceSize(f.maxbytes - f.s) + ' <progress style=height:10px;width:100px value=' + f.s + ' max=' + f.maxbytes + ' />';
  2707. }
  2708. function p5showPublicLink(u) { setDialogMode(2, "Public Link", 1, null, '<input type=text style=width:100% value="' + u + '" readonly />'); }
  2709. var sortorder;
  2710. function p5sort_filename(a, b) { if (a.ln > b.ln) return (1 * sortorder); if (a.ln < b.ln) return (-1 * sortorder); return 0; }
  2711. function p5sort_timestamp(a, b) { if (a.d > b.d) return (1 * sortorder); if (a.d < b.d) return (-1 * sortorder); return 0; }
  2712. function p5sort_bysize(a, b) { if (a.s == b.s) return p5sort_filename(a, b); return (((a.s - b.s)) * sortorder); }
  2713. function p5sort_files(files) {
  2714. var r = [], sortselection = Q('p5sortdropdown').value;
  2715. for (var i in files) { files[i].nx = i; if (files[i].n == null) { files[i].n = i; } files[i].ln = files[i].n.toLowerCase(); r.push(files[i]); }
  2716. sortorder = 1;
  2717. if (sortselection > 3) { sortorder = -1; sortselection -= 3; }
  2718. if (sortselection == 1) { r.sort(p5sort_filename); }
  2719. else if (sortselection == 2) { r.sort(p5sort_bysize); }
  2720. else if (sortselection == 3) { r.sort(p5sort_timestamp); }
  2721. return r;
  2722. }
  2723. function p5setActions() {
  2724. var cc = getFileSelCount(), tc = getFileCount(), sfc = getFileSelCount(false); // In order: number of entires selected, number of total entries, number of selected entires that are files (not folders)
  2725. QE('p5DeleteFileButton', (cc > 0) && (filetreelocation.length > 0));
  2726. QE('p5NewFolderButton', filetreelocation.length > 0);
  2727. QE('p5UploadButton', filetreelocation.length > 0);
  2728. QE('p5RenameFileButton', (cc == 1) && (filetreelocation.length > 0));
  2729. QE('p5SelectAllButton', tc > 0);
  2730. Q('p5SelectAllButton').value = (cc > 0 ? "None" : "All");
  2731. QE('p5CutButton', (sfc > 0) && (cc == sfc));
  2732. QE('p5CopyButton', (sfc > 0) && (cc == sfc));
  2733. QE('p5PasteButton', (p5clipboard != null) && (p5clipboard.length > 0) && (filetreelocation.length > 0));
  2734. }
  2735. function getFileSelCount(includeDirs) { var cc = 0, checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && ((includeDirs != false) || (checkboxes[i].attributes.file.value == '3'))) cc++; } return cc; }
  2736. function getFileSelDirCount() { var cc = 0, checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && (checkboxes[i].attributes.file.value == '999')) cc++; } return cc; }
  2737. function getFileCount() { var cc = 0; var checkboxes = document.getElementsByName('fc'); return checkboxes.length; }
  2738. function p5selectallfile() { var nv = (getFileSelCount() == 0), checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { checkboxes[i].checked = nv; } p5setActions(); }
  2739. function setupBackPointers(x) { if (x.f != null) { var fs = 0, fc = 0; for (var i in x.f) { setupBackPointers(x.f[i]); x.f[i].parent = x; if (x.f[i].s) { fs += x.f[i].s; } if (x.f[i].c) { fc += x.f[i].c; } if (x.f[i].t == 3) { fc++; } } x.s = fs; x.c = fc; } return x; }
  2740. function getFileSizeStr(size) { if (size == 1) return "1 byte"; return format("{0} bytes", size); }
  2741. function p5folderup(x) { if (x == null) { filetreelocation.pop(); } else { while (filetreelocation.length > x) { filetreelocation.pop(); } } updateFiles(); return false; }
  2742. function p5folderset(x) { filetreelocation.push(decodeURIComponent(x)); updateFiles(); return false; }
  2743. function p5createfolder() { setDialogMode(2, "New Folder", 3, p5createfolderEx, '<input type=text id=p5renameinput maxlength=64 onkeyup=p5fileNameCheck(event) style=width:100% />'); focusTextBox('p5renameinput'); p5fileNameCheck(); }
  2744. function p5createfolderEx() { meshserver.send({ action: 'fileoperation', fileop: 'createfolder', path: filetreelocation, newfolder: Q('p5renameinput').value }); }
  2745. function p5deletefile() { var cc = getFileSelCount(), rec = (getFileSelDirCount() > 0) ? '<br /><br /><label><input type=checkbox id=p5recdeleteinput>' + "Recursive delete" + '</label><br>' : '<input type=checkbox id=p5recdeleteinput style=\'display:none\'>'; setDialogMode(2, "Delete", 3, p5deletefileEx, (cc > 1) ? (format("Delete {0} selected items?", cc) + rec) : ("Delete selected item?" + rec)); }
  2746. function p5deletefileEx() { var delfiles = [], checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { delfiles.push(checkboxes[i].value); } } meshserver.send({ action: 'fileoperation', fileop: 'delete', path: filetreelocation, delfiles: delfiles, rec: Q('p5recdeleteinput').checked }); }
  2747. function p5renamefile() { var renamefile, checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { renamefile = checkboxes[i].value; } } setDialogMode(2, "Rename", 3, p5renamefileEx, '<input type=text id=p5renameinput maxlength=64 onkeyup=p5fileNameCheck(event) style=width:100% value="' + renamefile + '" />', { action: 'fileoperation', fileop: 'rename', path: filetreelocation, oldname: renamefile }); focusTextBox('p5renameinput'); p5fileNameCheck(); }
  2748. function p5renamefileEx(b, t) { t.newname = Q('p5renameinput').value; meshserver.send(t); }
  2749. function p5fileNameCheck(e) { var x = isFilenameValid(Q('p5renameinput').value); QE('idx_dlgOkButton', x); if ((x == true) && (e && e.keyCode == 13)) { dialogclose(1); } }
  2750. var isFilenameValid = (function () { var x1 = /^[^\\/:\*\?"<>\|]+$/, x2 = /^\./, x3 = /^(nul|prn|con|lpt[0-9]|com[0-9])(\.|$)/i; return function isFilenameValid(fname) { return x1.test(fname) && !x2.test(fname) && !x3.test(fname) && (fname[0] != '.'); } })();
  2751. function p5uploadFile() { setDialogMode(2, "Upload File", 3, p5uploadFileEx, '<form method=post enctype=multipart/form-data action=uploadfile.ashx target=fileUploadFrame><input type=text name=link style=display:none id=p5uploadpath value="' + encodeURIComponent(filetreelinkpath) + '" /><input type=file name=files id=p5uploadinput style=width:100% multiple=multiple onchange="updateUploadDialogOk(\'p5uploadinput\')" /><input type=hidden name=authCookie value=' + authCookie + ' /><input type=submit id=p5loginSubmit style=display:none /></form>'); updateUploadDialogOk('p5uploadinput'); }
  2752. function p5uploadFileEx() { Q('p5loginSubmit').click(); }
  2753. function updateUploadDialogOk(x) { QE('idx_dlgOkButton', Q(x).value != ''); }
  2754. var p5clipboard = null, p5clipboardFolder = null, p5clipboardCut = 0;
  2755. function p5copyFile(cut) { var checkboxes = document.getElementsByName('fc'); p5clipboard = []; p5clipboardCut = cut, p5clipboardFolder = Clone(filetreelocation); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && (checkboxes[i].attributes.file.value == '3')) { p5clipboard.push(checkboxes[i].value); } } p5updateClipview(); }
  2756. function p5pasteFile() { var x = ''; if ((p5clipboard != null) && (p5clipboard.length > 0)) { x = format("Confirm {0} of {1} entrie{2} to this location?", (p5clipboardCut == 0 ? 'copy' : 'move'), p5clipboard.length, ((p5clipboard.length > 1) ? 's' : '')) } setDialogMode(2, "Paste", 3, p5pasteFileEx, x); }
  2757. function p5pasteFileEx() { meshserver.send({ action: 'fileoperation', fileop: (p5clipboardCut == 0 ? 'copy' : 'move'), scpath: p5clipboardFolder, path: filetreelocation, names: p5clipboard }); p5folderup(999); if (p5clipboardCut == 1) { p5clipboard = null, p5clipboardFolder = null, p5clipboardCut = 0; p5updateClipview(); } }
  2758. function p5updateClipview() { var x = ''; if ((p5clipboard != null) && (p5clipboard.length > 0)) { x = format("Holding {0} entrie{1} for {2}", p5clipboard.length, ((p5clipboard.length > 1) ? 's' : ''), (p5clipboardCut == 0 ? "copy" : "move")) + ', <a href=# onclick="return p5clearClip()" style=cursor:pointer>' + "Clear" + '</a>.' } QH('p5bottomstatus', x); p5setActions(); }
  2759. function p5clearClip() { p5clipboard = null; p5clipboardFolder = null; p5clipboardCut = 0; p5updateClipview(); return false; }
  2760. function p5fileDragDrop(e) {
  2761. haltEvent(e);
  2762. QV('bigfail', false);
  2763. QV('bigok', false);
  2764. //QV('p5fileCatchAllInput', false);
  2765. if (e.dataTransfer == null || e.dataTransfer.files.length == 0 || filetreelocation.length == 0) return;
  2766. var names = [], sizes = [], types = [], datas = [], readercount = e.dataTransfer.files.length;
  2767. for (var i = 0; i < e.dataTransfer.files.length; i++) {
  2768. var reader = new FileReader(), file = e.dataTransfer.files[i];
  2769. names.push(file.name);
  2770. sizes.push(file.size);
  2771. types.push(file.type);
  2772. reader.onload = function (event) {
  2773. datas.push(event.target.result);
  2774. if (--readercount == 0) {
  2775. Q('p5fileDragName').value = names.join('*');
  2776. Q('p5fileDragSize').value = sizes.join('*');
  2777. Q('p5fileDragType').value = types.join('*');
  2778. Q('p5fileDragData').value = datas.join('*');
  2779. Q('p5fileDragLink').value = encodeURIComponent(filetreelinkpath);
  2780. Q('p5loginSubmit2').click();
  2781. }
  2782. }
  2783. reader.readAsDataURL(file);
  2784. }
  2785. }
  2786. var p5dragtimer = null;
  2787. function p5fileDragOver(e) {
  2788. haltEvent(e);
  2789. if (p5dragtimer != null) { clearTimeout(p5dragtimer); p5dragtimer = null; }
  2790. var ac = true; // TODO: Set to true if we can accept the file
  2791. if (filetreelocation.length == 0) { ac = false; }
  2792. QV('bigok', ac);
  2793. QV('bigfail', !ac);
  2794. //QV('p5fileCatchAllInput', ac);
  2795. }
  2796. function p5fileDragLeave(e) {
  2797. haltEvent(e);
  2798. if (e.target.id != 'p5filetable') {
  2799. QV('bigfail', false);
  2800. QV('bigok', false);
  2801. //QV('p5fileCatchAllInput', false);
  2802. } else {
  2803. p5dragtimer = setTimeout('QV(\'bigfail\',false);QV(\'bigok\',false);p5dragtimer=null;', 200);
  2804. }
  2805. }
  2806. //
  2807. // MY DEVICES
  2808. //
  2809. function onRealNameCheckBox() {
  2810. showRealNames = Q('RealNameCheckBox').checked;
  2811. putstore('showRealNames', showRealNames ? 1 : 0);
  2812. mainUpdate(5);
  2813. }
  2814. function onOnlineCheckBox(e) {
  2815. putstore('onlineOnly', Q('OnlineCheckBox').checked ? 1 : 0);
  2816. onSearchInputChanged();
  2817. }
  2818. function updateDevicePageState() {
  2819. if ((devicePagingState == null) || (devicePagingState.total <= devicePagingState.limit)) {
  2820. QV('devViewPageState', false);
  2821. QV('devViewPageButton2', false);
  2822. QV('devViewPageButton3', false);
  2823. } else {
  2824. var currentPage = Math.floor((devicePagingState.skip + devicePagingState.limit) / devicePagingState.limit);
  2825. var maxPage = Math.ceil(devicePagingState.total / devicePagingState.limit);
  2826. QV('devViewPageState', true);
  2827. QV('devViewPageButton2', true);
  2828. QV('devViewPageButton3', true);
  2829. QH('devViewPageState', currentPage + '/' + maxPage);
  2830. }
  2831. }
  2832. function onDeviceViewPageChange(i) {
  2833. if (devicePagingState == null) return;
  2834. var currentPage = (Math.floor((devicePagingState.skip + devicePagingState.limit) / devicePagingState.limit));
  2835. var maxPage = Math.ceil(devicePagingState.total / devicePagingState.limit);
  2836. switch (i) {
  2837. case 2: { if (currentPage > 1) meshserver.send({ action: 'nodes', skip: (currentPage - 2) * devicePagingState.limit }); break; } // Goto previous page
  2838. case 3: { if (currentPage < maxPage) meshserver.send({ action: 'nodes', skip: currentPage * devicePagingState.limit }); break; } // Goto next page
  2839. }
  2840. }
  2841. function onDeviceSearchChanged(e) {
  2842. setTimeout(function () { onSearchInputChanged(); }, 10);
  2843. }
  2844. function clearSearchInput() {
  2845. Q('SearchInput').value = '';
  2846. Q('OnlineCheckBox').checked = false;
  2847. onSearchInputChanged();
  2848. }
  2849. function onSearchInputChanged() {
  2850. var x = Q('SearchInput').value.toLowerCase().trim(); putstore('_search', Q('SearchInput').value);
  2851. QS('SearchInput')['background-color'] = (x == '') ? '#FFFFFF' : '#FDFFBE';
  2852. var userSearch = null, ipSearch = null, groupSearch = null, tagSearch = null, agentTagSearch = null, wscSearch = null, osSearch = null, amtSearch = null;
  2853. if (x.startsWith('user:'.toLowerCase())) { userSearch = x.substring('user:'.length); }
  2854. else if (x.startsWith('u:'.toLowerCase())) { userSearch = x.substring('u:'.length); }
  2855. else if (x.startsWith('ip:'.toLowerCase())) { ipSearch = x.substring('ip:'.length); }
  2856. else if (x.startsWith('group:'.toLowerCase())) { groupSearch = x.substring('group:'.length); }
  2857. else if (x.startsWith('g:'.toLowerCase())) { groupSearch = x.substring('g:'.length); }
  2858. else if (x.startsWith('tag:'.toLowerCase())) { tagSearch = Q('SearchInput').value.trim().substring('tag:'.length); }
  2859. else if (x.startsWith('t:'.toLowerCase())) { tagSearch = Q('SearchInput').value.trim().substring('t:'.length); }
  2860. else if (x.startsWith('atag:'.toLowerCase())) { agentTagSearch = Q('SearchInput').value.trim().substring('atag:'.length).toLowerCase(); }
  2861. else if (x.startsWith('a:'.toLowerCase())) { agentTagSearch = Q('SearchInput').value.trim().substring('a:'.length).toLowerCase(); }
  2862. else if (x.startsWith('os:'.toLowerCase())) { osSearch = Q('SearchInput').value.trim().substring('os:'.length).toLowerCase(); }
  2863. else if (x.startsWith('amt:'.toLowerCase())) { amtSearch = Q('SearchInput').value.trim().substring('amt:'.length).toLowerCase(); }
  2864. else if (x == 'wsc:ok') { wscSearch = 1; }
  2865. else if (x == 'wsc:noav') { wscSearch = 2; }
  2866. else if (x == 'wsc:noupdate') { wscSearch = 3; }
  2867. else if (x == 'wsc:nofirewall') { wscSearch = 4; }
  2868. else if (x == 'wsc:any') { wscSearch = 5; }
  2869. if (x == '') {
  2870. // No search
  2871. for (var d in nodes) { nodes[d].v = true; }
  2872. } else if (ipSearch != null) {
  2873. // IP address search
  2874. for (var d in nodes) { nodes[d].v = ((nodes[d].ip != null) && (nodes[d].ip.indexOf(ipSearch) >= 0)); }
  2875. } else if (groupSearch != null) {
  2876. // Group filter
  2877. for (var d in nodes) { nodes[d].v = (meshes[nodes[d].meshid].name.toLowerCase().indexOf(groupSearch) >= 0); }
  2878. } else if (tagSearch != null) {
  2879. // Tag filter
  2880. for (var d in nodes) {
  2881. nodes[d].v = ((nodes[d].tags == null) && (tagSearch == '')) || ((nodes[d].tags != null) && (nodes[d].tags.indexOf(tagSearch) >= 0));
  2882. }
  2883. } else if (agentTagSearch != null) {
  2884. // Agent Tag filter
  2885. for (var d in nodes) {
  2886. nodes[d].v = (((nodes[d].agent != null) && (nodes[d].agent.tag == null)) && (agentTagSearch == '')) || ((nodes[d].agent != null) && (nodes[d].agent.tag != null) && (nodes[d].agent.tag.toLowerCase().indexOf(agentTagSearch) >= 0));
  2887. }
  2888. } else if (userSearch != null) {
  2889. // User search
  2890. for (var d in nodes) {
  2891. nodes[d].v = false;
  2892. if (nodes[d].users && nodes[d].users.length > 0) { for (var i in nodes[d].users) { if (nodes[d].users[i].toLowerCase().indexOf(userSearch) >= 0) { nodes[d].v = true; } } }
  2893. }
  2894. } else if (osSearch != null) {
  2895. // OS search
  2896. for (var d in nodes) { nodes[d].v = ((nodes[d].osdesc != null) && (nodes[d].osdesc.toLowerCase().indexOf(osSearch) >= 0)); }
  2897. } else if (amtSearch != null) {
  2898. // Intel AMT search
  2899. for (var d in nodes) { nodes[d].v = (nodes[d].intelamt != null) && ((amtSearch == '') || (nodes[d].intelamt.state == amtSearch)); }
  2900. } else if (wscSearch != null) {
  2901. // Windows Security Center
  2902. for (var d in nodes) {
  2903. nodes[d].v = false;
  2904. if (nodes[d].wsc) {
  2905. if ((wscSearch == 1) && (nodes[d].wsc.antiVirus == 'OK') && (nodes[d].wsc.autoUpdate == 'OK') && (nodes[d].wsc.firewall == 'OK')) { nodes[d].v = true; }
  2906. else if (((wscSearch == 2) || (wscSearch == 5)) && (nodes[d].wsc.antiVirus != 'OK')) { nodes[d].v = true; }
  2907. else if (((wscSearch == 3) || (wscSearch == 5)) && (nodes[d].wsc.autoUpdate != 'OK')) { nodes[d].v = true; }
  2908. else if (((wscSearch == 4) || (wscSearch == 5)) && (nodes[d].wsc.firewall != 'OK')) { nodes[d].v = true; }
  2909. }
  2910. }
  2911. } else if (x == '*') {
  2912. // Star filter
  2913. for (var d in nodes) { nodes[d].v = (stars[nodes[d]._id] == 1); }
  2914. } else {
  2915. // Device name search
  2916. try {
  2917. var rs = x.split(/\s+/).join('|'), rx = new RegExp(rs); // In some cases (like +), this can throw an exception.
  2918. for (var d in nodes) {
  2919. if (features2 & 0x00008000) {
  2920. if(features2 & 0x10000000){
  2921. nodes[d].v = (rx.test(nodes[d].name.toLowerCase())) || (rx.test(meshes[nodes[d].meshid].name.toLowerCase())) || ((nodes[d].rnamel != null) && rx.test(nodes[d].rnamel.toLowerCase()));
  2922. }else {
  2923. nodes[d].v = (rx.test(nodes[d].name.toLowerCase())) || ((nodes[d].rnamel != null) && rx.test(nodes[d].rnamel.toLowerCase()));
  2924. }
  2925. } else {
  2926. if(features2 & 0x10000000){
  2927. if (showRealNames) {
  2928. nodes[d].v = (nodes[d].rnamel != null) && rx.test(nodes[d].rnamel.toLowerCase()) || (rx.test(meshes[nodes[d].meshid].name.toLowerCase()));
  2929. } else {
  2930. nodes[d].v = rx.test(nodes[d].name.toLowerCase()) || (rx.test(meshes[nodes[d].meshid].name.toLowerCase()));
  2931. }
  2932. }else{
  2933. if (showRealNames) {
  2934. nodes[d].v = (nodes[d].rnamel != null) && rx.test(nodes[d].rnamel.toLowerCase());
  2935. } else {
  2936. nodes[d].v = rx.test(nodes[d].name.toLowerCase());
  2937. }
  2938. }
  2939. }
  2940. }
  2941. } catch (ex) { for (var d in nodes) { nodes[d].v = true; } }
  2942. }
  2943. // Check power state
  2944. var onlineOnly = Q('OnlineCheckBox').checked;
  2945. if (onlineOnly) { for (var d in nodes) { if ((nodes[d].conn == null) || (nodes[d].conn == 0)) { nodes[d].v = false; } } }
  2946. mainUpdate(4);
  2947. }
  2948. var gotKeyPressEvent = false;
  2949. function ondeskkeypress(e, t) {
  2950. setSessionActivity();
  2951. if (desktop && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 1)) {
  2952. gotKeyPressEvent = true;
  2953. Q('softKeyboard').value = '';
  2954. // Check what keys we are allows to send
  2955. if (currentNode != null) {
  2956. var meshrights = GetMeshRights(currentNode.meshid);
  2957. var inputAllowed = ((features2 & 0x2000) == 0) && ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
  2958. if (inputAllowed == false) return false;
  2959. var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
  2960. if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
  2961. }
  2962. return desktop.m.handleKeys(e);
  2963. }
  2964. if (terminal && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 5) && (t !== 1)) {
  2965. if (e.altKey == true) { return true; }
  2966. gotKeyPressEvent = true;
  2967. Q('softKeyboard').value = '';
  2968. var k = 0;
  2969. if (e.charCode != 0) { k = e.charCode; } else if (e.keyCode != 0) { k = e.keyCode; }
  2970. if (k != 0) {
  2971. if (terminal.urlname == 'sshterminalrelay.ashx') {
  2972. // SSH
  2973. terminal.socket.send('~' + String.fromCharCode(k));
  2974. } else {
  2975. // Agent
  2976. terminal.sendText(String.fromCharCode(k));
  2977. }
  2978. }
  2979. return false;
  2980. }
  2981. }
  2982. function ondeskkeydown(e, t) {
  2983. setSessionActivity();
  2984. if (desktop && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 1)) {
  2985. gotKeyPressEvent = false;
  2986. Q('softKeyboard').value = '';
  2987. // Check what keys we are allows to send
  2988. if (currentNode != null) {
  2989. var meshrights = GetMeshRights(currentNode.meshid);
  2990. var inputAllowed = ((features2 & 0x2000) == 0) && ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
  2991. if (inputAllowed == false) return false;
  2992. var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
  2993. if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
  2994. }
  2995. return desktop.m.handleKeyDown(e);
  2996. }
  2997. if (terminal && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 5) && (t !== 1)) {
  2998. if (e.altKey == true) { return true; }
  2999. Q('softKeyboard').value = '';
  3000. gotKeyPressEvent = false;
  3001. var k = 0;
  3002. if (e.charCode != 0) { k = e.charCode; } else if (e.keyCode != 0) { k = e.keyCode; }
  3003. if (k == 8) { // Enter and backspace
  3004. if (terminal.urlname == 'sshterminalrelay.ashx') {
  3005. // SSH
  3006. terminal.socket.send('~' + String.fromCharCode(k));
  3007. } else {
  3008. // Agent
  3009. terminal.sendText(String.fromCharCode(k));
  3010. }
  3011. }
  3012. else if (e.ctrlKey && (k >= 64) && (k <= 95)) {
  3013. // Ctrl keys
  3014. if (terminal.urlname == 'sshterminalrelay.ashx') {
  3015. // SSH
  3016. terminal.socket.send('~' + String.fromCharCode(k - 64));
  3017. } else {
  3018. // Agent
  3019. terminal.sendText(String.fromCharCode(k - 64));
  3020. }
  3021. }
  3022. }
  3023. }
  3024. function ondeskkeyup(e, t) {
  3025. setSessionActivity();
  3026. if (desktop && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 1)) {
  3027. var inputStr = Q('softKeyboard').value;
  3028. Q('softKeyboard').value = '';
  3029. // Check what keys we are allows to send
  3030. if (currentNode != null) {
  3031. var meshrights = GetMeshRights(currentNode.meshid);
  3032. var inputAllowed = ((features2 & 0x2000) == 0) && ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
  3033. if (inputAllowed == false) return false;
  3034. var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
  3035. if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
  3036. }
  3037. if ((gotKeyPressEvent == false) && (inputStr.length > 0) && desktop.m.SendKeyUnicode) {
  3038. // This is a mobile keyboard, we need to send that is in the input control.
  3039. var inputchar = inputStr[inputStr.length - 1].charCodeAt(0);
  3040. desktop.m.SendKeyUnicode(desktop.m.KeyAction.DOWN, inputchar);
  3041. desktop.m.SendKeyUnicode(desktop.m.KeyAction.UP, inputchar);
  3042. } else {
  3043. return desktop.m.handleKeyUp(e);
  3044. }
  3045. }
  3046. if (terminal && !xxdialogMode && (xxcurrentView == 10) && (currentDevicePanel == 5) && (gotKeyPressEvent == false) && (t !== 1)) {
  3047. if (e.altKey == true) { return true; }
  3048. var inputStr = Q('softKeyboard').value;
  3049. Q('softKeyboard').value = '';
  3050. if (terminal.urlname == 'sshterminalrelay.ashx') {
  3051. // SSH
  3052. terminal.socket.send('~' + inputStr);
  3053. } else {
  3054. // Agent
  3055. if (inputStr)
  3056. terminal.sendText(inputStr);
  3057. }
  3058. return false;
  3059. }
  3060. }
  3061. var sort = 0;
  3062. var deviceHeaderId = 0;
  3063. var deviceHeaderCount;
  3064. var deviceHeaders = {};
  3065. var showRealNames = false;
  3066. var deviceHeaderTotal = 0;
  3067. var deviceHeaders = {};
  3068. var deviceHeadersTitles = {};
  3069. function updateDevices() {
  3070. var r = '', c = 0, current = null, count = 0, displayedMeshes = {}, groups = {}, groupCount = {};
  3071. // 3 wide, list view or desktop view
  3072. deviceHeaderId = 0;
  3073. deviceHeaderCount = {};
  3074. deviceHeaderTotal = 0;
  3075. deviceHeaders = {};
  3076. deviceHeadersTitles = {};
  3077. var current;
  3078. // Perform node sort
  3079. if (sort == 0) { nodes.sort(meshSort); }
  3080. else if (sort == 1) { nodes.sort(powerSort); }
  3081. else if (sort == 2) { if (showRealNames == true) { nodes.sort(deviceHostSort); } else { nodes.sort(deviceSort); } }
  3082. // Go thru the list of nodes and display them
  3083. for (var i in nodes) {
  3084. if (nodes[i].v == false) continue;
  3085. //var meshrights = GetNodeRights(nodes[i]);
  3086. if (sort == 0) {
  3087. // Mesh header
  3088. nodes.sort(meshSort);
  3089. //if (nodes[i].meshid != current) {
  3090. if (((meshes[nodes[i].meshid]?nodes[i].meshid:'*') != current)) {
  3091. deviceHeaderSet();
  3092. var extra = '';
  3093. if ((meshes[nodes[i].meshid] != null) && (meshes[nodes[i].meshid].mtype == 1)) { extra = '<span style=color:lightgray>' + ", Intel&reg; AMT only" + '</span>'; }
  3094. if (current != null) { if (c == 2) { r += '<td><div style=width:301px></div></td>'; } if (r != '') { r += '</tr></table>'; } }
  3095. r += '<div class=DevSt style=padding-top:4px><span style=float:right>';
  3096. //r += getMeshActions(meshes[nodes[i].meshid], meshrights);
  3097. if (meshes[nodes[i].meshid]) {
  3098. r += '</span><span id=MxMESH style=cursor:pointer onclick=goForward("' + nodes[i].meshid + '")>' + EscapeHtml(meshes[nodes[i].meshid].name) + '</span>' + extra + '<span id=DevxHeader' + deviceHeaderId + ' style=color:lightgray></span></div>';
  3099. current = nodes[i].meshid;
  3100. } else {
  3101. r += '</span><span id=MxMESH><i>' + "Individual Devices" + '</i></span><span id=DevxHeader' + deviceHeaderId + ' style=color:lightgray></span></div>';
  3102. current = '*';
  3103. }
  3104. displayedMeshes[current] = 1;
  3105. c = 0;
  3106. }
  3107. } else if (sort == 1) {
  3108. // Power header
  3109. if (nodes[i].pwr !== current) {
  3110. deviceHeaderSet();
  3111. if (current !== null) { if (c == 2) { r += '<td><div style=width:301px></div></td>'; } if (r != '') { r += '</tr></table>'; } }
  3112. r += '<div class=DevSt style=width:100%;padding-top:4px><span>' + PowerStateStr2(nodes[i].pwr) + '</span><span id=DevxHeader' + deviceHeaderId + ' style=color:lightgray></span></div>';
  3113. current = nodes[i].pwr;
  3114. c = 0;
  3115. }
  3116. } else if (sort == 2) {
  3117. // Device header
  3118. if (current == null) { current = '1'; }
  3119. }
  3120. count++;
  3121. r += '<div name=xxdevice onclick=goForward(\'' + nodes[i]._id + '\') class=devList1 id=\'' + nodes[i]._id + '\'></div>'; // This is a standin for the device, it gets rendered only if visible.
  3122. // If we are displaying devices by group, put the device in the right group.
  3123. /*
  3124. if ((sort == 3) && (r != '')) {
  3125. if (nodes[i].tags) {
  3126. for (var j in nodes[i].tags) {
  3127. var tag = nodes[i].tags[j];
  3128. if (groups[tag] == null) { groups[tag] = r; groupCount[tag] = 1; } else { groups[tag] += r; groupCount[tag] += 1; }
  3129. if (view == 3) break;
  3130. }
  3131. }
  3132. r = '';
  3133. }
  3134. */
  3135. deviceHeaderTotal++;
  3136. if (typeof deviceHeaderCount[nodes[i].state] == 'undefined') { deviceHeaderCount[nodes[i].state] = 1; } else { deviceHeaderCount[nodes[i].state]++; }
  3137. }
  3138. // If there is nothing to display, explain the problem
  3139. var viewNothing = false;
  3140. if ((r == '') && (nodes.length > 0) && (Q('SearchInput').value != '')) {
  3141. viewNothing = true;
  3142. r = '<div style="margin:30px">' + "No devices matching this search." + '</div>';
  3143. }
  3144. // Display all empty device groups, we need to do this because users can add devices to these at any time.
  3145. if ((sort == 0) && (Q('SearchInput').value == '')) {
  3146. for (var i in meshes) {
  3147. var mesh = meshes[i];
  3148. if ((displayedMeshes[mesh._id] == null) && (IsMeshViewable(mesh))) {
  3149. if ((current != '') && (r != '')) { r += '</tr></table>'; }
  3150. r += '<div><div colspan=3 class=DevSt><span style=float:right>';
  3151. //r += getMeshActions(mesh, meshrights);
  3152. r += '</span><span id=MxMESH style=cursor:pointer onclick=goForward("' + mesh._id + '")>' + EscapeHtml(mesh.name) + '</span></div>';
  3153. if (mesh.mtype == 1) { r += '<div style=padding:10px><i>' + "No Intel&reg; AMT devices in this group"; }
  3154. if (mesh.mtype > 1) { r += '<div style=padding:10px><i>' + "No devices in this group"; }
  3155. r += '.</i></div></div>';
  3156. current = mesh._id;
  3157. count++;
  3158. }
  3159. }
  3160. }
  3161. if (count == 0) {
  3162. if ((Q('SearchInput').value != '') || (Q('OnlineCheckBox').checked)) {
  3163. QH('xdevices', '<div style="margin-top:50px;text-align:center"><span style="font-size:30px">' + "No devices" + '</span><br /><br />' + "No devices matching this search." + ' <a onclick=clearSearchInput() style=cursor:pointer>' + "Clear search filter" + '</a></div>');
  3164. } else {
  3165. QH('xdevices', '<div style="margin-top:50px;text-align:center"><span style="font-size:30px">' + "No devices" + '</span><br /><br />' + "Use the desktop version of this website to add devices." + '</div>');
  3166. }
  3167. } else {
  3168. QH('xdevices', r);
  3169. }
  3170. deviceHeaderSet();
  3171. for (var i in deviceHeaders) { QH(i, deviceHeaders[i]); }
  3172. for (var i in deviceHeadersTitles) { Q(i).title = deviceHeadersTitles[i]; }
  3173. onDevicesScrollEx();
  3174. }
  3175. var onDevicesTouchActive = false;
  3176. var onDevicesScrollnagleTimer = null;
  3177. function onDevicesScroll() {
  3178. if (onDevicesScrollnagleTimer == null) { onDevicesScrollnagleTimer = setTimeout(onDevicesScrollEx, 250); }
  3179. }
  3180. function onDeviceTouch(x) {
  3181. if (onDevicesTouchActive == x) return;
  3182. onDevicesTouchActive = x;
  3183. if (x == false) onDevicesScrollEx();
  3184. }
  3185. function onDevicesScrollEx() {
  3186. var devdivs = document.getElementsByName('xxdevice');
  3187. onDevicesScrollnagleTimer = null;
  3188. for (var i = 0; i < devdivs.length; i++) {
  3189. // Show
  3190. var node = getNodeFromId(devdivs[i].id)
  3191. if (node == null) break;
  3192. updateDeviceViewHtml(devdivs[i], node);
  3193. }
  3194. }
  3195. // Update a single device in the current view
  3196. function updateDeviceViewDevice(node) {
  3197. if (node == null) return;
  3198. var devdiv = Q(node._id);
  3199. if ((devdiv != null) && (devdiv.innerHTML != '')) { updateDeviceViewHtml(devdiv, node); } // Only update if the device is visible
  3200. }
  3201. function updateDeviceViewHtml(div, node) {
  3202. var visibleTop = Q('xdevices').scrollTop - 250, visibleBottom = Q('xdevices').scrollTop + Q('xdevices').clientHeight + 250;
  3203. if ((div.offsetTop >= visibleTop) && (div.offsetTop < visibleBottom)) {
  3204. var title = EscapeHtml(node.name);
  3205. if (title.length == 0) { title = '<i>' + "None" + '</i>'; }
  3206. if ((node.rname != null) && (node.rname.length > 0)) { title += ' / ' + EscapeHtml(node.rname); }
  3207. var name = EscapeHtml(node.name);
  3208. if (showRealNames == true && node.rname != null) name = EscapeHtml(node.rname);
  3209. if (name.length == 0) { name = '<i>' + "None" + '</i>'; }
  3210. // Add device notification icons
  3211. var devNotify = '', devNotifySub = '';
  3212. // This device is "starred"
  3213. if (stars[node._id] == 1) {
  3214. devNotifySub += '<img class=deviceNotifyDotSub src=images/icon-star-notify-16.png width=16 height=16>';
  3215. }
  3216. // This device has session information
  3217. if (node.sessions != null) {
  3218. // Display any agent messages
  3219. if (node.sessions.msg != null) {
  3220. devNotifySub += '<div style="width:16;height:16" class=deviceNotifyDotSub>' + Object.keys(node.sessions.msg).length + '</div>';
  3221. }
  3222. // Sessions are active
  3223. if ((node.sessions.kvm != null) || (node.sessions.terminal != null) || (node.sessions.files != null) || (node.sessions.tcp != null) || (node.sessions.udp != null)) {
  3224. devNotifySub += '<img class=deviceNotifyDotSub src=images/icon-relay-notify.png width=16 height=16>';
  3225. }
  3226. // Help is required
  3227. if (node.sessions.help != null) {
  3228. devNotifySub += '<img class=deviceNotifyDotSub src=images/icon-help-notify-16.png width=16 height=16>';
  3229. }
  3230. // Battery state
  3231. if (node.sessions.battery != null) {
  3232. var bat = node.sessions.battery;
  3233. var statestr = '';
  3234. if (bat.state == 'ac') { statestr = "Device is plugged-in"; }
  3235. else if (bat.state == 'dc') { statestr = "Device is battery powered"; }
  3236. var levelstr = '', levelnum = -1;
  3237. if ((typeof bat.level == 'number') && (bat.level >= 0) && (bat.level <= 100)) {
  3238. levelstr = bat.level + '%';
  3239. levelnum = (Math.floor((bat.level + 10) / 25) + 1);
  3240. if (levelnum > 5) { lvl = 5; }
  3241. if (bat.state == 'ac') { if (bat.level == 100) { levelnum = 11; } else { levelnum += 5; } }
  3242. }
  3243. if (levelnum > 0) {
  3244. devNotify += '<div class="deviceBatterySmall deviceBatterySmall' + levelnum + '" title="' + ((statestr != null) ? (statestr + ', ' + levelstr) : levelstr) + '"></div>';
  3245. }
  3246. }
  3247. }
  3248. // Add any device icons
  3249. if (devNotifySub != '') { devNotify += '<div class=deviceNotifyDot>' + devNotifySub + '</div>'; }
  3250. // Node
  3251. var icon = node.icon, nodestate = NodeStateStr(node);
  3252. if (((!node.conn) || (node.conn == 0)) && (node.mtype != 3)) { icon += ' gray'; }
  3253. div.innerHTML = '<div>' + devNotify + '<div class="i' + icon + ' devList2"></div><div class=devList3><div class=devList4><b>' + name + '</b></div><div class=devList5>' + nodestate + '</div></div></div>';
  3254. } else {
  3255. div.innerHTML = ''; // Hide
  3256. }
  3257. }
  3258. // Show device help requests
  3259. function showDeviceHelpRequests(nodeid, force, e) {
  3260. if (e) haltEvent(e);
  3261. if (xxdialogMode && !force) return false;
  3262. var node = null, x = '';
  3263. if (nodeid == null) { node = currentNode; } else { node = getNodeFromId(nodeid); }
  3264. if ((node == null) || (node.sessions == null)) { setDialogMode(0); return false; }
  3265. if (node.sessions.help != null) { for (var j in node.sessions.help) { x += '<div style=margin-bottom:6px><b>' + EscapeHtml(j) + '</b></div><div style=margin-bottom:6px>' + EscapeHtml(node.sessions.help[j]) + '</div>'; } }
  3266. if (x != '') { setDialogMode(2, "Help Requests" + ' - ' + EscapeHtml(node.name), 1, null, x, 'HELPREQ-' + node._id); } else { setDialogMode(0); }
  3267. return false;
  3268. }
  3269. // Show currently active sessions on this device
  3270. function showDeviceSessions(nodeid, force, e) {
  3271. if (((force !== true) && xxdialogMode) || (currentNode == null)) return;
  3272. var node = currentNode, x = '';
  3273. for (var i in node.sessions) {
  3274. if ((i == 'kvm') && (node.sessions.multidesk == null)) {
  3275. x += '<u>' + "Remote Desktop" + '</u>';
  3276. for (var j in node.sessions.kvm) {
  3277. if (j.startsWith('user/')) {
  3278. var trash = '';
  3279. if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("kvm", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
  3280. x += addHtmlValue4(getUserName(j), ((node.sessions.kvm[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.kvm[j]))) + trash);
  3281. } else if (j == 'busy') {
  3282. x += addHtmlValue2("Device is busy", ((node.sessions.kvm[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.kvm[j]))));
  3283. }
  3284. }
  3285. } else if (i == 'multidesk') {
  3286. x += '<u>' + "Remote Desktop" + '</u>';
  3287. for (var j in node.sessions.multidesk) {
  3288. var trash = '';
  3289. if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("multidesk", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
  3290. x += addHtmlValue4(getUserName(j), ((node.sessions.multidesk[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.multidesk[j]))) + trash);
  3291. }
  3292. } else if (i == 'terminal') {
  3293. x += '<u>' + "Terminal" + '</u>';
  3294. for (var j in node.sessions.terminal) {
  3295. var trash = '';
  3296. if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("terminal", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
  3297. x += addHtmlValue4(getUserName(j), ((node.sessions.terminal[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.terminal[j]))) + trash);
  3298. }
  3299. } else if (i == 'files') {
  3300. x += '<u>' + "Files" + '</u>';
  3301. for (var j in node.sessions.files) {
  3302. var trash = '';
  3303. if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("files", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
  3304. x += addHtmlValue4(getUserName(j), ((node.sessions.files[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.files[j]))) + trash);
  3305. }
  3306. } else if (i == 'tcp') {
  3307. x += '<u>' + "TCP Routing" + '</u>';
  3308. for (var j in node.sessions.tcp) {
  3309. var trash = '';
  3310. if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("tcp", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
  3311. x += addHtmlValue4(getUserName(j), ((node.sessions.tcp[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.tcp[j]))) + trash);
  3312. }
  3313. } else if (i == 'udp') {
  3314. x += '<u>' + "UDP Routing" + '</u>';
  3315. for (var j in node.sessions.udp) {
  3316. var trash = '';
  3317. if ((j == userinfo._id) || (GetNodeRights(node) == 0xFFFFFFFF)) { trash = ' <a href=# onclick=\'return endDeviceSession("udp", "' + encodeURIComponentEx(node._id) + '", "' + encodeURIComponentEx(j) + '")\' title="' + "Disconnect this session" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
  3318. x += addHtmlValue4(getUserName(j), ((node.sessions.udp[j] == 1) ? "1 session" : nobreak(format("{0} sessions", node.sessions.udp[j]))) + trash);
  3319. }
  3320. }
  3321. }
  3322. if (x != '') { setDialogMode(2, "Sessions" + ' - ' + EscapeHtml(node.name), 1, null, x, 'SESSIONS-' + node._id); } else { setDialogMode(0); }
  3323. }
  3324. function endDeviceSession(protocol, nodeid, userid) {
  3325. var userIdSplit = decodeURIComponent(userid).split('/'), uid = userIdSplit[0] + '/' + userIdSplit[1] + '/' + userIdSplit[2], guestname = null;
  3326. if ((userIdSplit.length == 4) && (userIdSplit[3].startsWith('guest:'))) { guestname = atob(userIdSplit[3].substring(6)); }
  3327. if (protocol == 'multidesk') {
  3328. meshserver.send({ action: 'endDesktopMultiplex', nodeid: decodeURIComponent(nodeid), xuserid: uid, guestname, guestname });
  3329. } else {
  3330. meshserver.send({ action: 'msg', type: 'endtunnel', nodeid: decodeURIComponent(nodeid), xuserid: uid, guestname, guestname, protocol: protocol });
  3331. }
  3332. }
  3333. // Show currently active sessions on this device
  3334. function showDeviceMessages(nodeid, force, e) {
  3335. if (e) haltEvent(e);
  3336. if (xxdialogMode && !force) return false;
  3337. var node = null, x = '<div style=max-height:200px;width:100%;overflow-y:auto;overflow-x:hidden>', count = 0;
  3338. if (nodeid == null) { node = currentNode; } else { node = getNodeFromId(nodeid); }
  3339. if ((node == null) || (node.sessions == null) || (node.sessions.msg == null)) { setDialogMode(0); return false; }
  3340. for (var i in node.sessions.msg) {
  3341. var msg = i, icon = 5;
  3342. if (typeof node.sessions.msg[i].msg == 'string') { msg = node.sessions.msg[i].msg; }
  3343. if (typeof node.sessions.msg[i].icon == 'number') { icon = node.sessions.msg[i].icon; }
  3344. if ((icon < 1) || (icon > 9)) { icon = 5; }
  3345. x += '<table style=width:100%><td style=width:24px><div class=NotifyIconSmall' + icon + '></div><td><div style="border-radius:5px;background-color:#BBB;width:calc(100% - 18px);padding:8px">' + EscapeHtml(msg) + '</div></table>';
  3346. count++;
  3347. }
  3348. x += '</div>';
  3349. if (count > 0) setDialogMode(2, "Agent Messages" + ' - ' + EscapeHtml(node.name), 1, null, x, 'MESSAGES-' + node._id);
  3350. return false;
  3351. }
  3352. var powerStatetable = ['', "Powered", "Sleep", "Sleep", "Sleep", "Hibernating", "Power off", "Present", "Off"];
  3353. var powerStateStrings = ['', "Powered", "Sleeping", "Sleeping", "Deep Sleep", "Hibernating", "Soft-Off", "Present", "Off"];
  3354. var powerStateStrings2 = ['', "Device is powered", "Device is in sleep state (S1)", "Device is in sleep state (S2)", "Device is in deep sleep state (S3)", "Device is hibernating (S4)", "Device is in soft-off state (S5)", "Device is present, but power state cannot be determined", "The device is powered off"];
  3355. var powerColorTable = ['#00000000', 'black', 'blue', 'blue', 'lightblue', 'blueviolet', 'darkgreen', 'lightseagreen', 'lightseagreen'];
  3356. function NodeStateStr(node) {
  3357. var states = [];
  3358. if (node.state > 0 && node.state < powerStatetable.length) state.push(powerStatetable[node.state]);
  3359. if (node.conn) {
  3360. if ((node.conn & 1) != 0) { states.push('<span>' + ((node.mtype == 4) ? ((node.porttype == 'PDU') ? "Switch" : "IP-KVM") : "Agent") + '</span>'); }
  3361. if ((node.conn & 2) != 0) { states.push('<span>' + "CIRA" + '</span>'); }
  3362. else if ((node.conn & 4) != 0) { states.push('<span>' + "Intel&reg; AMT" + '</span>'); }
  3363. if ((node.conn & 8) != 0) { states.push('<span>' + "Relay" + '</span>'); }
  3364. if ((node.conn & 16) != 0) { states.push('<span>' + "MQTT" + '</span>'); }
  3365. }
  3366. if ((node.pwr != null) && (node.pwr != 0)) { states.push(powerStateStrings[node.pwr]); }
  3367. return states.join(', ');
  3368. }
  3369. function PowerStateStr(x) {
  3370. if (x < powerStatetable.length) return powerStatetable[x];
  3371. return '';
  3372. }
  3373. function PowerStateStr2(x) {
  3374. if ((x != 0) && (x < powerStatetable.length)) return powerStatetable[x];
  3375. return "Unknown";
  3376. }
  3377. function onSortSelectChange(skipsave) {
  3378. sort = document.getElementById('sortselect').selectedIndex;
  3379. if (!skipsave) { putstore('sort', sort); }
  3380. mainUpdate(4);
  3381. }
  3382. function deviceHeaderSet() {
  3383. if (deviceHeaderId == 0) { deviceHeaderId = 1; return; }
  3384. deviceHeaders['DevxHeader' + deviceHeaderId] = ', ' + deviceHeaderTotal + ((deviceHeaderTotal == 1) ? " node" : " nodes");
  3385. var title = '';
  3386. for (var x in deviceHeaderCount) { if (title.length > 0) title += ', '; title += deviceHeaderCount[x] + ' ' + PowerStateStr2(x); }
  3387. deviceHeadersTitles['DevxHeader' + deviceHeaderId] = title;
  3388. deviceHeaderId++;
  3389. deviceHeaderCount = {};
  3390. deviceHeaderTotal = 0;
  3391. }
  3392. /*
  3393. function meshSort(a, b) { if (a.meshnamel > b.meshnamel) return 1; if (a.meshnamel < b.meshnamel) return -1; if (a.meshid == b.meshid) { if (showRealNames == true) { if (a.rnamel > b.rnamel) return 1; if (a.rnamel < b.rnamel) return -1; return 0; } else { if (a.namel > b.namel) return 1; if (a.namel < b.namel) return -1; return 0; } } return 0; }
  3394. function powerSort(a, b) { var ap = a.pwr ? a.pwr : 0; var bp = b.pwr ? b.pwr : 0; if (ap == bp) { if (showRealNames == true) { if (a.rnamel > b.rnamel) return 1; if (a.rnamel < b.rnamel) return -1; return 0; } else { if (a.namel > b.namel) return 1; if (a.namel < b.namel) return -1; return 0; } } if (ap > bp) return 1; if (ap < bp) return -1; return 0; }
  3395. function deviceSort(a, b) { if (a.namel > b.namel) return 1; if (a.namel < b.namel) return -1; return 0; }
  3396. function deviceHostSort(a, b) { if (a.rnamel > b.rnamel) return 1; if (a.rnamel < b.rnamel) return -1; return 0; }
  3397. */
  3398. var sortCollator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })
  3399. function meshSort(a, b) {
  3400. var x = sortCollator.compare(a.meshnamel, b.meshnamel);
  3401. if (x != 0) return x;
  3402. x = sortCollator.compare(a.meshid, b.meshid);
  3403. if (x != 0) return x;
  3404. if (showRealNames == true) { return sortCollator.compare(a.rnamel, b.rnamel); }
  3405. return sortCollator.compare(a.namel, b.namel);
  3406. }
  3407. function powerSort(a, b) { var ap = a.pwr ? a.pwr : 0; var bp = b.pwr ? b.pwr : 0; if (ap > bp) return -1; if (ap < bp) return 1; if (ap == bp) { if (showRealNames == true) { return sortCollator.compare(a.rnamel, b.rnamel); } else { return sortCollator.compare(a.namel, b.namel); } } }
  3408. function deviceSort(a, b) { return sortCollator.compare(a.namel, b.namel); }
  3409. function deviceHostSort(a, b) { return sortCollator.compare(a.rnamel, b.rnamel); }
  3410. //
  3411. // MY DEVICE
  3412. //
  3413. function refreshDevice(nodeid) {
  3414. if (!currentNode || currentNode._id != nodeid) return;
  3415. gotoDevice(nodeid, xxcurrentView, true);
  3416. }
  3417. var currentDevicePanel = 0;
  3418. var currentNode;
  3419. var powerTimelineNode = null;
  3420. var powerTimelineReq = null;
  3421. var powerTimelineUpdate = null;
  3422. var powerTimeline = null;
  3423. function getCurrentNode() { return currentNode; };
  3424. function gotoDevice(nodeid, panel, refresh) {
  3425. // Remind the user to verify the email address
  3426. if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until a email address is verified. This is required for password recovery. Go to the \"My Account\" to change and verify an email address."); return; }
  3427. // Remind the user to add two factor authentication
  3428. if ((features & 0x00040000) && !((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0) || (userinfo.otpkeys > 0) || (userinfo.otpduo > 0) || (userinfo.otpdev > 0) || ((features & 0x00800000) && (userinfo.otpekey == 1)))) { setDialogMode(2, "Account Security", 1, null, "Unable to access a device until two-factor authentication is enabled. This is required for extra security. Go to the \"My Account\" and look at the \"Account Security\" section."); return; }
  3429. var node = getNodeFromId(nodeid);
  3430. if (node == null) { goBack(); return; }
  3431. var mesh = meshes[node.meshid];
  3432. var meshrights = GetNodeRights(node);
  3433. var deviceSwitch = ((currentNode == null) || (currentNode._id != nodeid));
  3434. if (!currentNode || currentNode._id != node._id || refresh == true) {
  3435. currentNode = node;
  3436. // Setup session notification
  3437. QV('p10deviceNotify', (currentNode.sessions != null) && ((node.sessions.kvm != null) || (node.sessions.terminal != null) || (node.sessions.files != null) || (node.sessions.tcp != null) || (node.sessions.udp != null)));
  3438. QV('p10deviceStar', stars[currentNode._id] == 1);
  3439. QV('p10deviceHelp', (currentNode.sessions != null) && (currentNode.sessions.help != null))
  3440. if ((currentNode.sessions != null) && (currentNode.sessions.msg != null)) { QV('p10deviceMsg', true); QH('p10deviceMsg', Object.keys(currentNode.sessions.msg).length); } else { QV('p10deviceMsg', false); }
  3441. // Device Battery
  3442. QV('p10deviceBattery', false);
  3443. if ((currentNode.sessions != null) && (currentNode.sessions.battery != null)) {
  3444. var bat = currentNode.sessions.battery;
  3445. var statestr = '';
  3446. if (bat.state == 'ac') { statestr = "Device is plugged-in"; }
  3447. if (bat.state == 'dc') { statestr = "Device is battery powered"; }
  3448. var levelstr = '', levelnum = -1;
  3449. if ((typeof bat.level == 'number') && (bat.level >= 0) && (bat.level <= 100)) {
  3450. levelstr = bat.level + '%';
  3451. levelnum = (Math.floor((bat.level + 10) / 25) + 1);
  3452. if (levelnum > 5) { lvl = 5; }
  3453. if (bat.state == 'ac') { if (bat.level == 100) { levelnum = 11; } else { levelnum += 5; } }
  3454. }
  3455. if (levelnum > 0) {
  3456. Q('p10deviceBattery').title = (statestr != null) ? (statestr + ', ' + levelstr) : levelstr;
  3457. QV('p10deviceBattery', true);
  3458. Q('p10deviceBattery').className = 'deviceBatteryLarge deviceBatteryLarge' + levelnum;
  3459. }
  3460. } else {
  3461. QV('p10deviceBattery', false);
  3462. }
  3463. // Add node name
  3464. var nname = EscapeHtml(node.name);
  3465. if (nname.length == 0) { nname = '<i>' + "None" + '</i>'; }
  3466. if ((meshrights & 4) != 0) { nname = '<span onclick=showEditNodeValueDialog(0) style=cursor:pointer>' + nname + '</span>'; }
  3467. QH('p10deviceName', nname);
  3468. // Node attributes
  3469. var x = '<table style=width:100%>';
  3470. // Attribute: Mesh
  3471. if (mesh) { x += addDeviceAttribute('<span>' + "Group" + '</span>', '<a onclick=goForward("' + node.meshid + '") style=cursor:pointer>' + EscapeHtml(meshes[node.meshid].name) + '</a>'); }
  3472. // Attribute: Name
  3473. if (node.rname != null) { x += addDeviceAttribute('<span>' + "Name" + '</span>', '<span>' + EscapeHtml(node.rname) + '</span>'); }
  3474. // Attribute: Host
  3475. if ((((features & 1) == 0) && (node.mtype != 4)) || (node.mtype == 3)) { // If not WAN-only, local hostname is in use
  3476. if ((meshrights & 4) != 0) {
  3477. if (node.host) {
  3478. x += addDeviceAttribute("Hostname", '<span onclick=showEditNodeValueDialog(1) style=cursor:pointer>' + EscapeHtml(node.host) + '</span>');
  3479. } else {
  3480. x += addDeviceAttribute("Hostname", '<span onclick=showEditNodeValueDialog(1) style=cursor:pointer><i>' + "None" + '</i></span>');
  3481. }
  3482. } else {
  3483. x += addDeviceAttribute("Hostname", EscapeHtml(node.host));
  3484. }
  3485. }
  3486. // Attribute: Description
  3487. var description = node.desc ? EscapeHtml(node.desc) : '<i>' + "None" + '</i>';
  3488. if ((meshrights & 4) != 0) {
  3489. x += addDeviceAttribute("Description", '<span onclick=showEditNodeValueDialog(2) style=cursor:pointer>' + description + '</span>');
  3490. } else {
  3491. x += addDeviceAttribute("Description", description);
  3492. }
  3493. // IP-KVM information
  3494. if (node.mtype == 4) {
  3495. if (node.portnum != null) { x += addDeviceAttribute("Port Number", node.portnum); }
  3496. if (node.porttype != null) { x += addDeviceAttribute("Port Type", node.porttype); }
  3497. }
  3498. // Attribute: Mesh Agent
  3499. if ((node.agent != null) && (node.agent.id != null) && (node.mtype == 3)) {
  3500. if (node.agent.id == 4) { x += addDeviceAttribute("Device Type", "Windows"); }
  3501. if (node.agent.id == 6) { x += addDeviceAttribute("Device Type", "Linux"); }
  3502. if (node.agent.id == 29) { x += addDeviceAttribute("Device Type", "macOS"); }
  3503. } else if ((node.agent != null) && (node.agent.id != null) && (node.agent.ver != null)) {
  3504. var str = '';
  3505. if (node.agent.id <= agentsStr.length) { str = agentsStr[node.agent.id]; } else { str = agentsStr[0]; }
  3506. if (node.agent.ver != 0) { str += ' v' + node.agent.ver; }
  3507. if (node.agent.id == 14) { str = node.agent.core; }
  3508. if ((node.agent.root === false) && ((node.conn & 1) != 0)) { str += ', ' + "Restricted"; }
  3509. x += addDeviceAttribute("Mesh Agent", str);
  3510. }
  3511. // Attribute: Intel AMT
  3512. if (node.intelamt != null) {
  3513. var str = '';
  3514. var provisioningStates = { 0: nobreak("Not Activated (Pre)"), 1: nobreak("Not Activated (In)"), 2: nobreak("Activated") };
  3515. if (node.intelamt.ver != null && node.intelamt.state == null) { str += '<i>' + nobreak("Unknown State") + '</i>, v' + EscapeHtml(node.intelamt.ver); }
  3516. else if ((node.intelamt.ver == null) && (node.intelamt.state == 2)) { str += '<i>' + "Activated" + '</i>'; }
  3517. else if ((node.intelamt.ver == null) || (node.intelamt.state == null)) { str += '<i>' + "Unknown Version & State" + '</i>'; }
  3518. else {
  3519. str += provisioningStates[node.intelamt.state];
  3520. if (node.intelamt.flags) { if (node.intelamt.flags & 2) { str = ' <span>' + "CCM" + '</span>'; } else if (node.intelamt.flags & 4) { str = ' <span>' + "ACM" + '</span>'; } }
  3521. str += (', v' + EscapeHtml(node.intelamt.ver));
  3522. }
  3523. // If Intel AMT is activated, show additional options
  3524. if (node.intelamt.state == 2) {
  3525. if (node.intelamt.tls == 1) { str += ', <span title="' + "Intel&reg; AMT is setup with TLS network security" + '">' + "TLS" + '</span>'; }
  3526. var editUserCredentialsIcon = false;
  3527. if (node.intelamt.user == null || node.intelamt.user == '') { // If credentials are not set, allow setting them.
  3528. if ((meshrights & 4) != 0) {
  3529. str += ', <i style=color:#FF0000;cursor:pointer title="' + "Edit Intel&reg; AMT credentials" + '" onclick=editDeviceAmtSettings("' + node._id + '")>' + "No Credentials" + '</i>';
  3530. editUserCredentialsIcon = true;
  3531. } else {
  3532. str += ', <i style=color:#FF0000>' + "No Credentials" + '</i>';
  3533. }
  3534. } else if (((features2 & 1) != 0) && (node.intelamt.warn != null)) { // If AMT manager is running and warned of invalid credentials, allow setting them.
  3535. var warn = null;
  3536. if ((node.intelamt.warn & 1) != 0) { warn = "Invalid Credentials"; }
  3537. if ((node.intelamt.warn & 8) != 0) { warn = "Trying Credentials"; }
  3538. if (warn != null) {
  3539. if ((meshrights & 4) != 0) {
  3540. str += ', <i style=color:#FF0000;cursor:pointer title="' + "Edit Intel&reg; AMT credentials" + '" onclick=editDeviceAmtSettings("' + node._id + '")>' + warn + '</i>';
  3541. editUserCredentialsIcon = true;
  3542. } else {
  3543. str += ', <i style=color:#FF0000>' + warn + '</i>';
  3544. }
  3545. }
  3546. }
  3547. // If the AMT manager is not running, always allow Intel AMT credentials to be edited.
  3548. if (((meshrights & 4) != 0) && ((features2 & 1) == 0)) { editUserCredentialsIcon = true; }
  3549. str += ' ';
  3550. if (editUserCredentialsIcon) {
  3551. str += '<img src=images/link4.png height=10 width=10 title="' + "Edit Intel&reg; AMT credentials" + '" style=cursor:pointer onclick=editDeviceAmtSettings("' + node._id + '")>';
  3552. }
  3553. }
  3554. /*
  3555. if (node.intelamt.state == 2) {
  3556. if (node.intelamt.user == null || node.intelamt.user == '') {
  3557. if ((meshrights & 4) != 0) {
  3558. str += ', <i style=color:#FF0000;cursor:pointer onclick=editDeviceAmtSettings("' + node._id + '")>' + nobreak("No Credentials") + '</i>';
  3559. } else {
  3560. str += ', <i style=color:#FF0000>' + "No Credentials" + '</i>';
  3561. }
  3562. }
  3563. str += ' ';
  3564. if ((meshrights & 4) != 0) {
  3565. str += '<img src=images/link4.png height=10 width=10 style=cursor:pointer onclick=editDeviceAmtSettings("' + node._id + '")>';
  3566. }
  3567. }
  3568. */
  3569. var meName = "Intel&reg; ME";
  3570. if (typeof node.intelamt.sku == 'number') {
  3571. if ((node.intelamt.sku & 8) != 0) { meName = "Intel&reg; AMT"; }
  3572. else if ((node.intelamt.sku & 16) != 0) { meName = "Intel&reg; SM"; }
  3573. }
  3574. x += addDeviceAttribute(meName, str);
  3575. }
  3576. // Attribute: Mesh Agent Tag
  3577. if ((node.agent != null) && (node.agent.tag != null) && (node.agent.tag != 'mailto:')) {
  3578. var tag = EscapeHtml(node.agent.tag);
  3579. if (tag.startsWith('mailto:')) { tag = '<a href="' + tag + '">' + tag.substring(7) + '</a>'; }
  3580. x += addDeviceAttribute("Agent Tag", tag);
  3581. }
  3582. // Attribute: Intel AMT
  3583. //if (node.intelamt && node.intelamt.user) { x += addDeviceAttribute('Intel&reg; AMT', node.intelamt.user); }
  3584. // Attribute: Connectivity (Only show this if more than just the agent is connected).
  3585. var connectivity = node.conn;
  3586. if (connectivity && connectivity > 1) {
  3587. var cstate = [];
  3588. if ((node.conn & 1) != 0) cstate.push('<span>' + ((node.mtype == 4) ? ((node.porttype == 'PDU') ? "Switch" : "IP-KVM") : "Agent") + '</span>');
  3589. if ((node.conn & 2) != 0) cstate.push('<span>' + "Intel&reg; AMT CIRA" + '</span>');
  3590. else if ((node.conn & 4) != 0) cstate.push('<span>' + "Intel&reg; AMT" + '</span>');
  3591. if ((node.conn & 8) != 0) cstate.push('<span>' + "Agent Relay" + '</span>');
  3592. if ((node.conn & 16) != 0) cstate.push('<span>' + "MQTT" + '</span>');
  3593. x += addDeviceAttribute("Connectivity", cstate.join(', '));
  3594. }
  3595. // Node tags
  3596. var groupingTags = '<i>' + "None" + '</i>';
  3597. if (node.tags != null) { groupingTags = ''; for (var i in node.tags) { groupingTags += '<span class=tagSpan>' + EscapeHtml(node.tags[i]) + '</span> '; } }
  3598. if ((meshrights & 4) != 0) {
  3599. x += addDeviceAttribute("Tags", '<span onclick=showEditNodeValueDialog(3) style=cursor:pointer;color:black>' + groupingTags + '</span>');
  3600. } else {
  3601. x += addDeviceAttribute("Tags", '<span style=line-height:26px;color:black>' + groupingTags + '</span>');
  3602. }
  3603. // SSH & RDP Credentials
  3604. if ((node.ssh != null) || (node.rdp != null)) {
  3605. var y = [];
  3606. if ((meshrights & 4) != 0) {
  3607. if (node.ssh != null) { y.push('<span onclick=showClearSshDialog(3) style=cursor:pointer>' + ((node.ssh == 1) ? "SSH-User+Pass" : ((node.ssh == 2) ? "SSH-User+Key+Pass" : "SSH-User+Key")) + ' <img class=hoverButton src="images/link5.png" width=10 height=10 /></span>'); }
  3608. if (node.rdp != null) { y.push('<span onclick=showClearRdpDialog(3) style=cursor:pointer>' + "RDP" + ' <img class=hoverButton src="images/link5.png" width=10 height=10 /></span>'); }
  3609. } else {
  3610. if (node.ssh != null) { y.push(((node.ssh == 1) ? "SSH-User+Pass" : ((node.ssh == 2) ? "SSH-User+Key+Pass" : "SSH-User+Key"))); }
  3611. if (node.rdp != null) { y.push("RDP"); }
  3612. }
  3613. x += addDeviceAttribute("Credentials", y.join(', '));
  3614. }
  3615. x += '</table><br />';
  3616. // Show action button, only show if we have permissions 4, 8, 64
  3617. if (((meshrights & (4 + 8 + 64 + 262144)) != 0) && (node.mtype < 3)) { x += '<input type=button value="' + "Actions" + '" onclick=deviceActionFunction() />'; }
  3618. x += '<input type=button value="' + "Notes" + '" onclick=showNotes(' + ((meshrights & 128) == 0) + ',"' + encodeURIComponent(node._id) + '") />';
  3619. //if ((connectivity & 1) && (meshrights & 8) && (node.agent.id < 5)) { x += '<input type=button value=Toast onclick=deviceToastFunction() />'; }
  3620. if ((node.mtype == 4) && (connectivity & 1)) {
  3621. if (node.porttype == 'PDU') {
  3622. if (node.pwr == 1) {
  3623. if (meshrights & 0x40000) { x += '<input type=button value="' + "Turn off" + '" title="' + "Turn off" + '" onclick=setIpPduState(0) />'; }
  3624. } else if (node.pwr == 8) {
  3625. if (meshrights & 0x40) { x += '<input type=button value="' + "Turn on" + '" title="' + "Turn on" + '" onclick=setIpPduState(1) />'; }
  3626. }
  3627. } else {
  3628. if (meshrights & 8) { x += '<input type=button value="' + "Remote Control" + '" title="' + "Remote Control" + '" onclick=openIpKvmRemoteControl("' + encodeURIComponentEx(node._id) + '") />'; }x
  3629. }
  3630. }
  3631. QH('p10html', x);
  3632. // If we are looking at a local non-windows device, enable terminal and files capability.
  3633. if ((node.mtype == 3) && (node.agent != null) && (node.agent.id > 4) && (features2 & 0x00000200)) { node.agent.caps = 6; }
  3634. // Show node last 7 days timeline
  3635. //drawDeviceTimeline();
  3636. setupTerminal();
  3637. setupFiles();
  3638. if (meshrights & 16) { setupConsole(); }
  3639. // Show bottom buttons
  3640. x = '<div style=float:right;font-size:x-small;margin-right:10px>';
  3641. if ((meshrights & 0x8000) != 0) { x += '<a style=cursor:pointer onclick=p10showDeleteNodeDialog("' + node._id + '")>' + "Delete Device" + '</a>'; }
  3642. x += '</div><div style=font-size:x-small>';
  3643. if (webRelayPort != 0) {
  3644. x += '<a onclick=p10WebRouter("' + node._id + '",1,' + (node.httpport ? node.httpport : 80) + ')>' + "HTTP" + ((node.httpport && (node.httpport != 80)) ? '/' + node.httpport : '') + '</a>&nbsp;';
  3645. x += '<a onclick=p10WebRouter("' + node._id + '",2,' + (node.httpsport ? node.httpsport : 443) + ')>' + "HTTPS" + ((node.httpsport && (node.httpsport != 443)) ? '/' + node.httpsport : '') + '</a>&nbsp;';
  3646. }
  3647. // noVNC link
  3648. if ((((connectivity & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((meshrights & 8) != 0) && ((features & 0x20000000) == 0)) {
  3649. x += '<a id=rfbLink onclick=p10rfb("' + node._id + '")>' + "Web-VNC" + '</a>&nbsp;';
  3650. }
  3651. // MSTSC.js link
  3652. if ((((connectivity & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((meshrights & 8) != 0) && ((features & 0x40000000) == 0)) {
  3653. x += '<a id=mstscLink onclick=p10mstsc("' + node._id + '")>' + "Web-RDP" + '</a>&nbsp;';
  3654. }
  3655. // SSH link
  3656. if ((features2 & 0x200) && (((connectivity & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((meshrights & 8) != 0)) {
  3657. x += '<a id=sshLink onclick=p10ssh("' + node._id + '")>' + "Web-SSH" + '</a>&nbsp;';
  3658. }
  3659. //if (mesh.mtype == 2) x += '<a style=cursor:pointer onclick=p10showNodeNetInfoDialog("' + node._id + '")>Interfaces</a>&nbsp;';
  3660. //if (xxmap != null) x += '<a style=cursor:pointer onclick=p10showNodeLocationDialog("' + node._id + '")>Location</a>&nbsp;';
  3661. x += '</div><br>'
  3662. QH('p10html3', x);
  3663. // Set the node power state
  3664. var powerstate = PowerStateStr(node.state);
  3665. //if (node.state == 0) { powerstate = 'Unknown State'; }
  3666. if ((connectivity & 1) != 0) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += ((node.mtype == 4) ? ((node.porttype == 'PDU') ? "Switch" : "IP-KVM") : "Mesh Agent"); }
  3667. if ((connectivity & 2) != 0) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += "Intel&reg; AMT connected"; }
  3668. else if ((connectivity & 4) != 0) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += "Intel&reg; AMT detected"; }
  3669. if ((connectivity & 16) != 0) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += "MQTT channel connected"; }
  3670. if ((node.porttype == 'PDU') || ((node.pwr > 1) && (node.pwr != 7))) { if (powerstate.length > 0) { powerstate += ', '; } powerstate += powerStateStrings[node.pwr]; }
  3671. QH('MainComputerState', '<span style=font-size:12px>' + powerstate + '</span>');
  3672. // Set the node icon
  3673. var icon = node.icon;
  3674. if (((!node.conn) || (node.conn == 0)) && (node.mtype != 3)) { icon += ' gray'; }
  3675. QH('MainComputerImage', '<div class="i' + icon + '"></div>');
  3676. // Request the power timeline
  3677. if ((powerTimelineNode != currentNode._id) && (powerTimelineReq != currentNode._id)) {
  3678. QH('p10html2', '');
  3679. powerTimelineReq = currentNode._id;
  3680. meshserver.send({ action: 'powertimeline', nodeid: currentNode._id });
  3681. meshserver.send({ action: 'lastconnect', nodeid: currentNode._id });
  3682. meshserver.send({ action: 'getsysinfo', nodeid: currentNode._id });
  3683. meshserver.send({ action: 'getnetworkinfo', nodeid: currentNode._id });
  3684. QH('p10detailshtml', '');
  3685. }
  3686. // Clear user consent status if present
  3687. if (deviceSwitch) {
  3688. p11clearConsoleMsg();
  3689. p13clearConsoleMsg();
  3690. }
  3691. // Clear the desktop session selector
  3692. QV('p11DeskSessionSelector', false);
  3693. QH('p11DeskSessionSelector', '');
  3694. }
  3695. setupDesktop(); // Always refresh the desktop, even if we are on the same device, we need to do some canvas switching.
  3696. if (!panel) panel = 10;
  3697. go(panel);
  3698. // Update the footer menu
  3699. if (xxcurrentView == 10) { setupDeviceMenu(); }
  3700. }
  3701. function setIpPduState(op) {
  3702. if (op == 0) {
  3703. setDialogMode(2, "Power Operation", 3, function () { meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: 2 }); }, "Perform power off?"); // Turn off
  3704. } else {
  3705. setDialogMode(2, "Power Operation", 3, function () { meshserver.send({ action: 'wakedevices', nodeids: [currentNode._id] }); }, "Perform power on?"); // Turn on
  3706. }
  3707. }
  3708. function openIpKvmRemoteControl(nodeid) {
  3709. if (xxdialogMode) return;
  3710. var nid = decodeURIComponent(nodeid).split('/')[2];
  3711. safeNewWindow('/ipkvm.ashx/' + nid + '/', 'ipkvm:' + nid);
  3712. }
  3713. function deviceToastFunction() {
  3714. if (xxdialogMode) return;
  3715. setDialogMode(2, "Device Toast", 3, deviceToastFunctionEx, '<textarea id=d2devToast style=width:100%;height:80px;resize:none;overflow-y:scroll></textarea>');
  3716. }
  3717. function deviceToastFunctionEx() {
  3718. meshserver.send({ action: 'toast', nodeids: [currentNode._id], title: 'MeshCentral', msg: Q('d2devToast').value });
  3719. }
  3720. // && ((meshrights == 0xFFFFFFFF) || ((meshrights & 65536) == 0))
  3721. function setupDeviceMenu(op, obj) {
  3722. var meshrights = GetNodeRights(currentNode);
  3723. if (op != null) { currentDevicePanel = op; }
  3724. QV('p10general', currentDevicePanel == 0);
  3725. QV('p10desktop', currentDevicePanel == 1); // Show if we have remote control rights or desktop view only rights
  3726. QV('p10files', currentDevicePanel == 2);
  3727. QV('p10details', currentDevicePanel == 3);
  3728. QV('p10console', currentDevicePanel == 4);
  3729. QV('p10terminal', currentDevicePanel == 5);
  3730. var menus = [];
  3731. if (currentDevicePanel != 0) { menus.push({ n: "General", f: 'setupDeviceMenu(0)' }); }
  3732. if ((currentDevicePanel != 1) &&
  3733. (currentNode != null) &&
  3734. ((meshrights & 8) || (meshrights & 256)) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 65536) == 0)) &&
  3735. (((currentNode.agent == null) && (currentNode.intelamt) && ((typeof currentNode.intelamt.sku !== 'number') || ((currentNode.intelamt.sku & 8) != 0))) || (currentNode.agent && (currentNode.agent.caps & 1)))
  3736. ) { menus.push({ n: "Desktop", f: 'setupDeviceMenu(1)' }); }
  3737. if ((currentDevicePanel != 5) &&
  3738. (currentNode != null) &&
  3739. ((meshrights & 8) || (meshrights & 256)) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 512) == 0)) &&
  3740. (((currentNode.agent == null) && (currentNode.intelamt) && ((typeof currentNode.intelamt.sku !== 'number') || ((currentNode.intelamt.sku & 8) != 0))) || (currentNode.agent && (currentNode.agent.caps & 2)))
  3741. ) { menus.push({ n: "Terminal", f: 'setupDeviceMenu(5)' }); }
  3742. if ((currentDevicePanel != 2) && (currentNode != null) && (meshrights & 8) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 1024) == 0)) && ((currentNode.mtype != 1) && (currentNode.agent) && (currentNode.agent.caps & 4))) { menus.push({ n: "Files", f: 'setupDeviceMenu(2)' }); }
  3743. if ((currentDevicePanel != 3) && (currentNode != null) && (currentNode.mtype < 3) && ((meshrights & 1048576) != 0)) { menus.push({ n: "Details", f: 'setupDeviceMenu(3)' }); }
  3744. if ((currentDevicePanel != 4) && (currentNode != null) && (meshrights & 0x00000010) && (currentNode.mtype == 2)) { menus.push({ n: "Console", f: 'setupDeviceMenu(4)' }); }
  3745. updateFooterMenu(menus);
  3746. updateCurrentUrl();
  3747. if (currentDevicePanel == 1) { deskAdjust(); }
  3748. }
  3749. function deviceActionFunction() {
  3750. if (xxdialogMode) return;
  3751. var rights = GetNodeRights(currentNode), count = 0;
  3752. var x = "Select an operation to perform on this device." + '<br /><br />';
  3753. var y = '<select id=d2deviceop style=float:right;width:170px onchange=deviceActionFunctionValidate()>';
  3754. var z = '';
  3755. if ((currentNode.agent != null) && (currentNode.agent.id == 14)) {
  3756. if (((currentNode.conn & 1) != 0) && ((rights & 8) != 0)) {
  3757. count++;
  3758. y += '<option value=400>' + "Flash" + '</option>';
  3759. y += '<option value=401>' + "Vibrate" + '</option>';
  3760. z += '<div id=d2devicetimediv>' + addHtmlValue("Time", '<select id=d2devicetime style=float:right;width:170px><option value=1000>' + "1 second" + '</option><option value=5000>' + "5 seconds" + '</option><option value=10000>' + "10 seconds" + '</option></select>') + '</div>';
  3761. }
  3762. } else {
  3763. if ((rights & 64) != 0) { count++; y += '<option value=100>' + "Wake-up" + '</option>'; } // Wake-up permission
  3764. //if (((currentNode.conn & 1) != 0) && ((rights & 131072) != 0)) { count++; y += '<option value=106>' + "Run Commands" + '</option>'; } // Remote command permission
  3765. if ((currentNode.conn != 0) && ((rights & 262144) != 0)) { count++; y += '<option value=4>' + "Sleep" + '</option><option value=3>' + "Reset" + '</option><option value=2>' + "Power off" + '</option>'; }
  3766. //if ((currentNode.conn & 16) != 0) { count++; y += '<option value=103>' + "Send MQTT Message" + '</option>'; }
  3767. if ((currentNode.intelamt != null) && (currentNode.intelamt.state == 2) && ((currentNode.conn & 6) != 0) && ((rights & 262144) != 0)) {
  3768. count++;
  3769. y += '<option value=310>' + "Intel&reg; AMT Reset" + '</option>';
  3770. y += '<option value=308>' + "Intel&reg; AMT Power off" + '</option>';
  3771. }
  3772. if ((currentNode.intelamt != null) && (currentNode.intelamt.state == 2) && ((currentNode.conn & 6) != 0) && ((rights & 64) != 0)) {
  3773. count++;
  3774. y += '<option value=302>' + "Intel&reg; AMT Power on" + '</option>';
  3775. }
  3776. //if ((getNodeAmtVersion(currentNode) >= 15) && (currentNode.intelamt.state == 2) && ((currentNode.conn & 6) != 0) && (rights == 0xFFFFFFFF) && ((features & 0x00000400) == 0)) { count++; y += '<option value=107>' + "Intel&reg; AMT One Click Recovery" + '</option>'; } // CIRA (2) or AMT (4) connected
  3777. //if (((currentNode.conn & 1) != 0) && ((rights & 32768) != 0)) { count++; y += '<option value=104>' + "Uninstall Agent" + '</option>'; }
  3778. }
  3779. y += '</select>';
  3780. x += addHtmlValue("Operation", y);
  3781. if (count == 0) { x = "No actions currently available for this device."; }
  3782. setDialogMode(2, "Device Action", (count == 0) ? 2 : 3, deviceActionFunctionEx, x + z);
  3783. if (count > 0) { deviceActionFunctionValidate(); }
  3784. }
  3785. function deviceActionFunctionValidate() {
  3786. var op = Q('d2deviceop').value;
  3787. try { QV('d2devicetimediv', (op == 400) || (op == 401)); } catch (ex) { }
  3788. }
  3789. function deviceActionFunctionEx() {
  3790. var op = Q('d2deviceop').value;
  3791. if (op == 100) {
  3792. // Device wake
  3793. meshserver.send({ action: 'wakedevices', nodeids: [currentNode._id] });
  3794. } else if (op == 103) {
  3795. // Send MQTT Message
  3796. //p10showSendMqttMsgDialog([currentNode._id]);
  3797. } else if (op == 104) {
  3798. // Uninstall agent
  3799. //p10showSendUninstallAgentDialog([currentNode._id]);
  3800. } else if (op == 106) {
  3801. // Run commands
  3802. /*
  3803. var wintype = false, linuxtype = false;
  3804. if (currentNode.agent) { if ((currentNode.agent.id > 0) && (currentNode.agent.id < 5)) { wintype = true; } else { linuxtype = true; } }
  3805. if ((wintype == true) || (linuxtype == true)) {
  3806. var x = "Run commands on selected devices." + '<br />';
  3807. if (wintype == true) {
  3808. x += '<select id=d2cmdtype style=width:100%;margin-bottom:4px;margin-top:4px>';
  3809. x += '<option value=1>' + "Windows Command Prompt" + '</option><option value=2>' + "Windows PowerShell" + '</option>';
  3810. if (linuxtype == true) { x += '<option value=3>' + "Linux/BSD/macOS Command Shell" + '</option>'; }
  3811. x += '</select>';
  3812. }
  3813. x += '<select id=d2cmduser style=width:100%;margin-bottom:4px><option value=0>' + "Run as agent" + '</option><option value=1>' + "Run as user, agent if no user" + '</option><option value=2>' + "Must run as user" + '</option></select>';
  3814. x += '<textarea id=d2runcmd style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll></textarea>';
  3815. setDialogMode(2, "Run Commands", 3, deviceRunCmdsFunctionEx, x);
  3816. Q('d2runcmd').focus();
  3817. //QE('idx_dlgOkButton', true);
  3818. }
  3819. */
  3820. } else if (op == 107) {
  3821. // Intel AMT One Click Recovery (OCR)
  3822. /*
  3823. Q('d3localmodeform').action = 'oneclickrecovery.ashx';
  3824. Q('d3auth').value = authCookie;
  3825. Q('d3filter').value = '.iso';
  3826. Q('d3attrib').value = currentNode._id;
  3827. setDialogMode(3, "Intel&reg; AMT One Click Recovery", 3, deviceActionOneClickRecovery);
  3828. d3init();
  3829. */
  3830. } else if (op == 302) { // Intel AMT power on
  3831. setDialogMode(2, "Intel&reg; AMT Power Operation", 3, function () { meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op) }); }, "Perform Intel&reg; AMT power on?");
  3832. } else if (op == 308) { // Intel AMT power off
  3833. setDialogMode(2, "Intel&reg; AMT Power Operation", 3, function () { meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op) }); }, "Perform Intel&reg; AMT power off?<br><br><b>NOTE: If there is an active AMT session, then power off command will be rejected, so you must disconnect from the AMT session first!</b>");
  3834. } else if (op == 310) { // Intel AMT reset
  3835. setDialogMode(2, "Intel&reg; AMT Power Operation", 3, function () { meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op) }); }, "Perform Intel&reg; AMT reset?");
  3836. } else if ((op == 400) || (op == 401)) {
  3837. // Flash / vibrate
  3838. meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op), time: parseInt(Q('d2devicetime').value) });
  3839. } else {
  3840. // Power operation
  3841. meshserver.send({ action: 'poweraction', nodeids: [currentNode._id], actiontype: parseInt(op) });
  3842. }
  3843. }
  3844. function showNotes(readonly, noteid) {
  3845. if (xxdialogMode) return;
  3846. if (noteid == null) { noteid = encodeURIComponentEx('p' + userinfo._id); }
  3847. var x = '<textarea id=d2devNotes ro=' + readonly + ' noteid=' + noteid + ' readonly style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll></textarea>';
  3848. if (noteid.startsWith('node%2F%2F')) { x += '<span style=font-size:10px>' + "Device group notes can be viewed and changed by other device group administrators." + '<span>'; }
  3849. setDialogMode(2, "Notes", 3, showNotesEx, x, noteid);
  3850. meshserver.send({ action: 'getNotes', id: decodeURIComponent(noteid) });
  3851. }
  3852. function showNotesEx(buttons, tag) { meshserver.send({ action: 'setNotes', id: decodeURIComponent(tag), notes: encodeURIComponentEx(Q('d2devNotes').value) }); }
  3853. function deviceLockFunction() {
  3854. if ((xxdialogMode != null || xxdialogMode == 0) && (desktop != null) && (desktop.contype == 1)) { setDialogMode(2, "Lock Desktop", 3, function() { if ((desktop != null) && (desktop.contype == 1)) { desktop.sendCtrlMsg('{"ctrlChannel":"102938","type":"lock"}'); } }, "Lock user desktop?"); }
  3855. }
  3856. function deviceChat(e) {
  3857. if (xxdialogMode) return;
  3858. setDialogMode(2, "Device Action", 3, function () {
  3859. var url = '/messenger?id=meshmessenger/' + encodeURIComponentEx(currentNode._id) + '/' + encodeURIComponentEx(userinfo._id) + '&title=' + currentNode.name;
  3860. if (serverinfo.domainsuffix != '') { url = '/' + serverinfo.domainsuffix + url; }
  3861. if ((authCookie != null) && (authCookie != '')) { url += '&auth=' + authCookie; }
  3862. if (e && (e.shiftKey == true)) {
  3863. safeNewWindow(url, 'meshmessenger:' + currentNode._id);
  3864. } else {
  3865. safeNewWindow(url, 'meshmessenger:' + currentNode._id, 'directories=no,titlebar=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,width=400,height=560');
  3866. }
  3867. meshserver.send({ action: 'meshmessenger', nodeid: decodeURIComponent(currentNode._id) });
  3868. }, "Start chat session?");
  3869. }
  3870. function deviceUrlFunction() {
  3871. if (xxdialogMode) return;
  3872. setDialogMode(2, "Open Page on Device", 3, deviceUrlFunctionEx, '<input id=d2devurl placeholder="http://server.com" style=width:100%;overflow-y:scroll onkeyup=deviceUrlFunctionValidate() onchange=deviceUrlFunctionValidate()></input>');
  3873. Q('d2devurl').focus();
  3874. deviceUrlFunctionValidate();
  3875. }
  3876. function deviceUrlFunctionValidate() {
  3877. var x = Q('d2devurl').value.toLowerCase();
  3878. QE('idx_dlgOkButton', ((x.startsWith('http://') && (x.length > 7)) || (x.startsWith('https://') && (x.length > 8))));
  3879. }
  3880. function deviceUrlFunctionEx() {
  3881. meshserver.send({ action: 'msg', type: 'openUrl', nodeid: currentNode._id, url: Q('d2devurl').value });
  3882. }
  3883. function runDeviceCmd(nodeid) { if (xxdialogMode) return; d2runCommandDialog({ nodeids: [ nodeid ? decodeURIComponent(nodeid) : currentNode._id ] }); }
  3884. function d2runCommandDialog(options) {
  3885. var wintype = false, linuxtype = false, agenttype = false;
  3886. for (var i in options.nodeids) {
  3887. var n = getNodeFromId(options.nodeids[i]);
  3888. if (n.agent) { if ((GetNodeRights(n) & 24) == 24) { agenttype = true; }
  3889. if (isWindowsNode(n)) { wintype = true; } else { linuxtype = true; } }
  3890. }
  3891. if ((wintype == true) || (linuxtype == true) || (agenttype == true)) {
  3892. // Fetch run options
  3893. var runopt = { type:1, runAs:0, source:1, cmd:'' };
  3894. try { runopt = JSON.parse(getstore('runopt', runopt)); } catch (ex) {}
  3895. if (options.selectedFile) {
  3896. var filename = options.selectedFile.name.toLowerCase();
  3897. console.log('filename', filename);
  3898. if (filename.endsWith('.bat')) { runopt.type = 1; }
  3899. if (filename.endsWith('.ps1')) { runopt.type = 2; }
  3900. if (filename.endsWith('.sh')) { runopt.type = 3; }
  3901. if (filename.endsWith('.agentconsole')) { runopt.type = 4; }
  3902. }
  3903. var x = '';
  3904. if (options.title) { x += options.title + '<br />'; }
  3905. x += '<select id=d2cmdtype onclick=d2runCommandValidate() style=width:100%;margin-bottom:4px;margin-top:4px>';
  3906. if (wintype == true) { x += '<option value=1' + ((runopt.type == 1)?' selected':'') + '>' + "Windows Command Prompt" + '</option><option value=2' + ((runopt.type == 2)?' selected':'') + '>' + "Windows PowerShell" + '</option>'; }
  3907. if (linuxtype == true) { x += '<option value=3' + ((runopt.type == 3)?' selected':'') + '>' + "Linux/BSD/macOS Command Shell" + '</option>'; }
  3908. if (agenttype == true) { x += '<option value=4' + ((runopt.type == 4)?' selected':'') + '>' + "Agent Console" + '</option>'; } // MESHRIGHT_REMOTECONTROL & MESHRIGHT_AGENTCONSOLE are needed
  3909. x += '</select>';
  3910. x += '<select id=d2cmduser style=width:100%;margin-bottom:4px><option value=0' + ((runopt.runAs == 0)?' selected':'') + '>' + "Run as agent" + '</option><option value=1' + ((runopt.runAs == 1)?' selected':'') + '>' + "Run as user, agent if no user" + '</option><option value=2' + ((runopt.runAs == 2)?' selected':'') + '>' + "Must run as user" + '</option></select>';
  3911. if (options.selectedFile == null) {
  3912. x += '<select id=d2cmdsource onclick=d2runCommandValidate() style=width:100%;margin-bottom:4px><option value=0' + ((runopt.source == 0)?' selected':'') + '>' + "Commands from text box" + '</option><option value=1' + ((runopt.source == 1)?' selected':'') + '>' + "Commands from file" + '</option>';
  3913. if (userinfo.siteadmin & 8) { x += '<option value=2' + ((runopt.source == 2)?' selected':'') + '>' + "Commands from file on server" + '</option>'; }
  3914. x += '</select><textarea id=d2runcmd onkeyup=d2runCommandValidate() style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll>' + (runopt.cmd ? EscapeHtml(decodeURIComponent(runopt.cmd)) : '') + '</textarea>';
  3915. x += '<div id=d2runfile style=display:none><input id=d2runfileex type=file onchange=d2runCommandValidate() id=d2localFile name=files onchange=d2runCommandValidate() /></div>';
  3916. if (userinfo.siteadmin & 8) { x += '<div id=d2runsfile style=display:none><div id=d2serveraction valign=bottom><input type=button id=p2FolderUp disabled="disabled" onclick=d3folderup() value="Up" />&nbsp;<span id=p2CurrentFolder></span></div><div id=d2serverfiles></div></div>'; }
  3917. }
  3918. setDialogMode(2, "Run Commands", 3, d2groupActionFunctionRunCommands, x, options);
  3919. if (options.selectedFile == null) {
  3920. Q('d2runcmd').focus();
  3921. if (userinfo.siteadmin & 8) { d3fileoptions = { dialog: 2, files: 'd2serverfiles', folderup: 'p2FolderUp', currentFolder: 'p2CurrentFolder', func: null }; d3updatefiles(); } // Update the server files
  3922. }
  3923. d2runCommandValidate();
  3924. }
  3925. }
  3926. function d2runCommandValidate() {
  3927. QV('d2cmduser', Q('d2cmdtype').value < 4);
  3928. if (xxdialogTag.selectedFile == null) {
  3929. QV('d2runcmd', Q('d2cmdsource').value == 0);
  3930. QV('d2runfile', Q('d2cmdsource').value == 1);
  3931. QV('d2runsfile', Q('d2cmdsource').value == 2);
  3932. var ok = false;
  3933. if (Q('d2cmdsource').value == 0) { if (Q('d2runcmd').value.length > 0) { ok = true; } } // From text box
  3934. if (Q('d2cmdsource').value == 1) { if (Q('d2runfileex').files.length == 1) { ok = true; } } // From file
  3935. if (Q('d2cmdsource').value == 2) { ok = false; } // From server file
  3936. QE('idx_dlgOkButton', ok);
  3937. } else {
  3938. QE('idx_dlgOkButton', true);
  3939. }
  3940. }
  3941. function d2groupActionFunctionRunCommands(b, options) {
  3942. var type = 3;
  3943. try { type = parseInt(Q('d2cmdtype').value); } catch (ex) { }
  3944. if (options.selectedFile == null) { putstore('runopt', JSON.stringify({ type: type, runAs: parseInt(Q('d2cmduser').value), source: parseInt(Q('d2cmdsource').value), cmd: encodeURIComponent(Q('d2runcmd').value) })); } // Save run options
  3945. var cmd = { action: 'runcommands', nodeids: options.nodeids, type: type, runAsUser: parseInt(Q('d2cmduser').value) };
  3946. if (options.selectedFile) {
  3947. // Drag & drop file
  3948. var reader = new FileReader();
  3949. reader.onload = function (e) { cmd.cmds = e.target.result; meshserver.send(cmd); if (options.func) { options.func(); } }
  3950. reader.readAsText(options.selectedFile);
  3951. } else if (Q('d2cmdsource').value == 0) {
  3952. // From text box
  3953. cmd.cmds = Q('d2runcmd').value;
  3954. meshserver.send(cmd);
  3955. if (options.func) { options.func(); }
  3956. } else if (Q('d2cmdsource').value == 1) {
  3957. // From file
  3958. var reader = new FileReader();
  3959. reader.onload = function (e) { cmd.cmds = e.target.result; meshserver.send(cmd); if (options.func) { options.func(); } }
  3960. reader.readAsText(Q('d2runfileex').files[0]);
  3961. } else if (Q('d2cmdsource').value == 2) {
  3962. // From server file
  3963. var files = d3getFileSel();
  3964. if (files.length != 1) return;
  3965. cmd.cmdpath = d3filetreelocation.join('/') + '/' + files[0];
  3966. meshserver.send(cmd);
  3967. if (options.func) { options.func(); }
  3968. }
  3969. }
  3970. // Look to see if we need to update the device timeline
  3971. function updateDeviceTimeline() {
  3972. if ((meshserver.State != 2) || (powerTimelineNode == null) || (powerTimelineUpdate == null) || (currentNode == null) || (currentNode.mtype == 3)) return;
  3973. if ((powerTimelineNode == powerTimelineReq) && (currentNode._id == powerTimelineNode) && (powerTimelineUpdate < Date.now())) { powerTimelineUpdate = null; meshserver.send({ action: 'powertimeline', nodeid: currentNode._id }); }
  3974. }
  3975. // Draw device power bars. The bars are 766px wide.
  3976. function drawDeviceTimeline() {
  3977. if (currentNode.mtype == 3 || hidePowerTimeline === 'true') { QH('p10html2', '<br />'); return; }
  3978. var timeline = null, now = Date.now();
  3979. if (currentNode._id == powerTimelineNode) { timeline = powerTimeline; }
  3980. // Calculate when the timeline starts
  3981. var d = new Date();
  3982. d.setHours(0, 0, 0, 0);
  3983. d = new Date(d.getTime() - (1000 * 60 * 60 * 24 * 6));
  3984. var timelineStart = d.getTime();
  3985. // De-compact the timeline
  3986. var timeline2 = [];
  3987. if (timeline != null && timeline.length > 1) {
  3988. timeline2.push([0, timeline[1], timeline[0]]); // Start, End, Power
  3989. var ct = timeline[1];
  3990. for (var i = 2; i < timeline.length; i += 2) {
  3991. var power = timeline[i], dt = now;
  3992. if (timeline.length > (i + 1)) { dt = timeline[i + 1]; }
  3993. timeline2.push([ct, ct + dt, power]); // Start, End, Power
  3994. ct = ct + dt;
  3995. }
  3996. }
  3997. // Draw the timeline
  3998. var x = '', count = 1, date = new Date();
  3999. var totalWidth = Q('masthead').offsetWidth - (90 + 9 + 9 + 14); // Compute the total width of the power bar
  4000. date.setHours(0, 0, 0, 0);
  4001. for (var i = 0; i < 7; i++) {
  4002. var datavalue = '', start = date.getTime(), end = start + (1000 * 60 * 60 * 24);
  4003. for (var j in timeline2) {
  4004. var block = timeline2[j];
  4005. if (isTimeBlockInside(start, end, block[0], block[1]) == true) {
  4006. var ts = Math.max(start, block[0]);
  4007. var te = Math.min(Math.min(end, block[1]), now);
  4008. var width = Math.round(((te - ts) * totalWidth) / 86400000);
  4009. if (width > 0) { datavalue += '<div style=display:table-cell;width:' + width + 'px;background-color:' + powerColor(block[2]) + ';height:16px></div>'; }
  4010. }
  4011. }
  4012. x += '<tr style=' + (((count % 2) == 0) ? 'background-color:#DDD' : '') + '><td><div>&nbsp;' + printDate(date) + '<div></div></div></td><td><div>' + datavalue + '</div></td></tr>';
  4013. ++count;
  4014. date = new Date(date.getFullYear(), date.getMonth(), date.getDate() - 1);
  4015. }
  4016. QH('p10html2', '<table style="color:black;background-color:#EEE;border-color:#AAA;border-width:1px;border-style:solid;border-collapse:collapse;width:calc(100% - 18px);margin:9px" border=0 cellpadding=2 cellspacing=0><tbody><tr style=background-color:#AAAAAA;font-weight:bold><th scope=col style=text-align:center;width:90px>' + "Day" + '</th><th scope=col style=text-align:center>' + "Power State" + '</th></tr>' + x + '</tbody></table>');
  4017. }
  4018. // Return a color for the given power state
  4019. function powerColor(x) { if (x < powerColorTable.length) { return powerColorTable[x]; } return 'yellow'; }
  4020. // Return true if the time block is visible within the start/end period
  4021. function isTimeBlockInside(start, end, blockStart, blockEnd) {
  4022. if ((blockStart < start) && (blockEnd > end)) return true; // Block is wider than timespan
  4023. if ((blockStart > start) && (blockStart < end)) return true;
  4024. if ((blockEnd > start) && (blockEnd < end)) return true;
  4025. return false;
  4026. }
  4027. function addDeviceAttribute(name, value) {
  4028. return '<tr><td style=width:100px;color:gray>' + name + '</td><td style=overflow:hidden>' + value + '</td></tr>';
  4029. }
  4030. function editDeviceAmtSettings(nodeid, func) {
  4031. if (xxdialogMode) return;
  4032. var x = '', node = getNodeFromId(nodeid), buttons = 3, meshrights = GetNodeRights(node);
  4033. if ((meshrights & 4) == 0) return;
  4034. x += addHtmlValue("Username", '<input id=dp10username style=width:170px maxlength=32 autocomplete=nope placeholder="admin" onchange=validateDeviceAmtSettings() onkeyup=validateDeviceAmtSettings() />');
  4035. x += addHtmlValue("Password", '<input id=dp10password type=password style=width:170px autocomplete=nope maxlength=32 onchange=validateDeviceAmtSettings() onkeyup=validateDeviceAmtSettings() />');
  4036. // Only display the TLS setting if the Intel AMT manager is not running on the server. With the manager TLS is auto-detected.
  4037. if ((features2 & 1) == 0) { x += addHtmlValue("Security", '<select id=dp10tls style=width:176px><option value=0>' + "No TLS security" + '</option><option value=1>' + "TLS security required" + '</option></select>'); }
  4038. if ((node.intelamt.user != null) && (node.intelamt.user != '')) { buttons = 7; }
  4039. setDialogMode(2, "Edit Intel&reg; AMT credentials", buttons, editDeviceAmtSettingsEx, x, { node: node, func: func });
  4040. if ((node.intelamt.user != null) && (node.intelamt.user != '')) { Q('dp10username').value = node.intelamt.user; } else { Q('dp10username').value = 'admin'; }
  4041. if ((features2 & 1) == 0) { Q('dp10tls').value = node.intelamt.tls; }
  4042. validateDeviceAmtSettings();
  4043. }
  4044. function validateDeviceAmtSettings() {
  4045. QE('idx_dlgOkButton', passwordcheck(Q('dp10password').value));
  4046. }
  4047. function editDeviceAmtSettingsEx(button, tag) {
  4048. if (button == 2) {
  4049. // Delete button pressed, remove credentials
  4050. meshserver.send({ action: 'changedevice', nodeid: tag.node._id, intelamt: { user: '', pass: '' } });
  4051. } else {
  4052. // Change Intel AMT credentials
  4053. var amtuser = Q('dp10username').value;
  4054. if (amtuser == '') amtuser = 'admin';
  4055. var amtpass = Q('dp10password').value;
  4056. if (amtpass == '') amtuser = '';
  4057. var x = { action: 'changedevice', nodeid: tag.node._id, intelamt: { user: amtuser, pass: amtpass } };
  4058. if ((features2 & 1) == 0) { x.intelamt.tls = parseInt(Q('dp10tls').value); }
  4059. meshserver.send(x);
  4060. if (tag.func) { setTimeout(tag.func, 1000); }
  4061. }
  4062. }
  4063. function p10showDeleteNodeDialog(nodeid) {
  4064. if (xxdialogMode) return;
  4065. setDialogMode(2, "Delete Node", 3, p10showDeleteNodeDialogEx, format("Delete {0}?", EscapeHtml(currentNode.name)) + '<br /><br /><label><input id=p10check type=checkbox onchange=p10validateDeleteNodeDialog() />' + "Confirm" + '</label>', nodeid);
  4066. p10validateDeleteNodeDialog();
  4067. }
  4068. function p10validateDeleteNodeDialog() {
  4069. QE('idx_dlgOkButton', Q('p10check').checked);
  4070. }
  4071. function p10showDeleteNodeDialogEx(buttons, nodeid) {
  4072. meshserver.send({ action: 'removedevices', nodeids: [nodeid] });
  4073. }
  4074. function p10WebRouter(nodeid, protocol, port, addr) {
  4075. var relayid = null;
  4076. var node = getNodeFromId(nodeid);
  4077. if (node.mtype == 3) { // Setup device relay if needed
  4078. var mesh = meshes[node.meshid];
  4079. if (mesh && mesh.relayid) { relayid = mesh.relayid; addr = node.host; }
  4080. }
  4081. var servername = serverinfo.name;
  4082. if ((servername.indexOf('.') == -1) || ((features & 2) != 0)) { servername = window.location.hostname; } // If the server name is not set or it's in LAN-only mode, use the URL hostname as server name.
  4083. if (webRelayDns != '') { servername = webRelayDns; }
  4084. var url = 'https://' + servername + ':' + webRelayPort + '/control-redirect.ashx?n=' + nodeid + '&p=' + port + '&appid=' + protocol + '&c=' + authRelayCookie; // Protocol: 1 = HTTP, 2 = HTTPS
  4085. if (addr != null) { url += '&addr=' + addr; }
  4086. if (relayid != null) { url += '&relayid=' + relayid; }
  4087. safeNewWindow(url, 'WebRelay');
  4088. return false;
  4089. }
  4090. function p10rfb(nodeid, port) {
  4091. var node = getNodeFromId(nodeid), addr = null;
  4092. var mesh = meshes[node.meshid];
  4093. if (port == null) { if (node.rfbport != null) { port = node.rfbport; } else { port = 5900; } }
  4094. if (node.mtype == 3) { if (mesh && mesh.relayid) { nodeid = mesh.relayid; addr = node.host; } } // Setup device relay if needed
  4095. meshserver.send({ action: 'getcookie', nodeid: nodeid, tcpport: port, tcpaddr: addr, tag: 'novnc', name: mesh ? mesh.name : null });
  4096. }
  4097. function p10mstsc(nodeid, port) {
  4098. var node = getNodeFromId(nodeid);
  4099. var mesh = meshes[node.meshid];
  4100. if (port == null) { if (node.rdpport != null) { port = node.rdpport; } else { port = 3389; } } // Adjust RDP port if needed
  4101. meshserver.send({ action: 'getcookie', nodeid: nodeid, tcpport: port, tag: 'mstsc', name: mesh ? mesh.name : null });
  4102. }
  4103. function p10ssh(nodeid, port) {
  4104. var node = getNodeFromId(nodeid);
  4105. var mesh = meshes[node.meshid];
  4106. if (port == null) { if (node.sshport != null) { port = node.sshport; } else { port = 22; } }
  4107. meshserver.send({ action: 'getcookie', nodeid: nodeid, tcpport: port, tag: 'ssh', name: mesh ? mesh.name : null });
  4108. }
  4109. function p10showiconselector() {
  4110. if (xxdialogMode) return;
  4111. var rights = GetNodeRights(currentNode);
  4112. if ((rights & 4) == 0) return;
  4113. var x = '<table align=center><td style=text-align:center>';
  4114. x += '<div style=display:inline-block class=i1 onclick=p10setIcon(1)></div>';
  4115. x += '<div style=display:inline-block class=i2 onclick=p10setIcon(2)></div>';
  4116. x += '<div style=display:inline-block class=i3 onclick=p10setIcon(3)></div>';
  4117. x += '<div style=display:inline-block class=i4 onclick=p10setIcon(4)></div><br />';
  4118. x += '<div style=display:inline-block class=i5 onclick=p10setIcon(5)></div>';
  4119. x += '<div style=display:inline-block class=i6 onclick=p10setIcon(6)></div>';
  4120. x += '<div style=display:inline-block class=i7 onclick=p10setIcon(7)></div>';
  4121. x += '<div style=display:inline-block class=i8 onclick=p10setIcon(8)></div></table>';
  4122. setDialogMode(2, "Icon Selection", 0, null, x);
  4123. QV('id_dialogclose', true);
  4124. }
  4125. function p10setIcon(icon) {
  4126. setDialogMode(0);
  4127. meshserver.send({ action: 'changedevice', nodeid: currentNode._id, icon: icon });
  4128. }
  4129. function showClearSshDialog() { setDialogMode(2, "Edit Device", 3, showClearSshDialogEx, "Clear SSH credentials?"); }
  4130. function showClearSshDialogEx(button, mode) { meshserver.send({ action: 'changedevice', nodeid: currentNode._id, ssh: 0 }); }
  4131. function showClearRdpDialog() { setDialogMode(2, "Edit Device", 3, showClearRdpDialogEx, "Clear RDP credentials?"); }
  4132. function showClearRdpDialogEx(button, mode) { meshserver.send({ action: 'changedevice', nodeid: currentNode._id, rdp: 0 }); }
  4133. var showEditNodeValueDialog_modes = ["Device Name", "Hostname", "Description", "Tags"];
  4134. var showEditNodeValueDialog_modes2 = ['name', 'host', 'desc', 'tags'];
  4135. var showEditNodeValueDialog_modes3 = ['', '', '', "Group1, Group2, Group3"];
  4136. function showEditNodeValueDialog(mode) {
  4137. if (xxdialogMode) return;
  4138. var x = addHtmlValue(showEditNodeValueDialog_modes[mode], '<input id=dp10devicevalue style=width:170px maxlength=64 placeholder="' + showEditNodeValueDialog_modes3[mode] + '" onchange=p10editdevicevalueValidate(' + mode + ',event) onkeyup=p10editdevicevalueValidate(' + mode + ',event) />');
  4139. if (mode == 3) {
  4140. // Get a list of all possible device tags
  4141. var allTags = [], y = '';
  4142. for (var i in nodes) { if (nodes[i].tags) { for (var j in nodes[i].tags) { if (allTags.indexOf(nodes[i].tags[j]) == -1) { allTags.push(nodes[i].tags[j]); } } } }
  4143. if (allTags.length > 0) {
  4144. allTags.sort();
  4145. for (var i in allTags) { y += '<span style=padding:4px;background-color:#BBB;border-radius:3px;cursor:pointer onclick=showEditNodeValueDialogAddTag("' + encodeURIComponentEx(allTags[i]) + '")>' + EscapeHtml(allTags[i]) + '</span> '; }
  4146. x += '<div style=margin-top:8px;width:280px;line-height:26px;max-height:160px;overflow-y:auto>' + y + '</div>';
  4147. }
  4148. }
  4149. setDialogMode(2, "Edit Device", 3, showEditNodeValueDialogEx, x, mode);
  4150. var v = currentNode[showEditNodeValueDialog_modes2[mode]];
  4151. if (v == null) v = '';
  4152. if (Array.isArray(v)) { v = v.join(', '); }
  4153. Q('dp10devicevalue').value = v;
  4154. p10editdevicevalueValidate();
  4155. Q('dp10devicevalue').focus();
  4156. }
  4157. function showEditNodeValueDialogAddTag(t) {
  4158. var tt = Q('dp10devicevalue').value.split(','), t2 = [];
  4159. for (var i in tt) { t2.push(tt[i].trim()); }
  4160. if (t2.indexOf(t) >= 0) return;
  4161. Q('dp10devicevalue').value += ((Q('dp10devicevalue').value.length == 0) ? '' : ', ') + decodeURIComponent(t);
  4162. setTimeout(function () { Q('dp10devicevalue').selectionStart = Q('dp10devicevalue').selectionEnd = 90000; }, 0);
  4163. p10editdevicevalueValidate();
  4164. }
  4165. function showEditNodeValueDialogEx(button, mode) {
  4166. var x = { action: 'changedevice', nodeid: currentNode._id };
  4167. x[showEditNodeValueDialog_modes2[mode]] = Q('dp10devicevalue').value;
  4168. meshserver.send(x);
  4169. }
  4170. function p10editdevicevalueValidate(mode, e) {
  4171. var x = ((mode > 1) || (Q('dp10devicevalue').value.length > 0));
  4172. QE('idx_dlgOkButton', x);
  4173. if ((e != null) && (x == true) && (e.keyCode == 13)) { dialogclose(1); }
  4174. }
  4175. //
  4176. // DESKTOP
  4177. //
  4178. var desktop;
  4179. var desktopNode;
  4180. var desktopsettings = { encoding: 2, showfocus: false, showmouse: true, showcad: true, quality: 40, scaling: 1024, framerate: 50, autolock: false, agentencoding: 4 };
  4181. function setupDesktop() {
  4182. // Setup the remote desktop
  4183. if ((desktopNode != currentNode) && (desktop != null)) { desktop.Stop(); desktopNode = null; desktop = null; }
  4184. // If the device desktop is already connected in multi-desktop, use that.
  4185. if ((desktopNode != currentNode) || (desktop == null)) {
  4186. // Device is not already connected, just setup a blank canvas
  4187. //QH('DeskParent', '<canvas id=Desk width=640 height=200 style="width:100%;-ms-touch-action:none;margin-left:0px" oncontextmenu="return false" onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event)></canvas>');
  4188. desktopNode = currentNode;
  4189. // Setup the mouse wheel
  4190. Q('Desk').addEventListener('DOMMouseScroll', function (e) { return dmousewheel(e); });
  4191. Q('Desk').addEventListener('mousewheel', function (e) { return dmousewheel(e); });
  4192. }
  4193. desktopNode = currentNode;
  4194. updateDesktopButtons();
  4195. // On some browsers like IE, we can't save screen shots. Hide the scheenshot/capture buttons.
  4196. if (!Q('Desk')['toBlob']) { QV('deskSaveBtn', false); }
  4197. }
  4198. // Show and enable the right buttons
  4199. function updateDesktopButtons() {
  4200. var mesh = meshes[currentNode.meshid];
  4201. var deskState = 0;
  4202. if (desktop != null) { deskState = desktop.State; }
  4203. var meshrights = GetNodeRights(currentNode);
  4204. // Show the right buttons
  4205. QV('disconnectbutton1', (deskState != 0));
  4206. QE('deskFullScreen', (deskState != 0));
  4207. QV('connectbutton1', (deskState == 0) && ((meshrights & 8) || (meshrights & 256)) && (currentNode.agent != null) && (currentNode.agent.caps & 1));
  4208. QV('connectbutton1h',
  4209. (deskState == 0) &&
  4210. (meshrights & 8) &&
  4211. (
  4212. ((currentNode.intelamt != null) &&
  4213. (currentNode.intelamt.state == 2) &&
  4214. (currentNode.intelamt.ver != null) &&
  4215. ((currentNode.intelamt.sku == null) ||
  4216. ((typeof currentNode.intelamt.sku == 'number') &&
  4217. ((currentNode.intelamt.sku & 8) != 0))))
  4218. )
  4219. );
  4220. // Show the right settings
  4221. QV('d7amtkvm', (currentNode.intelamt != null && ((typeof currentNode.intelamt.sku != 'number') || ((currentNode.intelamt.sku & 16) == 0)) && ((currentNode.intelamt.ver != null) || (currentNode.agent == null))) && ((deskState == 0) || (desktop.contype == 2)));
  4222. QV('d7meshkvm', ((currentNode.agent != null) && (currentNode.agent.caps & 1) && ((deskState == false) || (desktop.contype == 1))));
  4223. // Enable buttons
  4224. var online = ((currentNode.conn & 1) != 0); // If Agent (1) connected, enable remote desktop
  4225. QE('connectbutton1', online);
  4226. var hwonline = ((currentNode.conn & 6) != 0); // If CIRA (2) or AMT (4) connected, enable hardware terminal
  4227. QE('connectbutton1h', hwonline);
  4228. //QE('deskSaveBtn', deskState == 3);
  4229. //QV('DeskCAD', meshrights & 8);
  4230. //QE('DeskCAD', deskState == 3);
  4231. //QV('DeskWD', (currentNode.agent) && (currentNode.agent.id < 5));
  4232. //QE('DeskWD', deskState == 3);
  4233. //QV('deskkeys', (currentNode.agent) && (currentNode.agent.id < 5));
  4234. //QE('deskkeys', deskState == 3);
  4235. //QE('DeskToolsButton', online);
  4236. QV('DeskToastButton', ((meshrights & 16384) != 0) && (currentNode.agent) && (currentNode.agent.id < 5) && (meshrights & 8));
  4237. //QE('DeskToastButton', online);
  4238. QV('deskActionsBtn', meshrights & 8);
  4239. Q('DeskControl').checked = ((meshrights & 8) != 0);
  4240. if (online == false) QV('DeskTools', false);
  4241. }
  4242. // Used to translate incoming agent console messages
  4243. var agentConsoleMessages = ['', "Waiting for user to grant access...", "Denied", "Failed to start remote terminal session, {0} ({1})", "Timeout", "Received invalid network data"];
  4244. function formatAgentConsoleMessage(msg, msgid, msgargs) {
  4245. var r;
  4246. if (msgargs == null) { msgargs = []; }
  4247. while (msgargs.length < 3) { msgargs.push(''); } // We need to call the format function in a way that works with older browsers and minifier, can't use apply() or ...
  4248. if (msgid && (msgid < agentConsoleMessages.length)) { r = EscapeHtml(format(agentConsoleMessages[msgid], (msgargs[0]), (msgargs[1]), (msgargs[2]))); } else { r = EscapeHtml(msg); }
  4249. return r.split('\n').join('<br />') + '<br /><br />';
  4250. }
  4251. function connectDesktop(e, contype, tsid, consent) {
  4252. setSessionActivity();
  4253. QV('p11DeskSessionSelector', false);
  4254. p11clearConsoleMsg();
  4255. if (desktop == null) {
  4256. desktopNode = currentNode;
  4257. if (contype == 2) {
  4258. // Setup the Intel AMT remote desktop
  4259. if ((desktopNode.intelamt.user == null) || (desktopNode.intelamt.user == '')) { editDeviceAmtSettings(desktopNode._id, connectDesktop); return; }
  4260. desktop = CreateAmtRedirect(CreateAmtRemoteDesktop('Desk'), authCookie);
  4261. desktop.debugmode = debugmode;
  4262. desktop.onStateChanged = onDesktopStateChange;
  4263. desktop.m.bpp = (desktopsettings.encoding == 1 || desktopsettings.encoding == 3) ? 1 : 2;
  4264. desktop.m.useZRLE = (desktopsettings.encoding < 3);
  4265. desktop.m.showmouse = true;
  4266. desktop.m.onScreenSizeChange = function (o, x, y) { if (fullscreen) { QS('deskarea3').width = (x * fullscreenzoom) + 'px'; QS('deskarea3').height = (y * fullscreenzoom) + 'px'; } deskAdjust(); }
  4267. // Use TLS if TLS is set
  4268. if (desktopNode.conn==4 && desktopNode.intelamt!=null && desktopNode.intelamt.tls==1) {
  4269. desktop.Start(desktopNode._id, 16995, '*', '*', 1);
  4270. } else {
  4271. desktop.Start(desktopNode._id, 16994, '*', '*', 0);
  4272. }
  4273. desktop.contype = 2;
  4274. } else if ((contype == null) || (contype == 1) || ((contype == 3) && (currentNode.agent.id > 4))) {
  4275. // Setup the Mesh Agent remote desktop
  4276. desktop = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
  4277. desktop.debugmode = debugmode;
  4278. desktop.m.debugmode = debugmode;
  4279. desktop.attemptWebRTC = attemptWebRTC;
  4280. desktop.webrtcconfig = webrtcconfiguration;
  4281. desktop.options = {};
  4282. if (tsid != null) { desktop.options.tsid = tsid; }
  4283. if (consent != null) { desktop.options.consent = consent; }
  4284. if (desktopsettings.autolock == true) { desktop.options.autolock = true; }
  4285. desktop.onStateChanged = onDesktopStateChange;
  4286. if ((features2 & 0x2000) != 0) desktop.m.stopInput = true;
  4287. desktop.onConsoleMessageChange = function () {
  4288. if (desktop.consoleMessage) {
  4289. Q('p11DeskConsoleMsg').innerHTML += formatAgentConsoleMessage(desktop.consoleMessage, desktop.consoleMessageId, desktop.consoleMessageArgs);
  4290. QV('p11DeskConsoleMsg', true);
  4291. if (p11DeskConsoleMsgTimer != null) { clearTimeout(p11DeskConsoleMsgTimer); }
  4292. if (desktop.consoleMessageTimeout) { p11DeskConsoleMsgTimer = setTimeout(p11clearConsoleMsg, desktop.consoleMessageTimeout * 1000); }
  4293. } else {
  4294. p11clearConsoleMsg();
  4295. }
  4296. }
  4297. desktop.m.ImageType = desktopsettings.agentencoding; // Send 4 if WebP is supported, otherwise send 1 for JPEG.
  4298. desktop.m.CompressionLevel = desktopsettings.quality; // Number from 1 to 100. 50 or less is best.
  4299. desktop.m.ScalingLevel = desktopsettings.scaling;
  4300. desktop.m.FrameRateTimer = desktopsettings.framerate;
  4301. desktop.m.onDisplayinfo = deskDisplayInfo;
  4302. desktop.m.onScreenSizeChange = function (o, x, y) { if (fullscreen) { QS('deskarea3').width = (x * fullscreenzoom) + 'px'; QS('deskarea3').height = (y * fullscreenzoom) + 'px'; } deskAdjust(); }
  4303. desktop.Start(desktopNode._id);
  4304. desktop.contype = 1;
  4305. } else if (contype == 3) {
  4306. // Ask for user sessions
  4307. meshserver.send({ action: 'msg', type: 'userSessions', nodeid: currentNode._id, tag: consent });
  4308. }
  4309. } else {
  4310. // Disconnect and clean up the remote desktop
  4311. desktop.Stop();
  4312. desktopNode = desktop = null;
  4313. }
  4314. }
  4315. function p11clearConsoleMsg() { QH('p11DeskConsoleMsg', ''); QV('p11DeskConsoleMsg', false); if (p11DeskConsoleMsgTimer) { clearTimeout(p11DeskConsoleMsgTimer); p11DeskConsoleMsgTimer = null; } }
  4316. function p12clearConsoleMsg() { QH('p12TermConsoleMsg', ''); QV('p12TermConsoleMsg', false); if (p12TermConsoleMsgTimer) { clearTimeout(p12TermConsoleMsgTimer); p12TermConsoleMsgTimer = null; } }
  4317. function p13clearConsoleMsg() { QH('p13FilesConsoleMsg', ''); QV('p13FilesConsoleMsg', false); if (p13FilesConsoleMsgTimer) { clearTimeout(p13FilesConsoleMsgTimer); p13FilesConsoleMsgTimer = null; } }
  4318. function p12setConsoleMsg(msg, timeout) {
  4319. if (msg) {
  4320. Q('p12TermConsoleMsg').innerHTML += msg;
  4321. QV('p12TermConsoleMsg', true);
  4322. if (p12TermConsoleMsgTimer != null) { clearTimeout(p12TermConsoleMsgTimer); }
  4323. if (timeout) { p12TermConsoleMsgTimer = setTimeout(p12clearConsoleMsg, timeout); }
  4324. } else {
  4325. p12clearConsoleMsg();
  4326. }
  4327. }
  4328. function p13setConsoleMsg(msg, timeout) {
  4329. if (msg) {
  4330. Q('p13FilesConsoleMsg').innerHTML += msg;
  4331. QV('p13FilesConsoleMsg', true);
  4332. if (p13FilesConsoleMsgTimer != null) { clearTimeout(p13FilesConsoleMsgTimer); }
  4333. if (timeout) { p13FilesConsoleMsgTimer = setTimeout(p13clearConsoleMsg, timeout); }
  4334. } else {
  4335. p13clearConsoleMsg();
  4336. }
  4337. }
  4338. function onDesktopStateChange(xdesktop, state) {
  4339. var xstate = state;
  4340. if ((xstate == 3) && (xdesktop.contype == 2)) { xstate++; }
  4341. var str = StatusStrs[xstate];
  4342. if ((desktop != null) && (desktop.webRtcActive == true)) { str += ", WebRTC"; }
  4343. //if (desktop.m.stopInput == true) { str += ', Loopback'; }
  4344. QH('deskstatus', str);
  4345. switch (state) {
  4346. case 0:
  4347. // Disconnect and clean up the remote desktop
  4348. desktop.Stop();
  4349. desktopNode = desktop = null;
  4350. QV('DeskScreens', false);
  4351. if (fullscreen == true) { deskToggleFull(); }
  4352. break;
  4353. case 2:
  4354. break;
  4355. default:
  4356. //console.log('Unknown onDesktopStateChange state', state);
  4357. break;
  4358. }
  4359. updateDesktopButtons();
  4360. deskAdjust();
  4361. setTimeout(deskAdjust, 50);
  4362. }
  4363. function showDesktopSettings() {
  4364. if (xxdialogMode) return;
  4365. applyDesktopSettings();
  4366. updateDesktopButtons();
  4367. setDialogMode(7, "Remote Desktop Settings", 3, showDesktopSettingsChanged);
  4368. }
  4369. function showDesktopSettingsChanged() {
  4370. desktopsettings.encoding = d7desktopmode.value;
  4371. desktopsettings.quality = d7bitmapquality.value;
  4372. desktopsettings.scaling = d7bitmapscaling.value;
  4373. desktopsettings.framerate = d7framelimiter.value;
  4374. desktopsettings.autolock = d7deskAutoLock.checked;
  4375. desktopsettings.agentencoding = d7encoding.value;
  4376. localStorage.setItem('desktopsettings', JSON.stringify(desktopsettings));
  4377. applyDesktopSettings();
  4378. if (desktop) {
  4379. if (desktop.contype == 1) {
  4380. if (desktop.State != 0) { desktop.m.SendCompressionLevel(desktopsettings.agentencoding, desktopsettings.quality, desktopsettings.scaling, desktopsettings.framerate); }
  4381. desktop.sendCtrlMsg('{"ctrlChannel":"102938","type":"autolock","value":' + desktopsettings.autolock + '}');
  4382. desktop.m.SendRefresh();
  4383. }
  4384. if (desktop.contype == 2) {
  4385. if (desktop.State != 0) { desktop.Stop(); setTimeout(function () { connectDesktop(null, 2); }, 50); }
  4386. }
  4387. }
  4388. }
  4389. function applyDesktopSettings() {
  4390. var r = '', ops = (features & 512) ? [100, 90, 70, 50, 40, 30, 20, 10, 5, 1] : [50, 40, 30, 20, 10, 5, 1];
  4391. for (var i in ops) { r += '<option value=' + ops[i] + '>' + ops[i] + '%</option>'; }
  4392. QH('d7bitmapquality', r);
  4393. d7desktopmode.value = desktopsettings.encoding;
  4394. d7bitmapquality.value = 40; // Default value
  4395. if (desktopsettings.agentencoding) { d7encoding.value = desktopsettings.agentencoding; } else { desktopsettings.agentencoding = 4; }
  4396. if (ops.indexOf(parseInt(desktopsettings.quality)) >= 0) { d7bitmapquality.value = desktopsettings.quality; }
  4397. d7bitmapscaling.value = desktopsettings.scaling;
  4398. if (desktopsettings.framerate) { d7framelimiter.value = desktopsettings.framerate; }
  4399. if (desktopsettings.autolock != null) { d7deskAutoLock.checked = desktopsettings.autolock; }
  4400. }
  4401. var keyboardShown = false;
  4402. var keyboardShownTimer = null;
  4403. var fullScreenMode = false;
  4404. function toggleKeyboard() {
  4405. if (xxdialogMode) return;
  4406. if (keyboardShownTimer != null) { clearTimeout(keyboardShownTimer); }
  4407. if (keyboardShown) { Q('softKeyboard').blur(); keyboardShown = false; } else { Q('softKeyboard').focus(); keyboardShown = true; }
  4408. QV('deskkeybutton2a', fullscreen && !keyboardShown);
  4409. QV('deskkeybutton2b', fullscreen && keyboardShown);
  4410. }
  4411. function keyboardFocusChange() {
  4412. keyboardShownTimer = setTimeout(function () {
  4413. keyboardShownTimer = null;
  4414. keyboardShown = (Q('softKeyboard') == document.activeElement);
  4415. QV('deskkeybutton2a', fullscreen && !keyboardShown);
  4416. QV('deskkeybutton2b', fullscreen && keyboardShown);
  4417. }, 10);
  4418. }
  4419. function exitButton() {
  4420. if (xxdialogMode) return;
  4421. QV('deskButtonMenu', false);
  4422. QV('termButtonMenu', false);
  4423. deskToggleFull();
  4424. }
  4425. function deskMenuButton(x) {
  4426. toggleMenu(true);
  4427. deskSendKeys(x);
  4428. }
  4429. //
  4430. // Desktop Shortcut Keys
  4431. //
  4432. function updateDeskShortcutKeys() {
  4433. var x = '<div class="menuButton" onclick="deskMenuButton(-1)">' + "Customize" + '</div>';
  4434. for (var i in deskKeyboardShortcuts) { x += '<div class="menuButton" onclick="deskMenuButton(' + deskKeyboardShortcuts[i] + ')">' + keyShortcutTotext(deskKeyboardShortcuts[i]) + '</div>'; }
  4435. QH('deskButtonMenu', x);
  4436. }
  4437. var keyStrings = { 8: "BackSpace", 9: "Tab", 13: "Enter", 27: "Escape", 32: "Space", 44: "Print Screen", 45: "Insert", 46: "Del", 36: "Home", 35: "End", 32: "Espace", 33: "Page Up", 34: "Page Down", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 0: "None" }
  4438. function keyShortcutTotext(n) {
  4439. var x = [];
  4440. if (n & 0x010000) { x.push("Shift"); }
  4441. if (n & 0x020000) { x.push("Alt"); }
  4442. if (n & 0x080000) { x.push("Ctrl"); }
  4443. if (n & 0x100000) { x.push("Win"); }
  4444. n = (n & 0xFFFF);
  4445. if ((n >= 112) && (n <= 123)) { x.push('F' + (n - 111)); } // Fx keys
  4446. else if ((n != 0) && (keyStrings[n])) { x.push(keyStrings[n]); }
  4447. else { if (n != 0) { x.push(String.fromCharCode(n)); } }
  4448. return x.join(' + ');
  4449. }
  4450. // Customize keyboard shortcuts
  4451. function deskCustomizeKeys() {
  4452. if (xxdialogMode) return;
  4453. var x = '<div id=d2shortcuts style="width:100%;height:180px;padding:4px;overflow-y:auto;border:1px solid gray"></div><div style=width:100%;padding:5px>';
  4454. x += '<label><input id=d1kshift type=checkbox /> ' + "Shift" + '</label><label> <input id=d1kalt type=checkbox /> ' + "Alt" + '</label><label> <input id=d1kctrl type=checkbox /> ' + "Ctrl" + '</label> <input id=d1kwin type=checkbox /> ' + "Win" + '</label>';
  4455. x += ' <select id=d2keySelect>';
  4456. for (var i in keyStrings) { x += '<option value=' + i + '>' + keyStrings[i] + '</option>'; }
  4457. for (var i = 1; i <= 12; i++) { x += '<option value=' + (i + 111) + '>F' + i + '</option>'; }
  4458. for (var i = 0; i < 10; i++) { x += '<option value=' + (i + 48) + '>' + i + '</option>'; }
  4459. for (var i = 0; i < 26; i++) { x += '<option value=' + (i + 65) + '>' + String.fromCharCode(i + 65) + '</option>'; }
  4460. x += '</select> <input type=button value=' + "Add" + ' onclick=addDeskCustomizeKey() /></div>';
  4461. QH('p10dialog2', x);
  4462. xxdialogMode = 2;
  4463. QV('p10dialog', true);
  4464. deskUpdateShortcutList();
  4465. }
  4466. function deskCustomizeKeysEx() {
  4467. QV('p10dialog', false);
  4468. xxdialogMode = 0;
  4469. putstore('deskKeyShortcuts', deskKeyboardShortcuts.join(','));
  4470. updateDeskShortcutKeys();
  4471. }
  4472. function restoreDeskCustomizeKey() {
  4473. deskKeyboardShortcuts = [];
  4474. putstore('deskKeyShortcuts', null);
  4475. var deskKeyboardShortcutsStr = getstore('deskKeyShortcuts', '0x0A002E,0x100000,0x100028,0x100026,0x10004C,0x10004D,0x11004D,0x100052,0x020073,0x080057,0x020009,0x100025,0x100027').split(',');
  4476. for (var i in deskKeyboardShortcutsStr) { if (deskKeyboardShortcutsStr[i] != "") { deskKeyboardShortcuts.push(parseInt(deskKeyboardShortcutsStr[i])); } }
  4477. updateDeskShortcutKeys();
  4478. deskUpdateShortcutList();
  4479. }
  4480. function deskUpdateShortcutList() {
  4481. var x = '';
  4482. for (var i in deskKeyboardShortcuts) {
  4483. var kt = keyShortcutTotext(deskKeyboardShortcuts[i]), orderButtons = '';
  4484. if (i != (deskKeyboardShortcuts.length - 1)) { orderButtons += '<img width=8 height=8 style=float:right;cursor:pointer;padding:3px src="images/c2.png" onclick=deskCustomizeKeyDown(' + deskKeyboardShortcuts[i] + ')>'; }
  4485. if (i != 0) { orderButtons += '<img width=8 height=8 style=float:right;cursor:pointer;padding:3px src="images/c3.png" onclick=deskCustomizeKeyUp(' + deskKeyboardShortcuts[i] + ')>'; }
  4486. x += '<div style="width:100%;background-color:#AAA;border-radius:4px;margin-bottom:4px;padding:4px;text-align:left;box-sizing:border-box" value=' + deskKeyboardShortcuts[i] + '>' + kt + '<img width=10 height=10 style=float:right;cursor:pointer;padding:2px;margin-left:8px src="images/trash.png" onclick=removeDeskCustomizeKey(' + deskKeyboardShortcuts[i] + ')>' + orderButtons + '</div>';
  4487. }
  4488. if (x == '') { x = '<i>' + "No keyboard shortcuts defined" + '</i>'; }
  4489. QH('d2shortcuts', x);
  4490. }
  4491. function deskCustomizeKeyDown(k) {
  4492. var i = deskKeyboardShortcuts.indexOf(k), x = deskKeyboardShortcuts[i + 1];
  4493. deskKeyboardShortcuts[i + 1] = deskKeyboardShortcuts[i];
  4494. deskKeyboardShortcuts[i] = x;
  4495. deskUpdateShortcutList();
  4496. }
  4497. function deskCustomizeKeyUp(k) {
  4498. var i = deskKeyboardShortcuts.indexOf(k), x = deskKeyboardShortcuts[i];
  4499. deskKeyboardShortcuts[i] = deskKeyboardShortcuts[i - 1];
  4500. deskKeyboardShortcuts[i - 1] = x;
  4501. deskUpdateShortcutList();
  4502. }
  4503. function removeDeskCustomizeKey(k) {
  4504. var na = [];
  4505. for (var i in deskKeyboardShortcuts) { if (deskKeyboardShortcuts[i] != k) { na.push(deskKeyboardShortcuts[i]); } }
  4506. deskKeyboardShortcuts = na;
  4507. deskUpdateShortcutList();
  4508. }
  4509. function addDeskCustomizeKey() {
  4510. var k = parseInt(Q('d2keySelect').value);
  4511. if (Q('d1kshift').checked) { k |= 0x010000; }
  4512. if (Q('d1kalt').checked) { k |= 0x020000; }
  4513. if (Q('d1kctrl').checked) { k |= 0x080000; }
  4514. if (Q('d1kwin').checked) { k |= 0x100000; }
  4515. if ((k > 0) && (deskKeyboardShortcuts.indexOf(k) == -1)) { deskKeyboardShortcuts.push(k); deskUpdateShortcutList(); }
  4516. }
  4517. // Remote desktop special key combos for Windows
  4518. function deskSendKeys(ks) {
  4519. if (xxdialogMode || desktop == null || desktop.State != 3) return;
  4520. // Construct the key command
  4521. if (ks == -1) { deskCustomizeKeys(); return; } // Customize
  4522. if (ks == 0x0A002E) { desktop.m.sendcad(); return; } // CTRL-ALT-DEL
  4523. //if ((desktop.contype == 1) && (ks == 0x10004C)) { desktop.sendCtrlMsg('{"action":"lock"}'); return; } // Lock desktop, WIN + L
  4524. var flags = (ks & 0xFF0000) >> 16, key = (ks & 0xFFFF), keyArray = [], keyArray2 = [];
  4525. var amtTranslate = {
  4526. 8: 0xff08, // BackSpace
  4527. 9: 0xff09, // Tab
  4528. 13: 0xff0d, // Return or Enter
  4529. 27: 0xff1b, // Escape
  4530. 45: 0xff63, // Insert
  4531. 46: 0xffff, // Delete
  4532. 36: 0xff50, // Home
  4533. 35: 0xff57, // End
  4534. 33: 0xff55, // Page Up
  4535. 34: 0xff56, // Page Down
  4536. 37: 0xff51, // Left arrow
  4537. 38: 0xff52, // Up arrow
  4538. 39: 0xff53, // Right arrow
  4539. 40: 0xff54, // Down arrow
  4540. 112: 0xffbe, // F1
  4541. 113: 0xffbf, // F2
  4542. 114: 0xffc0, // F3
  4543. 115: 0xffc1, // F4
  4544. 116: 0xffc2, // F5
  4545. 117: 0xffc3, // F6
  4546. 118: 0xffc4, // F7
  4547. 119: 0xffc5, // F8
  4548. 120: 0xffc6, // F9
  4549. 121: 0xffc7, // F10
  4550. 122: 0xffc8, // F11
  4551. 123: 0xffc9 // F12
  4552. }
  4553. // 0x010000 = Shift
  4554. // 0x020000 = Left-Alt
  4555. // 0x080000 = Ctrl
  4556. // 0x100000 = Window
  4557. if (desktop.contype == 2) {
  4558. // Intel AMT
  4559. if (flags & 1) { keyArray.push([0xffe1, 1]); keyArray2.push([0xffe1, 0]); } // Shift
  4560. if (flags & 2) { keyArray.push([0xffe9, 1]); keyArray2.push([0xffe9, 0]); } // Left-alt
  4561. if (flags & 8) { keyArray.push([0xffe3, 1]); keyArray2.push([0xffe3, 0]); } // Ctrl
  4562. if (flags & 16) { keyArray.push([0xffe7, 1]); keyArray2.push([0xffe7, 0]); } // Windows key
  4563. if (amtTranslate[key]) { key = amtTranslate[key]; }
  4564. if ((key >= 65) && (key <= 90)) { key += 32; }
  4565. if (key != 0) { keyArray.push([key, 1]); keyArray2.push([key, 0]); }
  4566. keyArray2.reverse();
  4567. for (var i = 0; i < keyArray2.length; i++) { keyArray.push(keyArray2[i]); }
  4568. desktop.m.sendkey(keyArray);
  4569. } else {
  4570. // Agent desktop
  4571. if (flags & 1) { keyArray.push([desktop.m.KeyAction.DOWN, 16]); keyArray2.push([desktop.m.KeyAction.UP, 16]); } // Shift
  4572. if (flags & 2) { keyArray.push([desktop.m.KeyAction.EXDOWN, 18]); keyArray2.push([desktop.m.KeyAction.EXUP, 18]); } // Left-alt
  4573. if (flags & 8) { keyArray.push([desktop.m.KeyAction.EXDOWN, 17]); keyArray2.push([desktop.m.KeyAction.EXUP, 17]); } // Ctrl
  4574. if (flags & 16) { keyArray.push([desktop.m.KeyAction.EXDOWN, 0x5B]); keyArray2.push([desktop.m.KeyAction.EXUP, 0x5B]); } // Windows key
  4575. if (key != 0) { keyArray.push([desktop.m.KeyAction.DOWN, key]); keyArray2.push([desktop.m.KeyAction.UP, key]); }
  4576. keyArray2.reverse();
  4577. for (var i = 0; i < keyArray2.length; i++) { keyArray.push(keyArray2[i]); }
  4578. desktop.m.SendKeyMsgKC(keyArray);
  4579. }
  4580. }
  4581. function toggleMenu(x) {
  4582. if (xxdialogMode) return;
  4583. QV('deskButtonMenu', fullscreen && !x && (currentDevicePanel == 1));
  4584. QV('termButtonMenu', fullscreen && !x && (currentDevicePanel == 5));
  4585. QV('deskkeybutton3a', fullscreen && x);
  4586. QV('deskkeybutton3b', fullscreen && !x);
  4587. }
  4588. function deskChangeMouseButton(x) {
  4589. if (xxdialogMode) return;
  4590. if (desktop == null) return;
  4591. desktop.m.SwapMouse = !desktop.m.SwapMouse;
  4592. QV('deskkeybutton4a', fullscreen && (!desktop.m.SwapMouse));
  4593. QV('deskkeybutton4b', fullscreen && (desktop.m.SwapMouse));
  4594. }
  4595. function deskChangeFullscreenZoom() {
  4596. if (xxdialogMode) return;
  4597. if (currentDevicePanel == 1) {
  4598. if (desktop == null) return;
  4599. if (fullscreenzoom == 1) { fullscreenzoom = 0.5; } else { fullscreenzoom = 1; }
  4600. QV('deskkeybutton5a', fullscreen && (fullscreenzoom == 1));
  4601. QV('deskkeybutton5b', fullscreen && (fullscreenzoom != 1));
  4602. QS('deskarea3').width = (desktop.m.ScreenWidth * fullscreenzoom) + 'px';
  4603. QS('deskarea3').height = (desktop.m.ScreenHeight * fullscreenzoom) + 'px';
  4604. deskAdjust();
  4605. }
  4606. if (currentDevicePanel == 5) {
  4607. if (terminal == null) return;
  4608. xterm.setOption('fontSize', (xterm.getOption('fontSize') == 15) ? 10 : 15)
  4609. }
  4610. }
  4611. var fullscreen = false;
  4612. var fullscreenzoom = 1;
  4613. function deskToggleFull() {
  4614. fullscreen = !fullscreen;
  4615. QV('mastheadx', !fullscreen);
  4616. QV('masthead', !fullscreen);
  4617. QV('topbar', !fullscreen);
  4618. QV('p11deviceNameHeader', !fullscreen);
  4619. QV('footer', !fullscreen);
  4620. QV('column_l_bottomgap', !fullscreen);
  4621. QV('idx_deskFullBtn2', fullscreen);
  4622. QV('deskFullBtn', !fullscreen);
  4623. QV('p10deskTopTable', !fullscreen);
  4624. QV('deskarea1', !fullscreen);
  4625. QV('deskarea4', !fullscreen);
  4626. QV('termarea1', !fullscreen);
  4627. QV('termarea4', !fullscreen);
  4628. var rights = GetNodeRights(currentNode);
  4629. var inputAllowed = ((features2 & 0x2000) == 0) && (currentNode.agent.id != 14) && ((rights == 0xFFFFFFFF) || (((rights & 8) != 0) && ((rights & 256) == 0) && ((rights & 4096) == 0)));
  4630. // Show full screen buttons if needed
  4631. QV('deskkeybutton1', fullscreen);
  4632. if (currentDevicePanel == 1) { // Desktop panel is being shown (1 = Desktop, 5 = Terminal)
  4633. // Move shortcut key to desktop position
  4634. QS('deskkeybutton2a').top = QS('deskkeybutton2b').top = '210px';
  4635. // Move the zoom button to normal or top position
  4636. QS('deskkeybutton5a').top = QS('deskkeybutton5b').top = (inputAllowed) ? '160px' : '60px'; // Zoom
  4637. QV('deskkeybutton2a', fullscreen && inputAllowed);
  4638. QV('deskkeybutton2b', false);
  4639. QV('deskkeybutton3a', fullscreen && inputAllowed);
  4640. QV('deskkeybutton3b', false);
  4641. QV('deskkeybutton4a', fullscreen && inputAllowed && (!desktop.m.SwapMouse));
  4642. QV('deskkeybutton4b', fullscreen && inputAllowed && (desktop.m.SwapMouse));
  4643. QV('deskkeybutton5a', fullscreen && (fullscreenzoom == 1));
  4644. QV('deskkeybutton5b', fullscreen && (fullscreenzoom != 1));
  4645. }
  4646. if (currentDevicePanel == 5) {
  4647. // Move right buttons to terminal position
  4648. //QS('deskkeybutton3a').top = QS('deskkeybutton3b').top = '60px'; // Shortcuts
  4649. //QS('deskkeybutton5a').top = QS('deskkeybutton5b').top = '110px'; // Zoom
  4650. QS('deskkeybutton2a').top = QS('deskkeybutton2b').top = '110px'; // Keyboard
  4651. QV('deskkeybutton2a', fullscreen);
  4652. QV('deskkeybutton2b', false);
  4653. QV('deskkeybutton3a', fullscreen);
  4654. QV('deskkeybutton3b', false);
  4655. QV('deskkeybutton4a', false);
  4656. QV('deskkeybutton4b', false);
  4657. QV('deskkeybutton5a', false);
  4658. QV('deskkeybutton5a', false);
  4659. //QV('deskkeybutton5a', xterm.getOption('fontSize') == 15);
  4660. //QV('deskkeybutton5b', xterm.getOption('fontSize') != 15);
  4661. }
  4662. if (fullscreen) {
  4663. QS('DeskParent').height = null;
  4664. QS('page_content').top = '0px';
  4665. QS('page_content').bottom = '0px';
  4666. if (currentDevicePanel == 1) {
  4667. QS('p10desktop').top = '0px';
  4668. QS('p10desktop').overflow = 'scroll';
  4669. QS('deskarea3').top = '0px';
  4670. QS('deskarea3').width = (desktop.m.ScreenWidth * fullscreenzoom) + 'px';
  4671. QS('deskarea3').height = (desktop.m.ScreenHeight * fullscreenzoom) + 'px';
  4672. QS('deskarea3')['padding-right'] = '55px';
  4673. }
  4674. if (currentDevicePanel == 5) {
  4675. QS('p10terminal').top = '0px';
  4676. QS('p10terminal').overflow = 'scroll';
  4677. QS('termarea3').top = '0px';
  4678. QS('termarea3').bottom = null;
  4679. QS('termarea3').right = null;
  4680. QS('termarea3')['padding-right'] = '55px';
  4681. QS('termarea3')['height'] = '100%';
  4682. }
  4683. QS('body')['background-color'] = '#000';
  4684. QS('p10')['background-color'] = '#000';
  4685. } else {
  4686. QS('DeskParent').height = '100%';
  4687. QS('page_content').top = '50px';
  4688. QS('page_content').bottom = '32px';
  4689. if (currentDevicePanel == 1) {
  4690. QS('p10desktop').top = '55px';
  4691. QS('p10desktop').overflow = 'hidden';
  4692. QS('deskarea3').top = '32px';
  4693. QS('deskarea3').left = null;
  4694. QS('deskarea3').width = '100%';
  4695. QS('deskarea3').height = 'calc(100% - 64px)';
  4696. QS('deskarea3')['padding-right'] = '';
  4697. QS('DeskParent')['margin-top'] = null;
  4698. QS('DeskParent')['margin-left'] = null;
  4699. }
  4700. if (currentDevicePanel == 5) {
  4701. //xterm.setOption('fontSize', 15)
  4702. QS('p10terminal').top = '55px';
  4703. QS('p10terminal').overflow = 'hidden';
  4704. Q('p10terminal').scrollTop = 0;
  4705. Q('p10terminal').scrollLeft = 0;
  4706. QS('termarea3').top = '32px';
  4707. QS('termarea3').bottom = '32px';
  4708. //QS('termarea3').right = '0px';
  4709. QS('termarea3')['padding-right'] = null;
  4710. QS('termarea3')['height'] = 'calc(100% - 60px)';
  4711. }
  4712. QS('body')['background-color'] = nightMode ? '#000' : '#FFF';
  4713. QS('p10')['background-color'] = null;
  4714. }
  4715. if (currentDevicePanel == 1) { deskAdjust(); }
  4716. }
  4717. function deskAdjust() {
  4718. if (currentDevicePanel != 1) return; // If not on desktop tab, ignore this.
  4719. if (fullscreen) {
  4720. QS('Desk')['margin-top'] = null;
  4721. QS('Desk')['margin-bottom'] = null;
  4722. QS('Desk').width = '100%';
  4723. QS('Desk').height = '100%';
  4724. var parentH = Q('p10desktop').clientHeight, parentW = Q('p10desktop').clientWidth;
  4725. var deskH = Q('deskarea3').clientHeight, deskW = Q('deskarea3').clientWidth - 55;
  4726. if (parentH > deskH) { QS('deskarea3').top = ((parentH - deskH) / 2) + 'px'; } else { QS('deskarea3').top = null; }
  4727. if (parentW > deskW) { QS('deskarea3').left = ((parentW - deskW) / 2) + 'px'; } else { QS('deskarea3').left = null; }
  4728. } else {
  4729. var parentH = Q('DeskParent').clientHeight, parentW = Q('DeskParent').clientWidth;
  4730. var deskH = Q('Desk').height, deskW = Q('Desk').width;
  4731. var webPageFullScreen = false;
  4732. // Fixed aspect ratio
  4733. if ((parentH / parentW) > (deskH / deskW)) {
  4734. var hNew = ((deskH * parentW) / deskW) + 'px';
  4735. QS('Desk').height = hNew;
  4736. QS('Desk').width = '100%';
  4737. } else {
  4738. var wNew = ((deskW * parentH) / deskH) + 'px';
  4739. QS('Desk').width = wNew;
  4740. QS('Desk').height = '100%';
  4741. }
  4742. QS('DeskParent').overflow = 'hidden';
  4743. // Adjust top/bottom margins
  4744. var x = (Q('DeskParent').clientHeight - Q('Desk').clientHeight) / 2;
  4745. QS('Desk')['margin-top'] = x + 'px';
  4746. QS('Desk')['margin-bottom'] = x + 'px';
  4747. }
  4748. }
  4749. function sendSpecialKeys() {
  4750. if (xxdialogMode || desktop == null || desktop.State != 3) return;
  4751. setDialogMode(3, "Special Keys", 3, deskSendKeys);
  4752. }
  4753. // Save the desktop image to file
  4754. function deskSaveImage() {
  4755. setSessionActivity();
  4756. if (xxdialogMode || desktop == null || desktop.State != 3) return;
  4757. var d = new Date(), n = 'Desktop-' + currentNode.name + '-' + d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2) + '-' + ('0' + d.getHours()).slice(-2) + '-' + ('0' + d.getMinutes()).slice(-2);
  4758. Q('Desk')['toBlob'](function (blob) { saveAs(blob, n + '.png'); });
  4759. }
  4760. function deskSelectScreens() {
  4761. if (xxdialogMode || desktop == null || desktop.State != 3) return;
  4762. var x = '', info = desktop.m.displays;
  4763. for (var i in info) { x += '<option value=' + i + ' ' + ((desktop.m.selectedDisplay == i) ? ' selected' : '') + '>' + info[i] + '</option>'; }
  4764. x = addHtmlValue4("Screen", '<select style=width:100% id=deskdisplays>' + x + '</select>');
  4765. setDialogMode(2, "Screen Selection", 3, deskSelectScreensEx, x);
  4766. }
  4767. function deskSelectScreensEx() {
  4768. if (desktop == null || desktop.State != 3) return;
  4769. desktop.m.SetDisplay(parseInt(Q('deskdisplays').value));
  4770. }
  4771. function deskDisplayInfo(sender, info, selDisplay, selItem) {
  4772. var displayCount = 0;
  4773. for (var x in info) { displayCount++; }
  4774. QV('DeskScreens', displayCount > 1);
  4775. }
  4776. function dmousedown(e) { setSessionActivity(); if ((!xxdialogMode && desktop != null)) { if (fullscreen) { e.addx = Q('p10desktop').scrollLeft * (1 / fullscreenzoom); e.addy = Q('p10desktop').scrollTop * (1 / fullscreenzoom); } desktop.m.mousedown(e); } }
  4777. function dmouseup(e) { setSessionActivity(); if ((!xxdialogMode && desktop != null)) { if (fullscreen) { e.addx = Q('p10desktop').scrollLeft * (1 / fullscreenzoom); e.addy = Q('p10desktop').scrollTop * (1 / fullscreenzoom); } desktop.m.mouseup(e); } }
  4778. function dmousemove(e) { setSessionActivity(); if ((!xxdialogMode && desktop != null)) { if (fullscreen) { e.addx = Q('p10desktop').scrollLeft * (1 / fullscreenzoom); e.addy = Q('p10desktop').scrollTop * (1 / fullscreenzoom); } desktop.m.mousemove(e); } }
  4779. function dmousewheel(e) { setSessionActivity(); if ((!xxdialogMode && desktop != null) && desktop.m.mousewheel) { if (fullscreen) { e.addx = Q('p10desktop').scrollLeft * (1 / fullscreenzoom); e.addy = Q('p10desktop').scrollTop * (1 / fullscreenzoom); } desktop.m.mousewheel(e); haltEvent(e); return true; } return false; }
  4780. function drotate(x) { if (!xxdialogMode && desktop != null) { desktop.m.setRotation(desktop.m.rotation + x); deskAdjust(); } }
  4781. //
  4782. // TERMINAL
  4783. //
  4784. var terminalNode;
  4785. function setupTerminal() {
  4786. // Setup the terminal
  4787. if ((terminalNode != currentNode) && (terminal != null)) { terminal.Stop(); terminal = null; }
  4788. terminalNode = currentNode;
  4789. updateTerminalButtons();
  4790. }
  4791. // Show and enable the right buttons
  4792. function updateTerminalButtons() {
  4793. var mtype = (currentNode.agent == 1) ? 1 : 2;
  4794. var termState = ((terminal != null) && (terminal.state != 0));
  4795. QE('termFullScreen', (termState != 0));
  4796. // If we are looking at a local non-windows device, enable terminal and files capability.
  4797. if ((terminalNode.mtype == 3) && (terminalNode.agent != null) && (terminalNode.agent.id > 4) && (features2 & 0x00000200)) { terminalNode.agent.caps = 6; }
  4798. // Show the right buttons
  4799. QV('disconnectbutton2span', (termState == true));
  4800. QV('connectbutton2span', (termState == false) && (terminalNode.agent != null) && (terminalNode.agent.caps & 2) && (terminalNode.mtype != 3));
  4801. QV('connectbutton2sspan', (termState == false) && (terminalNode.agent != null) && (terminalNode.agent.caps & 2) && (terminalNode.agent.id != 3));
  4802. // Enable buttons
  4803. var online = ((terminalNode.conn & 1) != 0) || (terminalNode.mtype == 3); // If Agent (1) connected, enable Terminal
  4804. QE('connectbutton2', online);
  4805. QE('connectbutton2s', online);
  4806. // Enable action button if mesh type is not "local devices"
  4807. QV('termActionsBtn', terminalNode.mtype != 3);
  4808. QE('ctrlcbutton', termState);
  4809. QE('ctrlxbutton', termState);
  4810. QE('escbutton', termState);
  4811. if (((termState == true) && (terminal.contype != 3)) || (terminalNode.agent == null) || (terminalNode.agent.id == 3) || (terminalNode.agent.id == 4)) {
  4812. QH('terminalCustomUpperRight', '');
  4813. } else {
  4814. QH('terminalCustomUpperRight', '<a style=cursor:pointer onclick=cmsshportaction(1,event)>' + format("SSH Port {0}", (terminalNode.sshport ? terminalNode.sshport : 22)) + '</a>');
  4815. }
  4816. }
  4817. function cmsshportaction(action) {
  4818. if (xxdialogMode) return;
  4819. var x = "SSH remote connection port:" + '<br /><br /><input type=text placeholder="22" inputmode="numeric" pattern="[0-9]*" onkeypress="return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)" maxlength=5 id=d10sshport type=text>';
  4820. setDialogMode(2, "SSH Connection", 3, function () {
  4821. // Save the new SSH port to the server
  4822. var sshport = ((Q('d10sshport').value.length > 0) ? parseInt(Q('d10sshport').value) : 22);
  4823. meshserver.send({ action: 'changedevice', nodeid: currentNode._id, sshport: sshport });
  4824. }, x, currentNode);
  4825. Q('d10sshport').focus();
  4826. if (currentNode.sshport != null) { Q('d10sshport').value = currentNode.sshport; }
  4827. }
  4828. // Called when the terminal state changes
  4829. function onTerminalStateChange(xterminal, state) {
  4830. var xstate = state;
  4831. if ((xstate == 3) && (xterminal.contype == 2)) { xstate++; }
  4832. var str = StatusStrs[xstate];
  4833. if (terminal.webRtcActive == true) { str += ", WebRTC"; }
  4834. QH('termstatus', str);
  4835. switch (state) {
  4836. case 0:
  4837. // Disconnected, clear the terminal
  4838. xterm.dispose();
  4839. xterm = null;
  4840. if (terminal != null) { terminal.Stop(); terminal = null; }
  4841. break;
  4842. case 3:
  4843. xterm.focus();
  4844. break;
  4845. default:
  4846. //console.log('Unhandled onTerminalStateChange state', state);
  4847. break;
  4848. }
  4849. updateTerminalButtons();
  4850. }
  4851. // Handles a tunnel to a remote shell
  4852. function CreateRemoteTunnel(onTunnelUpdate, options) {
  4853. var obj = { protocol: 1 };
  4854. if ((options != null) && (typeof options.protocol == 'number')) { obj.protocol = options.protocol; }
  4855. obj.onTunnelUpdate = onTunnelUpdate;
  4856. obj.xxStateChange = function (state) { }
  4857. obj.ProcessBinaryData = function (data) { obj.onTunnelUpdate(data); }
  4858. obj.ProcessData = function (data) { obj.onTunnelUpdate(data); }
  4859. obj.terminalEmulation = 1;
  4860. obj.fxEmulation = 0;
  4861. obj.lineFeed = '\r\n';
  4862. return obj;
  4863. }
  4864. function tunnelUpdate(data) {
  4865. if (xterm != null) {
  4866. if (xterm.writeUtf8) {
  4867. if (typeof data == 'string') { xterm.writeUtf8(data); } else { xterm.writeUtf8(new Uint8Array(data)); }
  4868. } else {
  4869. if (typeof data == 'string') { xterm.write(data); } else { xterm.write(new Uint8Array(data)); }
  4870. }
  4871. }
  4872. }
  4873. //function tunnelUpdate(data) { if (typeof data == 'string') { xterm.writeUtf8(data); } else { xterm.writeUtf8(new Uint8Array(data)); } }
  4874. function sshTunnelAuthDialog(j, func) {
  4875. var x = '';
  4876. if (j.askkeypass) {
  4877. x += addHtmlValue("Authentication", '<select id=dp2authmethod style=width:150px onchange=sshAuthUpdate(event)><option value=3 selected>' + "Stored Key" + '</option><option value=1>' + "Username & Password" + '</option><option value=2>' + "Username and Key" + '</option></select>');
  4878. } else {
  4879. x += addHtmlValue("Authentication", '<select id=dp2authmethod style=width:150px onchange=sshAuthUpdate(event)><option value=1 selected>' + "Username & Password" + '</option><option value=2>' + "Username and Key" + '</option></select>');
  4880. }
  4881. x += '<div id=d2userauth style=display:none>';
  4882. x += addHtmlValue("Username", '<input id=dp2user style=width:150px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
  4883. x += '</div>';
  4884. x += '<div id=d2passauth style=display:none>';
  4885. x += addHtmlValue("Password", '<input type=password id=dp2pass style=width:150px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
  4886. if ((features2 & 0x00400000) == 0) { x += '<label><input id=dp2keep type=checkbox>' + "Remember credentials" + '</label>'; }
  4887. x += '</div><div id=d2keyauth style=display:none>';
  4888. x += addHtmlValue("Key File", '<input type=file id=dp2key style=width:150px maxlength=64 autocomplete=off onchange=sshAuthUpdate(event) />' + '<div id=d2badkey style=font-size:x-small>' + "Key file must be in OpenSSH format." + '</div>');
  4889. x += addHtmlValue("Key Password", '<input type=password id=dp2keypass style=width:150px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
  4890. if ((features2 & 0x00400000) == 0) {
  4891. x += '<label><input id=dp2keep1 type=checkbox onchange=sshAuthUpdate(event)>' + "Remember user & key" + '</label><br/>';
  4892. x += '<label><input id=dp2keep2 type=checkbox>' + "Remember password" + '</label>';
  4893. }
  4894. x += '</div>';
  4895. if (j.askkeypass) {
  4896. x += '<div id=d2keyauth2 style=display:none>';
  4897. x += addHtmlValue("Password", '<input type=password id=dp2keypass2 style=width:150px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
  4898. x += '</div>';
  4899. }
  4900. setDialogMode(2, "Authentication", 11, func, x, 'ssh');
  4901. Q('dp2user').focus();
  4902. sshAuthUpdate();
  4903. setTimeout(sshAuthUpdate, 50);
  4904. }
  4905. function sshTunnelUpdate(data) {
  4906. if (typeof data == 'string') {
  4907. if (data[0] == '{') {
  4908. var j = JSON.parse(data);
  4909. switch (j.action) {
  4910. case 'sshauth': {
  4911. sshTunnelAuthDialog(j, sshConnectEx);
  4912. /*
  4913. var x = '';
  4914. x += addHtmlValue("Authentication", '<select id=dp2authmethod style=width:150px onchange=sshAuthUpdate(event)><option value=1 selected>' + "Username & Password" + '</option><option value=2>' + "Username and Key" + '</option></select>')
  4915. x += addHtmlValue("Username", '<input id=dp2user style=width:150px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
  4916. x += '<div id=d2passauth>';
  4917. x += addHtmlValue("Password", '<input type=password id=dp2pass style=width:150px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
  4918. x += '</div><div id=d2keyauth style=display:none>';
  4919. x += addHtmlValue("Key File", '<input type=file id=dp2key style=width:150px maxlength=64 autocomplete=off onchange=sshAuthUpdate(event) />');
  4920. x += addHtmlValue("Key Password", '<input type=password id=dp2keypass style=width:150px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
  4921. x += '</div>';
  4922. x += '<label><input id=dp2keep type=checkbox>' + "Remember credentials" + '</label>';
  4923. x += '<div id=d2keyauth2 style=font-size:x-small><br />' + "Key file must be in OpenSSH format." + '</div>';
  4924. setDialogMode(2, "Authentication", 11, sshConnectEx, x, 'ssh');
  4925. setTimeout(sshAuthUpdate, 50);
  4926. */
  4927. break;
  4928. }
  4929. case 'sshautoauth': {
  4930. terminal.socket.send(JSON.stringify({ action: 'sshautoauth', cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
  4931. break;
  4932. }
  4933. case 'autherror': { p12setConsoleMsg("Authentication Error", 5000); break; }
  4934. case 'sessionerror': { p12setConsoleMsg("Session expired", 5000); break; }
  4935. case 'sessiontimeout': { p12setConsoleMsg("Session timeout", 5000); break; }
  4936. }
  4937. } else if (data[0] == '~') {
  4938. if (xterm.writeUtf8) { xterm.writeUtf8(data.substring(1)); } else { xterm.write(data.substring(1)); }
  4939. }
  4940. }
  4941. }
  4942. /*
  4943. function sshAuthUpdate(e) {
  4944. QV('d2passauth', Q('dp2authmethod').value == 1);
  4945. QV('d2keyauth', Q('dp2authmethod').value == 2);
  4946. QV('d2keyauth2', Q('dp2authmethod').value == 2);
  4947. if (Q('dp2authmethod').value == 1) {
  4948. QE('idx_dlgOkButton', (Q('dp2user').value.length > 0) && (Q('dp2pass').value.length > 0));
  4949. } else {
  4950. QE('idx_dlgOkButton', false);
  4951. var ok = (Q('dp2user').value.length > 0) && (Q('dp2key').files != null) && (Q('dp2key').files.length == 1) && (Q('dp2key').files[0].size < 8000);
  4952. if (ok == true) {
  4953. var reader = new FileReader();
  4954. reader.onload = function (e) {
  4955. var validkey =
  4956. ((e.target.result.indexOf('-----BEGIN OPENSSH PRIVATE KEY-----') >= 0) && (e.target.result.indexOf('-----END OPENSSH PRIVATE KEY-----') >= 0)) ||
  4957. ((e.target.result.indexOf('-----BEGIN RSA PRIVATE KEY-----') >= 0) && (e.target.result.indexOf('-----END RSA PRIVATE KEY-----') >= 0));
  4958. QE('idx_dlgOkButton', validkey);
  4959. }
  4960. reader.readAsText(Q('dp2key').files[0]);
  4961. }
  4962. }
  4963. }
  4964. function sshConnectEx(b) {
  4965. if (b == 0) {
  4966. if (terminal != null) { connectTerminal(); } // Disconnect
  4967. } else {
  4968. if (Q('dp2authmethod').value == 1) {
  4969. terminal.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: Q('dp2keep').checked, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
  4970. } else {
  4971. var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value, keep = Q('dp2keep').checked;
  4972. reader.onload = function (e) { terminal.socket.send(JSON.stringify({ action: 'sshauth', username: username, keypass: keypass, key: e.target.result, keep: keep, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight })); }
  4973. reader.readAsText(Q('dp2key').files[0]);
  4974. }
  4975. }
  4976. }
  4977. */
  4978. function sshAuthUpdate(e) {
  4979. QV('d2userauth', Q('dp2authmethod').value != 3);
  4980. QV('d2passauth', Q('dp2authmethod').value == 1);
  4981. QV('d2keyauth', Q('dp2authmethod').value == 2);
  4982. QV('d2keyauth2', Q('dp2authmethod').value == 3);
  4983. if (Q('dp2authmethod').value == 1) {
  4984. QE('idx_dlgOkButton', (Q('dp2user').value.length > 0) && (Q('dp2pass').value.length > 0));
  4985. } else if (Q('dp2authmethod').value == 3) {
  4986. QE('idx_dlgOkButton', Q('dp2keypass2').value.length > 0);
  4987. } else {
  4988. QE('idx_dlgOkButton', false);
  4989. if ((features2 & 0x00400000) == 0) { QE('dp2keep2', Q('dp2keep1').checked); }
  4990. var ok = (Q('dp2user').value.length > 0) && (Q('dp2key').files != null) && (Q('dp2key').files.length == 1) && (Q('dp2key').files[0].size < 8000);
  4991. if (ok == true) {
  4992. var reader = new FileReader();
  4993. reader.onload = function (e) {
  4994. var validkey =
  4995. ((e.target.result.indexOf('-----BEGIN OPENSSH PRIVATE KEY-----') >= 0) && (e.target.result.indexOf('-----END OPENSSH PRIVATE KEY-----') >= 0)) ||
  4996. ((e.target.result.indexOf('-----BEGIN RSA PRIVATE KEY-----') >= 0) && (e.target.result.indexOf('-----END RSA PRIVATE KEY-----') >= 0));
  4997. QE('idx_dlgOkButton', validkey);
  4998. QS('d2badkey')['color'] = validkey ? '#000' : '#F00';
  4999. }
  5000. reader.readAsText(Q('dp2key').files[0]);
  5001. }
  5002. }
  5003. // When the enter key is pressed, move to the next field
  5004. if (e && (e.keyCode == 13) && (e.target) && (Q('dp2authmethod').value == 1)) {
  5005. if (e.target.id == 'dp2user') { Q('dp2pass').focus(); }
  5006. if (e.target.id == 'dp2pass') { dialogclose(1); }
  5007. }
  5008. }
  5009. function sshConnectEx(b) {
  5010. if (b == 0) {
  5011. if (terminal != null) { connectTerminal(); } // Disconnect
  5012. } else {
  5013. var keep = 0;
  5014. if (Q('dp2authmethod').value == 1) {
  5015. if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep').checked ? 1 : 0); }
  5016. terminal.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: keep, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
  5017. } else if (Q('dp2authmethod').value == 3) {
  5018. terminal.socket.send(JSON.stringify({ action: 'sshkeyauth', keypass: Q('dp2keypass2').value, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
  5019. } else {
  5020. if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep1').checked ? 1 : 0); if (keep == 1) { keep += (Q('dp2keep2').checked ? 1 : 0); } } // Keep: 1 = user & key, 2 = User, key and password
  5021. var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value;
  5022. reader.onload = function (e) { terminal.socket.send(JSON.stringify({ action: 'sshauth', username: username, keypass: keypass, key: e.target.result, keep: keep, cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight })); }
  5023. reader.readAsText(Q('dp2key').files[0]);
  5024. }
  5025. }
  5026. }
  5027. // Send the new terminal size to the agent
  5028. function xTermSendResize() {
  5029. xtermResizeTimer = null;
  5030. if ((xterm != null) && (terminal != null) && (terminal.sendCtrlMsg != null)) {
  5031. if (terminal.urlname == 'sshterminalrelay.ashx') {
  5032. terminal.socket.send(JSON.stringify({ action: 'resize', cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
  5033. } else {
  5034. terminal.sendCtrlMsg(JSON.stringify({ ctrlChannel: '102938', type: 'termsize', cols: xterm.cols, rows: xterm.rows }));
  5035. }
  5036. }
  5037. }
  5038. function connectTerminal(e, contype, options) {
  5039. p12clearConsoleMsg();
  5040. if (!terminal) {
  5041. // Terminal setup
  5042. var termoptions = { protocol: ((options != null) && (typeof options.protocol == 'number')) ? options.protocol : 1 };
  5043. if (options && options.requireLogin) { termoptions.requireLogin = true; }
  5044. /*
  5045. if ([1, 2, 3, 4, 21, 22].indexOf(currentNode.agent.id) == -1) {
  5046. if (Q('termSizeList').value == 1) { termoptions.cols = 80; termoptions.rows = 25; termoptions.xterm = true; }
  5047. else if (Q('termSizeList').value == 2) { termoptions.cols = 100; termoptions.rows = 30; termoptions.xterm = true; }
  5048. else if (Q('termSizeList').value == 3) {
  5049. // TODO: Try to improve terminal auto-size.
  5050. termoptions.cols = Math.floor((Q('column_l').clientWidth - 60) / 10);
  5051. termoptions.rows = Math.floor((Q('column_l').clientHeight - 120) / 20);
  5052. termoptions.xterm = true;
  5053. }
  5054. }
  5055. // If shift is pressed
  5056. if ((e && (e.shiftKey == true))) {
  5057. if (currentNode.agent.id > 4) {
  5058. if (termoptions.protocol == 1) { termoptions.protocol = 7; } // Switch to user shell
  5059. } else {
  5060. if (termoptions.protocol == 1) { termoptions.protocol = 6; } // Switch to Powershell
  5061. }
  5062. }
  5063. */
  5064. // If the server requires a shell type
  5065. if ((serverinfo.linuxshell) != null && (currentNode.agent.id > 4)) {
  5066. if (serverinfo.linuxshell == 'root') { termoptions.protocol = 1; delete termoptions.requireLogin; }
  5067. if (serverinfo.linuxshell == 'user') { termoptions.protocol = 8; delete termoptions.requireLogin; }
  5068. if (serverinfo.linuxshell == 'login') { termoptions.protocol = 1; termoptions.requireLogin = true; }
  5069. }
  5070. // Setup a mesh agent xterm terminal
  5071. QV('termarea3xdiv', true);
  5072. // Setup the terminal with auto-fit
  5073. if (xterm != null) { xterm.dispose(); }
  5074. xterm = new Terminal();
  5075. xtermfit = new FitAddon.FitAddon();
  5076. if (xtermfit) { xterm.loadAddon(xtermfit); }
  5077. //xterm.setOption('scrollback', 0);
  5078. //xterm.setOption('fontSize', 15);
  5079. xterm.open(Q('termarea3xdiv'));
  5080. xterm.onData(function (data) { if (terminal.urlname == 'sshterminalrelay.ashx') { terminal.socket.send('~' + data); } else { terminal.sendText(data); } })
  5081. if (xtermfit) { xtermfit.fit(); }
  5082. xterm.onResize(function (size) {
  5083. // Despam resize
  5084. if (xtermResizeTimer) clearTimeout(xtermResizeTimer);
  5085. xtermResizeTimer = setTimeout(xTermSendResize, 200);
  5086. });
  5087. // Remove terminal textarea and scrollbar.
  5088. document.getElementsByClassName('xterm-helper-textarea')[0].onfocus = () => { xterm.blur(); if (!fullscreen) toggleKeyboard(); };
  5089. document.getElementsByClassName('xterm-viewport')[0].style.overflow = 'hidden';
  5090. // Setup a terminal tunnel to the agent
  5091. terminal = CreateAgentRedirect(meshserver, CreateRemoteTunnel((contype == 3) ? sshTunnelUpdate : tunnelUpdate, termoptions), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
  5092. if (contype == 3) { terminal.urlname = 'sshterminalrelay.ashx'; } // If this is a SSH session, change the URL to the SSH application relay.
  5093. terminal.debugmode = debugmode;
  5094. terminal.m.debugmode = debugmode;
  5095. terminal.options = termoptions;
  5096. terminal.options = { cols: xterm.cols, rows: xterm.rows };
  5097. if (termoptions.requireLogin) { terminal.options.requireLogin = true; }
  5098. terminal.Start(terminalNode._id);
  5099. terminal.onStateChanged = onTerminalStateChange;
  5100. terminal.contype = contype;
  5101. terminal.attemptWebRTC = false; // Never do WebRTC on terminal, because of a race condition we can't do it.
  5102. terminal.onConsoleMessageChange = function () { p12setConsoleMsg(terminal.consoleMessage ? formatAgentConsoleMessage(terminal.consoleMessage, terminal.consoleMessageId, terminal.consoleMessageArgs) : null, terminal.consoleMessageTimeout); }
  5103. } else {
  5104. terminal.Stop();
  5105. terminal = null;
  5106. if (fullscreen) { deskToggleFull(); }
  5107. }
  5108. Q('connectbutton2').blur(); // Deselect the connect button so the button does not get key presses.
  5109. }
  5110. function termSendKey(key, id) {
  5111. if (!terminal || xxdialogMode) return;
  5112. if (xterm != null) {
  5113. if (terminal.urlname == 'sshterminalrelay.ashx') {
  5114. // SSH
  5115. terminal.socket.send('~' + String.fromCharCode(key));
  5116. } else if (terminal.sendText) {
  5117. // MeshAgent
  5118. terminal.sendText(String.fromCharCode(key));
  5119. } else {
  5120. // CIRA
  5121. terminal.send(String.fromCharCode(key));
  5122. }
  5123. xterm.focus();
  5124. } else if (terminal != null) {
  5125. terminal.m.TermSendKey(key);
  5126. Q(id).blur(); // Deselect the connect button so the button does not get key presses.
  5127. }
  5128. }
  5129. //
  5130. // Terminal Shortcut Keys
  5131. //
  5132. function updateTermShortcutKeys() {
  5133. var x = '';
  5134. for (var i = 64; i <= 95; i++) { x += '<div class="menuButton" style="width:70px" onclick="termMenuButton(' + i + ')">' + "Ctrl + " + String.fromCharCode(i) + '</div>'; }
  5135. QH('termButtonMenu', x);
  5136. }
  5137. function termMenuButton(c) {
  5138. toggleMenu(true);
  5139. if (terminal.urlname == 'sshterminalrelay.ashx') {
  5140. // SSH
  5141. terminal.socket.send('~' + String.fromCharCode(c - 64));
  5142. } else {
  5143. // Agent
  5144. terminal.sendText(String.fromCharCode(c - 64));
  5145. }
  5146. }
  5147. //
  5148. // FILES
  5149. //
  5150. var filesNode;
  5151. function setupFiles() {
  5152. // Setup the files tab
  5153. var samenode = (filesNode == currentNode);
  5154. filesNode = currentNode;
  5155. var online = ((filesNode.conn & 1) != 0) || (filesNode.mtype == 3); // If Agent (1) connected, enable Terminal
  5156. QE('p13Connect', online);
  5157. QE('p13Connects', online);
  5158. QV('p13Connect', (files == null) && (filesNode.mtype == 2));
  5159. QV('p13Connects', (files == null) && (filesNode.agent != null) && (filesNode.agent.id != 3) && (filesNode.agent.id != 4));
  5160. QV('p13Disconnect', files != null);
  5161. if (((samenode == false) || (online == false)) && files) { files.Stop(); files = null; }
  5162. p13setActions();
  5163. }
  5164. function onFilesStateChange(xfiles, state) {
  5165. setSessionActivity();
  5166. QV('p13Connect', (state == 0) && (filesNode.mtype == 2));
  5167. QV('p13Connects', (state == 0) && (filesNode.agent != null) && (filesNode.agent.id != 3) && (filesNode.agent.id != 4));
  5168. QV('p13Disconnect', state != 0);
  5169. var str = StatusStrs[state];
  5170. if (state == 3) {
  5171. if (files.contype == 2) { str += ", SFTP"; }
  5172. if (files.webRtcActive == true) { str += ", WebRTC"; }
  5173. }
  5174. Q('p13Status').textContent = str;
  5175. switch (state) {
  5176. case 0:
  5177. // Disconnected, clear the files
  5178. QH('p13files', '');
  5179. p13filetree = null;
  5180. p13filetreelocation = [];
  5181. QH('p13currentpath', '');
  5182. QE('p13FolderUp', false);
  5183. p13setActions();
  5184. if (files != null) { files.Stop(); files = null; }
  5185. if (uploadFile != null) { p13uploadFileTransferDone(); uploadFile = null; }
  5186. break;
  5187. case 3:
  5188. p13filetreelocation = [];
  5189. p13targetpath = '';
  5190. if (files) {
  5191. var filepaths = [];
  5192. try { filepaths = JSON.parse(getstore('_devFilePaths', '[]')); } catch (ex) { }
  5193. for (var i = 0; i < filepaths.length; i++) { if (filepaths[i].n == currentNode._id) { p13targetpath = filepaths[i].p; } }
  5194. p13filetreelocation = p13targetpath.split('/');
  5195. files.sendText({ action: 'ls', reqid: 1, path: p13targetpath });
  5196. //if (files.serverIsRecording == true) { QV('filesRecordIcon', true); }
  5197. }
  5198. break;
  5199. default:
  5200. //console.log('Unknown onFilesStateChange state', state);
  5201. break;
  5202. }
  5203. }
  5204. function CreateRemoteFiles(onFileUpdate) {
  5205. var obj = { protocol: 5 };
  5206. obj.onFileUpdate = onFileUpdate;
  5207. obj.xxStateChange = function (state) { }
  5208. obj.ProcessData = function (data) { obj.onFileUpdate(data); }
  5209. return obj;
  5210. }
  5211. // Debug Only
  5212. var autoConnectFilesTimer = null;
  5213. function autoConnectFiles(e) { if (autoConnectFilesTimer == null) { autoConnectFilesTimer = setInterval(connectFiles, 100); } else { clearInterval(autoConnectFilesTimer); autoConnectFilesTimer = null; } }
  5214. function connectFiles(e, contype) {
  5215. p13clearConsoleMsg();
  5216. if (!files) {
  5217. // Setup a mesh agent files
  5218. files = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotFiles), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
  5219. if (contype == 2) { files.urlname = 'sshfilesrelay.ashx'; } // If this is a SSH session, change the URL to the SSH application relay.
  5220. files.contype = contype;
  5221. files.attemptWebRTC = attemptWebRTC;
  5222. files.webrtcconfig = webrtcconfiguration;
  5223. files.onStateChanged = onFilesStateChange;
  5224. files.onConsoleMessageChange = function () {
  5225. if (files.consoleMessage) {
  5226. Q('p13FilesConsoleMsg').innerHTML += formatAgentConsoleMessage(files.consoleMessage, files.consoleMessageId, files.consoleMessageArgs);
  5227. QV('p13FilesConsoleMsg', true);
  5228. if (p13FilesConsoleMsgTimer != null) { clearTimeout(p13FilesConsoleMsgTimer); }
  5229. if (files.consoleMessageTimeout) { p13FilesConsoleMsgTimer = setTimeout(p13clearConsoleMsg, files.consoleMessageTimeout * 1000); }
  5230. } else {
  5231. p13clearConsoleMsg();
  5232. }
  5233. }
  5234. files.Start(filesNode._id);
  5235. } else {
  5236. //QH('Term', '');
  5237. files.Stop();
  5238. files = null;
  5239. }
  5240. p13clipboard = p13clipboardFolder = null;
  5241. p13clipboardCut = 0;
  5242. p13updateClipview();
  5243. }
  5244. var p13filetree = null;
  5245. var p13targetpath = null;
  5246. var p13filetreelocation = [];
  5247. function p13gotFiles(data) {
  5248. if ((data.length > 0) && (data.charCodeAt(0) != 123)) { p13gotDownloadBinaryData(data); return; } // This is ok because 4 first bytes is a control value.
  5249. //console.log('p13gotFiles', data);
  5250. try { data = JSON.parse(decode_utf8(data)); } catch (ex) { data = JSON.parse(data); }
  5251. if (data.action == 'download') { p13gotDownloadCommand(data); return; }
  5252. // Process any SSH actions
  5253. switch (data.action) {
  5254. case 'sshauth': { sshTunnelAuthDialog(data, p13sshConnectEx); break; }
  5255. case 'autherror': { p13setConsoleMsg("Authentication Error", 5000); return; }
  5256. case 'connectionerror': { p13setConsoleMsg("Connection Error", 5000); return; }
  5257. case 'sessionerror': { p13setConsoleMsg("Session expired", 5000); return; }
  5258. case 'sessiontimeout': { p13setConsoleMsg("Session timeout", 5000); return; }
  5259. }
  5260. // Process file upload commands
  5261. if ((data.action != null) && (data.action.startsWith('upload'))) { p13gotUploadData(data); return; }
  5262. if (data.path != null) {
  5263. if (data.dir == null) {
  5264. if (p13targetpath != '') { p13folderup(); }
  5265. } else {
  5266. data.path = data.path.replace(/\//g, '\\');
  5267. if ((p13filetree != null) && (data.path == p13filetree.path)) {
  5268. // This is an update to the same folder
  5269. var checkedNames = p13getCheckedNames();
  5270. p13filetree = data;
  5271. p13updateFiles(checkedNames);
  5272. } else {
  5273. // Make both paths use the same seperator not start with /
  5274. var x1 = data.path.replace(/\//g, '\\'), x2 = p13targetpath.replace(/\//g, '\\');
  5275. while ((x1.length > 0) && (x1[0] == '\\')) { x1 = x1.substring(1); }
  5276. while ((x2.length > 0) && (x2[0] == '\\')) { x2 = x2.substring(1); }
  5277. if ((x1 == x2) || ((data.path == '\\') && (p13targetpath == ''))) {
  5278. // This is a different folder
  5279. p13filetree = data;
  5280. p13updateFiles();
  5281. }
  5282. }
  5283. }
  5284. }
  5285. }
  5286. function p13sshConnectEx(b) {
  5287. if (b == 0) {
  5288. if (files != null) { connectFiles(); } // Disconnect
  5289. } else {
  5290. var keep = 0;
  5291. if (Q('dp2authmethod').value == 1) {
  5292. if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep').checked ? 1 : 0); }
  5293. files.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: keep }));
  5294. } else if (Q('dp2authmethod').value == 3) {
  5295. files.socket.send(JSON.stringify({ action: 'sshkeyauth', keypass: Q('dp2keypass2').value }));
  5296. } else {
  5297. if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep1').checked ? 1 : 0); if (keep == 1) { keep += (Q('dp2keep2').checked ? 1 : 0); } } // Keep: 1 = user & key, 2 = User, key and password
  5298. var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value;
  5299. reader.onload = function (e) { files.socket.send(JSON.stringify({ action: 'sshauth', username: username, keypass: keypass, key: e.target.result, keep: keep })); }
  5300. reader.readAsText(Q('dp2key').files[0]);
  5301. }
  5302. }
  5303. }
  5304. function p13getCheckedNames() {
  5305. // Save all existing checked boxes
  5306. var checkedNames = [], checkboxes = document.getElementsByName('fd');
  5307. for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { checkedNames.push(p13filetree.dir[checkboxes[i].value].n) }; }
  5308. return checkedNames;
  5309. }
  5310. function p13updateFiles(checkedNames) {
  5311. var html1 = '', html2 = '', displayPath = '<a style=cursor:pointer;color:black onclick=p13folderup(0)>' + "Root" + '</a>', fullPath = 'Root';
  5312. // Work on parsing the file path
  5313. var x = p13filetree.path.split('\\');
  5314. p13filetreelocation = [];
  5315. for (var i in x) { if (x[i] != '') { p13filetreelocation.push(x[i]); } } // Remove empty spaces
  5316. for (var i in p13filetreelocation) { displayPath += ' / <a style=cursor:pointer;color:black onclick=p13folderup(' + (parseInt(i) + 1) + ')>' + EscapeHtml(p13filetreelocation[i]) + '</a>' } // Setup the path we display
  5317. var newlinkpath = p13filetreelocation.join('/');
  5318. // Sort the files
  5319. var filetreexx = p13sort_files(p13filetree.dir);
  5320. // Display all files and folders at this location
  5321. for (var i in filetreexx) {
  5322. // Figure out the name and shortname
  5323. var f = filetreexx[i], name = f.n, shortname;
  5324. // if (name.length > 40) { shortname = EscapeHtml(name.substring(0, 70)) + "..."; } else { shortname = EscapeHtml(name); }
  5325. // Removed redundant filename length check because we handle it in the CSS
  5326. shortname = EscapeHtml(name);
  5327. // Figure out the size
  5328. var fsize = '';
  5329. if (f.s != null) { fsize = getFileSizeStr(f.s); }
  5330. var h = '';
  5331. if (f.t < 3) {
  5332. var right = '';
  5333. h = '<div class=filelist file=999><input file=999 style=float:left name=fd class=fcb type=checkbox onchange=p13setActions() value=\'' + f.nx + '\'>&nbsp;<span style=float:right>' + right + '</span><span title="' + shortname + '"><div class=fileIcon' + f.t + '></div><a style=cursor:pointer onclick=p13folderset("' + encodeURIComponent(f.nx) + '")>' + shortname + '</a></span></div>';
  5334. } else {
  5335. var link = shortname;
  5336. if (f.s > 0) { link = '<a rel=\"noreferrer noopener\" target=\"_blank\" style=cursor:pointer onclick=\"p13downloadfile(\'' + encodeURIComponent(newlinkpath + '/' + name) + '\',\'' + encodeURIComponent(name) + '\',' + f.s + ')\">' + shortname + '</a>'; }
  5337. h = '<div class=filelist file=3><input file=3 style=float:left name=fd class=fcb type=checkbox onchange=p13setActions() value=\'' + f.nx + '\'>&nbsp;<span style=float:right;padding-right:4px>' + fsize + '</span><span title="' + shortname + '"><div class=fileIcon' + f.t + '></div>' + link + '</span></div>';
  5338. }
  5339. if (f.t < 3) { html1 += h; } else { html2 += h; }
  5340. }
  5341. // Display the files and path
  5342. QH('p13files', html1 + html2);
  5343. QH('p13currentpath', displayPath);
  5344. QE('p13FolderUp', p13filetreelocation.length != 0);
  5345. // Re-check all boxes if needed using names
  5346. if (checkedNames != null) { var checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { if (checkedNames.indexOf(p13filetree.dir[checkboxes[i].value].n) >= 0) { checkboxes[i].checked = true; } } }
  5347. // Update the actions buttons
  5348. p13setActions();
  5349. }
  5350. function p13folderset(x) {
  5351. p13targetpath = joinPaths(p13filetree.path, p13filetree.dir[x].n).split('\\').join('/');
  5352. if (files) {
  5353. p13storeCurrentPath(p13targetpath);
  5354. files.sendText({ action: 'ls', reqid: 1, path: p13targetpath });
  5355. }
  5356. }
  5357. function p13folderup(x) {
  5358. if (x == null) { p13filetreelocation.pop(); } else { while (p13filetreelocation.length > x) { p13filetreelocation.pop(); } }
  5359. p13targetpath = p13filetreelocation.join('/');
  5360. if (files) {
  5361. p13storeCurrentPath(p13targetpath);
  5362. files.sendText({ action: 'ls', reqid: 1, path: p13targetpath });
  5363. }
  5364. }
  5365. // Store the current path for a given node as browser state.
  5366. // This is so, when reconnecting to a device, you go back to the same path.
  5367. function p13storeCurrentPath(path) {
  5368. var filepaths = [], j = -1;
  5369. try { filepaths = JSON.parse(getstore('_devFilePaths', '[]')); } catch (ex) { }
  5370. for (var i = 0; i < filepaths.length; i++) { if (filepaths[i].n == currentNode._id) { j = i; } }
  5371. if (j >= 0) { filepaths.splice(j, 1); }
  5372. filepaths.push({ n: currentNode._id, p: path });
  5373. while (filepaths.length > 40) { filepaths.shift(); } // Keep only 40 devices worth of paths.
  5374. putstore('_devFilePaths', JSON.stringify(filepaths));
  5375. }
  5376. var p13sortorder;
  5377. function p13sort_filename(a, b) { if (a.ln > b.ln) return (1 * p13sortorder); if (a.ln < b.ln) return (-1 * p13sortorder); return 0; }
  5378. function p13sort_timestamp(a, b) { if (a.d > b.d) return (1 * p13sortorder); if (a.d < b.d) return (-1 * p13sortorder); return 0; }
  5379. function p13sort_bysize(a, b) { if (a.s == b.s) return p13sort_filename(a, b); return (((a.s - b.s)) * p13sortorder); }
  5380. function p13sort_files(files) {
  5381. var r = [], sortselection = Q('p13sortdropdown').value;
  5382. for (var i in files) { files[i].nx = i; if (files[i].s == null) { files[i].s = 0; } if (files[i].n == null) { files[i].n = i; } files[i].ln = files[i].n.toLowerCase(); r.push(files[i]); }
  5383. p13sortorder = 1;
  5384. if (sortselection > 3) { p13sortorder = -1; sortselection -= 3; }
  5385. if (sortselection == 1) { r.sort(p13sort_filename); }
  5386. else if (sortselection == 2) { r.sort(p13sort_bysize); }
  5387. else if (sortselection == 3) { r.sort(p13sort_timestamp); }
  5388. return r;
  5389. }
  5390. function p13setActions() {
  5391. var advancedFeatures = ((currentNode.agent) && (currentNode.agent.id != 14)); // Reduct file feature on some devices.
  5392. if (p13filetree == null) {
  5393. QE('p13DeleteFileButton', false);
  5394. QE('p13NewFolderButton', false);
  5395. QE('p13UploadButton', false);
  5396. QE('p13RenameFileButton', false);
  5397. QE('p13SelectAllButton', false);
  5398. Q('p13SelectAllButton').value = "All";
  5399. QE('p13RefreshButton', false);
  5400. QE('p13CutButton', false);
  5401. QE('p13CopyButton', false);
  5402. QE('p13PasteButton', false);
  5403. } else {
  5404. var cc = p13getFileSelCount(), tc = p13getFileCount(), sfc = p13getFileSelCount(false); // In order: number of entires selected, number of total entries, number of selected entires that are files (not folders)
  5405. var winAgent = isWindowsNode(currentNode);
  5406. QE('p13DeleteFileButton', advancedFeatures && (cc > 0) && ((p13filetreelocation.length > 0) || (winAgent == false)));
  5407. QE('p13NewFolderButton', advancedFeatures && ((p13filetreelocation.length > 0) || (winAgent == false)));
  5408. QE('p13UploadButton', advancedFeatures && ((p13filetreelocation.length > 0) || (winAgent == false)));
  5409. QE('p13RenameFileButton', advancedFeatures && (cc == 1) && ((p13filetreelocation.length > 0) || (winAgent == false)));
  5410. QE('p13SelectAllButton', tc > 0);
  5411. Q('p13SelectAllButton').value = (cc > 0 ? "None" : "All");
  5412. QE('p13RefreshButton', true);
  5413. QE('p13CutButton', advancedFeatures && (cc > 0) && (cc == sfc) && (currentNode.mtype != 3) && ((p13filetreelocation.length > 0) || (winAgent == false)));
  5414. QE('p13CopyButton', advancedFeatures && (cc > 0) && (cc == sfc) && (currentNode.mtype != 3) && ((p13filetreelocation.length > 0) || (winAgent == false)));
  5415. QE('p13PasteButton', advancedFeatures && (currentNode.mtype != 3) && ((p13filetreelocation.length > 0) || (winAgent == false)) && ((p13clipboard != null) && (p13clipboard.length > 0)));
  5416. }
  5417. var filesState = ((files != null) && (files.state != 0));
  5418. if (((filesState == true) && (files.contype != 2)) || (filesNode.agent == null) || (filesNode.agent.id == 3) || (filesNode.agent.id == 4)) {
  5419. QH('filesCustomUpperRight', '');
  5420. } else {
  5421. QH('filesCustomUpperRight', '<a style=cursor:pointer onclick=cmsshportaction(1,event)>' + format("SSH Port {0}", (filesNode.sshport ? filesNode.sshport : 22)) + '</a>');
  5422. }
  5423. QV('filesActionsBtn', filesNode.mtype != 3);
  5424. }
  5425. function p13getFileSelCount(includeDirs) { var cc = 0; var checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && ((includeDirs != false) || (checkboxes[i].attributes.file.value == '3'))) cc++; } return cc; }
  5426. function p13getFileSelDirCount() { var cc = 0, checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && (checkboxes[i].attributes.file.value == '999')) cc++; } return cc; }
  5427. function p13getFileCount() { var cc = 0; var checkboxes = document.getElementsByName('fd'); return checkboxes.length; }
  5428. function p13selectallfile() { var nv = (p13getFileSelCount() == 0), checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { checkboxes[i].checked = nv; } p13setActions(); }
  5429. function p13createfolder() { setDialogMode(2, "New Folder", 3, p13createfolderEx, '<input type=text id=p13renameinput maxlength=64 onkeyup=p13fileNameCheck(event) style=width:100% />'); focusTextBox('p13renameinput'); p13fileNameCheck(); }
  5430. function p13createfolderEx() { files.sendText({ action: 'mkdir', reqid: 1, path: p13filetreelocation.join('/') + '/' + Q('p13renameinput').value }); p13folderup(999); }
  5431. function p13deletefile() { var cc = p13getFileSelCount(), rec = (p13getFileSelDirCount() > 0) ? '<br /><br /><label><input type=checkbox id=p13recdeleteinput>' + "Recursive delete" + '</label><br>' : '<input type=checkbox id=p13recdeleteinput style=\'display:none\'>'; setDialogMode(2, "Delete", 3, p13deletefileEx, (cc > 1) ? (format("Delete {0} selected items?", cc) + rec) : ("Delete selected item?" + rec)); }
  5432. function p13deletefileEx() { var delfiles = [], checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { delfiles.push(p13filetree.dir[checkboxes[i].value].n); } } files.sendText({ action: 'rm', reqid: 1, path: p13filetreelocation.join('/'), delfiles: delfiles, rec: Q('p13recdeleteinput').checked }); p13folderup(999); }
  5433. function p13renamefile() { var renamefile, checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { renamefile = p13filetree.dir[checkboxes[i].value].n; } } setDialogMode(2, "Rename", 3, p13renamefileEx, '<input type=text id=p13renameinput maxlength=64 onkeyup=p13fileNameCheck(event) style=width:100% value="' + renamefile + '" />', { action: 'rename', path: p13filetreelocation.join('/'), oldname: renamefile }); focusTextBox('p13renameinput'); p13fileNameCheck(); }
  5434. function p13renamefileEx(b, t) { t.newname = Q('p13renameinput').value; files.sendText(t); p13folderup(999); }
  5435. function p13fileNameCheck(e) { var x = isFilenameValid(Q('p13renameinput').value); QE('idx_dlgOkButton', x); if ((x == true) && (e != null) && (e.keyCode == 13)) { dialogclose(1); } }
  5436. function p13uploadFile() { setDialogMode(2, "Upload File", 3, p13uploadFileEx, '<input type=file name=files id=p13uploadinput style=width:100% multiple=multiple onchange="updateUploadDialogOk(\'p13uploadinput\')" />'); updateUploadDialogOk('p13uploadinput'); }
  5437. function p13uploadFileEx() { p13doUploadFiles(Q('p13uploadinput').files); }
  5438. function p13viewfile() {
  5439. var checkboxes = document.getElementsByName('fd');
  5440. for (var i = 0; i < checkboxes.length; i++) {
  5441. if (checkboxes[i].checked) {
  5442. if (p13filetree.dir[checkboxes[i].value].s <= 204800) {
  5443. p13downloadfile(encodeURIComponent(p13filetreelocation.join('/') + '/' + p13filetree.dir[checkboxes[i].value].n), encodeURIComponent(p13filetree.dir[checkboxes[i].value].n), p13filetree.dir[checkboxes[i].value].s, 'viewer');
  5444. } else { messagebox("File Editor", "Only files less than 200k can be edited."); }
  5445. break;
  5446. }
  5447. }
  5448. }
  5449. var p13clipboard = null, p13clipboardFolder = null, p13clipboardCut = 0;
  5450. function p13copyFile(cut) { var checkboxes = document.getElementsByName('fd'); p13clipboard = []; p13clipboardCut = cut, p13clipboardFolder = p13targetpath; for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && (checkboxes[i].attributes.file.value == '3')) { p13clipboard.push(p13filetree.dir[checkboxes[i].value].n); } } p13updateClipview(); }
  5451. function p13pasteFile() {
  5452. var x = '';
  5453. if ((p13clipboard != null) && (p13clipboard.length > 0)) {
  5454. if (p13clipboardCut == 0) {
  5455. if (p13clipboard.length > 1) { x = format("Confirm copy of {0} entries's to this location?", p13clipboard.length); } else { x = format("Confirm copy of 1 entrie to this location?"); }
  5456. } else {
  5457. if (p13clipboard.length > 1) { x = format("Confirm move of {0} entries's to this location?", p13clipboard.length); } else { x = format("Confirm move of 1 entrie to this location?"); }
  5458. }
  5459. }
  5460. setDialogMode(2, "Paste", 3, p13pasteFileEx, x);
  5461. }
  5462. function p13pasteFileEx() { files.sendText({ action: (p13clipboardCut == 0 ? 'copy' : 'move'), reqid: 1, scpath: p13clipboardFolder, dspath: p13targetpath, names: p13clipboard }); p13folderup(999); if (p13clipboardCut == 1) { p13clipboard = null, p13clipboardFolder = null, p13clipboardCut = 0; p13updateClipview(); } }
  5463. function p13updateClipview() {
  5464. var x = '';
  5465. if ((p13clipboard != null) && (p13clipboard.length > 0)) {
  5466. if (p13clipboardCut == 0) {
  5467. if (p13clipboard.length > 1) {
  5468. x = format("Holding {0} entries for copy" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.', p13clipboard.length);
  5469. } else {
  5470. x = format("Holding 1 entrie for copy" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.');
  5471. }
  5472. } else {
  5473. if (p13clipboard.length > 1) {
  5474. x = format("Holding {0} entries for move" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.', p13clipboard.length);
  5475. } else {
  5476. x = format("Holding 1 entrie for move" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.');
  5477. }
  5478. }
  5479. }
  5480. QH('p13bottomstatus', x);
  5481. p13setActions();
  5482. }
  5483. function p13clearClip() { p13clipboard = null; p13clipboardFolder = null; p13clipboardCut = 0; p13updateClipview(); return false; } function updateUploadDialogOk(x) { QE('idx_dlgOkButton', Q(x).value != ''); }
  5484. function getFileSelCount(includeDirs) { var cc = 0; var checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && ((includeDirs != false) || (checkboxes[i].attributes.file.value == "3"))) cc++; } return cc; }
  5485. function getFileCount() { var cc = 0; var checkboxes = document.getElementsByName('fc'); return checkboxes.length; }
  5486. //
  5487. // FILES DOWNLOAD
  5488. //
  5489. var downloadFile; // Global state for file download
  5490. // Called by the html page to start a download, arguments are: path, file name and file size.
  5491. function p13downloadfile(x, y, z) {
  5492. if (xxdialogMode || downloadFile || !files) return;
  5493. downloadFile = { path: decodeURIComponent(x), file: decodeURIComponent(y), size: z, tsize: 0, data: '', state: 0, id: Math.random() }
  5494. //console.log('p13downloadFileCancel', downloadFile);
  5495. files.sendText({ action: 'download', sub: 'start', id: downloadFile.id, path: downloadFile.path });
  5496. setDialogMode(2, "Download File", 10, p13downloadFileCancel, '<div>' + EscapeHtml(downloadFile.file) + '</div><br /><progress id=d2progressBar style=width:100% value=0 max=' + z + ' />');
  5497. }
  5498. // Called by the html page to cancel the download
  5499. function p13downloadFileCancel() { setDialogMode(0); files.sendText({ action: 'download', sub: 'cancel', id: downloadFile.id }); downloadFile = null; }
  5500. // Called by the transport when download control command is received
  5501. function p13gotDownloadCommand(cmd) {
  5502. //console.log('p13gotDownloadCommand', cmd);
  5503. if ((downloadFile == null) || (cmd.id != downloadFile.id)) return;
  5504. if (cmd.sub == 'start') { downloadFile.state = 1; files.sendText({ action: 'download', sub: 'startack', id: downloadFile.id }); }
  5505. else if (cmd.sub == 'cancel') { downloadFile = null; setDialogMode(0); }
  5506. }
  5507. // Called by the transport when binary data is received
  5508. function p13gotDownloadBinaryData(data) {
  5509. if (!downloadFile || downloadFile.state == 0) return;
  5510. if (data.length > 4) {
  5511. downloadFile.tsize += (data.length - 4); // Add to the total bytes received
  5512. downloadFile.data += data.substring(4); // Append the data
  5513. Q('d2progressBar').value = downloadFile.tsize; // Change the progress bar
  5514. }
  5515. if ((ReadInt(data, 0) & 1) != 0) { // Check end flag
  5516. saveAs(data2blob(downloadFile.data), downloadFile.file); downloadFile = null; setDialogMode(0); // Save the file
  5517. } else {
  5518. files.sendText({ action: 'download', sub: 'ack', id: downloadFile.id }); // Send the ACK
  5519. }
  5520. }
  5521. /*
  5522. var downloadFile; // Global state for file download
  5523. // Called by the html page to start a download, arguments are: path, file name and file size.
  5524. function p13downloadfile(x, y, z) {
  5525. if (xxdialogMode) return;
  5526. downloadFile = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotDownloadData), serverPublicNamePort, authCookie, authRelayCookie, domainUrl); // Create our websocket file transport
  5527. downloadFile.ctrlMsgAllowed = false;
  5528. downloadFile.onStateChanged = onFileDownloadStateChange;
  5529. downloadFile.xpath = decodeURIComponent(x);
  5530. downloadFile.xfile = decodeURIComponent(y);
  5531. downloadFile.xsize = z;
  5532. downloadFile.xtsize = 0;
  5533. downloadFile.xstate = 0;
  5534. downloadFile.Start(filesNode._id);
  5535. setDialogMode(2, "Download File", 10, p13downloadFileCancel, '<div>' + downloadFile.xfile + '</div><br /><progress id=d2progressBar style=width:100% value=0 max=' + z + ' />');
  5536. }
  5537. // Called by the html page to cancel the download
  5538. function p13downloadFileCancel(button, tag) {
  5539. //console.log('p13downloadFileCancel');
  5540. downloadFile.Stop();
  5541. delete downloadFile;
  5542. downloadFile = null;
  5543. }
  5544. // Called by the file transport to indicate when the transport connection state has changed
  5545. function onFileDownloadStateChange(xdownloadFile, state) {
  5546. switch (state) {
  5547. case 0: // Transport as disconnected. If this is not part of an abort, we need to save the file
  5548. setDialogMode(0); // Close any dialog boxes if present
  5549. if ((downloadFile != null) && (downloadFile.xstate == 1)) { saveAs(data2blob(downloadFile.xdata), downloadFile.xfile); } // Save the file
  5550. break;
  5551. case 3: // Transport as connected, send a command to indicate we want to start a file download
  5552. downloadFile.send(JSON.stringify({ action: 'download', reqid: 1, path: downloadFile.xpath }));
  5553. break;
  5554. default:
  5555. console.log('Unknown onFileDownloadStateChange state', state);
  5556. break;
  5557. }
  5558. }
  5559. // Called by the transport when data is received
  5560. function p13gotDownloadData(data) {
  5561. if (downloadFile.xstate == 0) { // If state is 0, this is a command confirming if the file will be transfered.
  5562. var cmd = JSON.parse(data);
  5563. if (cmd.action == 'downloadstart') { // Yes, the file is about to start
  5564. downloadFile.xstate = 1; // Switch to state 1, we will start receiving the file data
  5565. downloadFile.xdata = ''; // Start with empty data
  5566. downloadFile.send('a'); // Send the first ACK
  5567. } else if (cmd.action == 'downloaderror') { // Problem opening this file, cancel
  5568. p13downloadFileCancel();
  5569. }
  5570. } else { // We are in the process of receiving the file
  5571. downloadFile.xtsize += (data.length); // Add to the total bytes received
  5572. downloadFile.xdata += data; // Append the data
  5573. Q('d2progressBar').value = downloadFile.xtsize; // Change the progress bar
  5574. downloadFile.send('a'); // Send the ACK
  5575. }
  5576. }
  5577. */
  5578. //
  5579. // FILES UPLOAD
  5580. //
  5581. var uploadFile;
  5582. function p13doUploadFiles(files) {
  5583. if (xxdialogMode) return;
  5584. // Check if we are going to overwrite any files
  5585. var winAgent = isWindowsNode(currentNode);
  5586. var targetFiles = [], overWriteCount = 0;
  5587. for (var i in p13filetree.dir) { if (winAgent) { targetFiles.push(p13filetree.dir[i].n.toLowerCase()); } else { targetFiles.push(p13filetree.dir[i].n); } }
  5588. for (var i = 0; i < files.length; i++) {
  5589. if (winAgent) {
  5590. if (targetFiles.indexOf(files[i].name.toLowerCase()) >= 0) { overWriteCount++; }
  5591. } else {
  5592. if (targetFiles.indexOf(files[i].name) >= 0) { overWriteCount++; }
  5593. }
  5594. }
  5595. if (overWriteCount == 0) {
  5596. // If no overwrite, go ahead with upload
  5597. p13uploadFileContinue(1, files);
  5598. } else {
  5599. // Otherwise, prompt for confirmation
  5600. setDialogMode(2, "Upload File", 3, p13uploadFileContinue, format((overWriteCount == 1) ? "Upload will overwrite 1 file. Continue?" : "Upload will overwrite {0} files. Continue?", overWriteCount), files);
  5601. }
  5602. }
  5603. function p13uploadFileContinue(b, files) {
  5604. uploadFile = {};
  5605. uploadFile.xpath = p13filetreelocation.join('/');
  5606. uploadFile.xfiles = files;
  5607. uploadFile.xfilePtr = -1;
  5608. setDialogMode(2, "Upload File", 10, p13uploadFileCancel, '<div id=p13dfileName>' + "Connecting..." + '</div><br /><progress id=d2progressBar style=width:100% value=0 max=0 />');
  5609. p13uploadNextFile();
  5610. }
  5611. // Perform SHA-384 hashing
  5612. const byteToHex = [];
  5613. for (var n = 0; n <= 0xff; ++n) { var hexOctet = n.toString(16).padStart(2, '0'); byteToHex.push(hexOctet); }
  5614. function arrayBufferToHex(arrayBuffer) { return Array.prototype.map.call(new Uint8Array(arrayBuffer), n => byteToHex[n]).join(''); }
  5615. function performHash(data, f) { window.crypto.subtle.digest('SHA-384', data).then(function (v) { f(arrayBufferToHex(v)); }, function () { f(null); }); }
  5616. function performHashOnFile(file, f) {
  5617. // TODO: At some point, try to make this work for files of unlimited size using a digest stream
  5618. var reader = new FileReader();
  5619. reader.onerror = function (err) { f(null); }
  5620. reader.onload = function () { window.crypto.subtle.digest('SHA-384', reader.result).then(function (v) { f(arrayBufferToHex(v)); }, function () { f(null); }); };
  5621. reader.readAsArrayBuffer(file);
  5622. }
  5623. // Push the next file
  5624. function p13uploadNextFile() {
  5625. uploadFile.xfilePtr++;
  5626. if (uploadFile.xfiles.length > uploadFile.xfilePtr) {
  5627. uploadFile.xptr = 0;
  5628. var file = uploadFile.xfiles[uploadFile.xfilePtr];
  5629. QH('p13dfileName', EscapeHtml(file.name));
  5630. Q('d2progressBar').max = file.size;
  5631. Q('d2progressBar').value = 0;
  5632. if (file.xdata == null) {
  5633. uploadFile.xfile = file;
  5634. // If the remote file already exists and is smaller then our file, see if we can resume the trasfer
  5635. var f = null;
  5636. for (var i in p13filetree.dir) { if (p13filetree.dir[i].n == file.name) { f = p13filetree.dir[i]; } }
  5637. if ((f != null) && (f.s <= uploadFile.xfile.size)) {
  5638. performHashOnFile(uploadFile.xfile, function (hash) { files.sendText(JSON.stringify({ action: 'uploadhash', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, tag: { h: hash.toUpperCase(), s: f.s, skip: f.s == uploadFile.xfile.size } })); });
  5639. } else {
  5640. files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xfile.size }));
  5641. }
  5642. } else {
  5643. // Data already loaded
  5644. uploadFile.xdata = file.xdata;
  5645. files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength }));
  5646. }
  5647. } else {
  5648. p13uploadFileTransferDone();
  5649. }
  5650. }
  5651. // Used to cancel the entire transfer.
  5652. function p13uploadFileCancel(button, tag) {
  5653. if (uploadFile != null) { files.sendText(JSON.stringify({ action: 'uploadcancel', reqid: uploadFile.xfilePtr })); uploadFile = null; }
  5654. p13uploadFileTransferDone();
  5655. }
  5656. // Used to cancel the entire transfer.
  5657. function p13uploadFileTransferDone() {
  5658. uploadFile = null; // No more files to upload, clean up.
  5659. setDialogMode(0); // Close the dialog box
  5660. p13folderup(9999); // Refresh the current folder
  5661. }
  5662. // Receive upload ack from the mesh agent, use this to keep sending more data
  5663. function p13gotUploadData(cmd) {
  5664. if ((uploadFile == null) || (parseInt(uploadFile.xfilePtr) != parseInt(cmd.reqid))) { return; }
  5665. switch (cmd.action) {
  5666. case 'uploadstart': { uploadFile.xdataPriming = 8; p13uploadNextPart(false); break; } // Send 8 more blocks of 16k to fill the websocket.
  5667. case 'uploadack': { p13uploadNextPart(false); break; }
  5668. case 'uploaddone': { if (uploadFile.xfiles.length > uploadFile.xfilePtr + 1) { p13uploadNextFile(); } else { p13uploadFileTransferDone(); } break; }
  5669. case 'uploaderror': { p13uploadFileCancel(); break; }
  5670. case 'uploadhash': {
  5671. var file = uploadFile.xfiles[uploadFile.xfilePtr];
  5672. if (file) {
  5673. if (cmd.tag.h === cmd.hash) {
  5674. if (cmd.tag.skip) {
  5675. p13uploadNextFile();
  5676. } else {
  5677. uploadFile.xptr = cmd.tag.s;
  5678. files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xfile.size, append: true }));
  5679. }
  5680. } else {
  5681. files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xfile.size, append: false }));
  5682. }
  5683. }
  5684. break;
  5685. }
  5686. }
  5687. }
  5688. // Push the next part of the file into the websocket. If dataPriming is true, push more data only if it's not the last block of the file.
  5689. function p13uploadNextPart(dataPriming) {
  5690. if (uploadFile.xdata) {
  5691. var data = uploadFile.xdata, start = uploadFile.xptr;
  5692. if (start >= data.byteLength) {
  5693. files.sendText(JSON.stringify({ action: 'uploaddone', reqid: uploadFile.xfilePtr }));
  5694. } else {
  5695. var end = uploadFile.xptr + (attemptWebRTC ? 16384 : 65536);
  5696. if (end > data.byteLength) { if (dataPriming == true) { return; } end = data.byteLength; }
  5697. var dataslice = new Uint8Array(data.slice(start, end))
  5698. if ((dataslice[0] == 123) || (dataslice[0] == 0)) {
  5699. var datapart = new Uint8Array(end - start + 1);
  5700. datapart.set(dataslice, 1); // Add a zero char at the start of the send, this will indicate that it's not a JSON command.
  5701. files.send(datapart);
  5702. } else {
  5703. files.send(dataslice); // The data does not start with 0 or 123 "{" so it can't be confused for JSON.
  5704. }
  5705. uploadFile.xptr = end;
  5706. Q('d2progressBar').value = end;
  5707. }
  5708. } else if (uploadFile.xfile) {
  5709. if (uploadFile.xreader != null) return; // Data reading already in process
  5710. if (uploadFile.xptr >= uploadFile.xfile.size) return;
  5711. var end = uploadFile.xptr + (attemptWebRTC ? 16384 : 65536);
  5712. if (end > uploadFile.xfile.size) { if (dataPriming == true) { return; } end = uploadFile.xfile.size; }
  5713. uploadFile.xreader = new FileReader();
  5714. uploadFile.xreader.onerror = function (err) { console.log(err); }
  5715. uploadFile.xreader.onload = function () {
  5716. var data = uploadFile.xreader.result;
  5717. delete uploadFile.xreader;
  5718. if (data == null) return;
  5719. var dataslice = new Uint8Array(data)
  5720. if ((dataslice[0] == 123) || (dataslice[0] == 0)) {
  5721. var datapart = new Uint8Array(data.byteLength + 1);
  5722. datapart.set(dataslice, 1); // Add a zero char at the start of the send, this will indicate that it's not a JSON command.
  5723. files.send(datapart);
  5724. } else {
  5725. files.send(dataslice); // The data does not start with 0 or 123 "{" so it can't be confused for JSON.
  5726. }
  5727. uploadFile.xptr = end;
  5728. Q('d2progressBar').value = end;
  5729. if (uploadFile.xptr >= uploadFile.xfile.size) {
  5730. files.sendText(JSON.stringify({ action: 'uploaddone', reqid: uploadFile.xfilePtr }));
  5731. } else {
  5732. if (uploadFile.xdataPriming > 0) { uploadFile.xdataPriming--; p13uploadNextPart(true); }
  5733. }
  5734. };
  5735. uploadFile.xreader.readAsArrayBuffer(uploadFile.xfile.slice(uploadFile.xptr, end));
  5736. }
  5737. }
  5738. //
  5739. // DEVICE DETAILS
  5740. //
  5741. var DeviceDetailsHardware = null;
  5742. var DeviceDetailsNetwork = null;
  5743. var DeviceDetailsNodeId = null;
  5744. function updateDeviceDetails(node, hardware, network) {
  5745. if (currentNode == null) return;
  5746. if (node == null) { node = currentNode; }
  5747. if (currentNode._id != node._id) return;
  5748. if (DeviceDetailsNodeId != node._id) { DeviceDetailsHardware = null; DeviceDetailsNetwork = null; DeviceDetailsNodeId = node._id; }
  5749. if (hardware != null) { DeviceDetailsHardware = hardware; }
  5750. if (network != null) { DeviceDetailsNetwork = network; }
  5751. hardware = DeviceDetailsHardware;
  5752. network = DeviceDetailsNetwork;
  5753. if (hardware == null) { hardware = {}; }
  5754. if (network == null) { network = {}; }
  5755. var sections = [], s = {};
  5756. // Operating System
  5757. var x = '';
  5758. if (node.rname) { x += addDetailItem("Name", EscapeHtml(node.rname), s); }
  5759. if (hardware.windows && hardware.windows.osinfo && hardware.windows.osinfo.Description) { x += addDetailItem("Description", EscapeHtml(hardware.windows.osinfo.Description), s); }
  5760. if (node.osdesc) { x += addDetailItem("Version", EscapeHtml(node.osdesc), s); }
  5761. if (hardware.windows && hardware.windows.osinfo) {
  5762. var m = hardware.windows.osinfo;
  5763. if (m.OSArchitecture) {
  5764. if (m.OSArchitecture.startsWith('32')) { x += addDetailItem("Architecture", "32-bit", s); }
  5765. else if (m.OSArchitecture.startsWith('64')) { x += addDetailItem("Architecture", "64-bit", s); }
  5766. else { x += addDetailItem("Architecture", EscapeHtml(m.OSArchitecture), s); }
  5767. }
  5768. if(m.LastBootUpTime){
  5769. var thedate = {
  5770. year: parseInt(m.LastBootUpTime.substring(0, 4)),
  5771. month: parseInt(m.LastBootUpTime.substring(4, 6)) - 1, // Months are 0-based in JavaScript (0 - January, 11 - December)
  5772. day: parseInt(m.LastBootUpTime.substring(6, 8)),
  5773. hours: parseInt(m.LastBootUpTime.substring(8, 10)),
  5774. minutes: parseInt(m.LastBootUpTime.substring(10, 12)),
  5775. seconds: parseInt(m.LastBootUpTime.substring(12, 14)),
  5776. };
  5777. const date = printDateTime(new Date(thedate.year, thedate.month, thedate.day, thedate.hours, thedate.minutes, thedate.seconds));
  5778. x += addDetailItem("Last Boot Up Time", date);
  5779. }
  5780. if(m.Domain){ x += addDetailItem((m.PartOfDomain ? "Domain" : "Workgroup"), EscapeHtml(m.Domain), s); }
  5781. }
  5782. if(hardware.linux && hardware.linux.LastBootUpTime){
  5783. var lastBootUpTime = new Date(hardware.linux.LastBootUpTime);
  5784. var thedate = {
  5785. year: lastBootUpTime.getFullYear(),
  5786. month: lastBootUpTime.getMonth(),
  5787. day: lastBootUpTime.getDate(),
  5788. hours: lastBootUpTime.getHours(),
  5789. minutes: lastBootUpTime.getMinutes(),
  5790. seconds: lastBootUpTime.getSeconds()
  5791. };
  5792. const date = printDateTime(new Date(thedate.year, thedate.month, thedate.day, thedate.hours, thedate.minutes, thedate.seconds));
  5793. x += addDetailItem("Last Boot Up Time", date);
  5794. }
  5795. if(hardware.darwin && hardware.darwin.LastBootUpTime){
  5796. var lastBootUpTime = new Date(hardware.darwin.LastBootUpTime * 1000); // must times by 1000 even tho timestamp is correct?
  5797. var thedate = {
  5798. year: lastBootUpTime.getFullYear(),
  5799. month: lastBootUpTime.getMonth(),
  5800. day: lastBootUpTime.getDate(),
  5801. hours: lastBootUpTime.getHours(),
  5802. minutes: lastBootUpTime.getMinutes(),
  5803. seconds: lastBootUpTime.getSeconds()
  5804. };
  5805. const date = printDateTime(new Date(thedate.year, thedate.month, thedate.day, thedate.hours, thedate.minutes, thedate.seconds));
  5806. x += addDetailItem("Last Boot Up Time", date);
  5807. }
  5808. // Windows Security Central
  5809. if (node.wsc) {
  5810. var y = [];
  5811. if (node.wsc.antiVirus != null) { if (node.wsc.antiVirus == 'OK') { y.push("AV" + ' - <span style=color:green>' + "OK" + '</span>'); } else { y.push("AV" + ' - <span style=color:red>' + "BAD" + '</span>'); } }
  5812. if (node.wsc.autoUpdate != null) { if (node.wsc.autoUpdate == 'OK') { y.push("Update" + ' - <span style=color:green>' + "OK" + '</span>'); } else { y.push("Update" + ' - <span style=color:red>' + "BAD" + '</span>'); } }
  5813. if (node.wsc.firewall != null) { if (node.wsc.firewall == 'OK') { y.push("Firewall" + ' - <span style=color:green>' + "OK" + '</span>'); } else { y.push("Firewall" + ' - <span style=color:red>' + "BAD" + '</span>'); } }
  5814. x += addDetailItem("Windows Security", y.join(', '));
  5815. }
  5816. // Defender for Windows Server
  5817. if(node.defender) {
  5818. var y = [];
  5819. if (node.defender.RealTimeProtection != null) { if (node.defender.RealTimeProtection == true) { y.push("RealTimeProtection" + ' - <span style=color:green>' + "On" + '</span>'); } else { y.push("RealTimeProtection" + ' - <span style=color:red>' + "Off" + '</span>'); } }
  5820. if (node.defender.TamperProtected != null) { if (node.defender.TamperProtected == true) { y.push("TamperProtection" + ' - <span style=color:green>' + "On" + '</span>'); } else { y.push("TamperProtection" + ' - <span style=color:red>' + "Off" + '</span>'); } }
  5821. if (node.defender.AntivirusSignatureVersion != null) { y.push("SignatureVersion" + ' - <span style=color:green>' + EscapeHtml(node.defender.AntivirusSignatureVersion) + '</span>'); }
  5822. if (y.length > 0) x += addDetailItem("Windows Defender", y.join(', '));
  5823. }
  5824. // Antivirus
  5825. if (node.av && node.av.length > 0) {
  5826. var y = [];
  5827. for (var i in node.av) {
  5828. if (node.av[i].product) {
  5829. var avx = EscapeHtml(node.av[i].product);
  5830. if (node.av[i].enabled !== true) { avx += ' - <span style=color:red>' + "Disabled" + '</span>'; }
  5831. if (node.av[i].updated !== true) { avx += ' - <span style=color:red>' + "Out of date" + '</span>'; }
  5832. if ((node.av[i].enabled == true) && (node.av[i].updated == true)) { avx += ' - <span style=color:green>' + "OK" + '</span>'; }
  5833. y.push(avx);
  5834. }
  5835. }
  5836. x += addDetailItem("Antivirus", y.join('<br />'));
  5837. }
  5838. // Active Users
  5839. if (node.users && node.users.length > 0) {
  5840. var u = node.users.map(function(user) {
  5841. return addKeyLinkConditional(EscapeHtml(user), "Locked", (node.lusers && node.lusers.indexOf(user) >= 0));
  5842. }).join(', ');
  5843. x += addDetailItem((node.users.length > 1 ? "Active Users" : "Active User"), u);
  5844. }
  5845. if (x != '') { sections.push({ name: "Operating System", html: x, img: 'software' }); }
  5846. // MeshAgent
  5847. if (node.agent) {
  5848. var x = '';
  5849. if ((node.agent != null) && (node.agent.id != null) && (node.agent.ver != null)) {
  5850. var str = '';
  5851. if (node.agent.id <= agentsStr.length) { str = agentsStr[node.agent.id]; } else { str = agentsStr[0]; }
  5852. if (node.agent.ver != 0) { str += ' v' + node.agent.ver; }
  5853. if (node.agent.id == 14) { str = node.agent.core; }
  5854. x += addDetailItem("Mesh Agent", str);
  5855. }
  5856. if (node.firstconnect) { x += addDetailItem("First agent connection", printDateTime(new Date(node.firstconnect))); }
  5857. if ((node.conn & 1) != 0) {
  5858. x += addDetailItem("Last agent connection", "Connected now");
  5859. } else {
  5860. if (node.lastconnect) { x += addDetailItem("Last agent connection", printDateTime(new Date(node.lastconnect))); }
  5861. }
  5862. if (node.lastaddr) {
  5863. var splitip = node.lastaddr.split(':');
  5864. if (splitip.length > 2) {
  5865. // IPv6
  5866. x += addDetailItem("Last agent address", node.lastaddr);
  5867. } else {
  5868. // IPv4
  5869. if (isPrivateIP(node.lastaddr)) {
  5870. x += addDetailItem("Last agent address", splitip[0]);
  5871. } else {
  5872. x += addDetailItem("Last agent address", '<a href="https://iplocation.com/?ip=' + splitip[0] + '" rel="noreferrer noopener" target="MeshIPLoopup">' + splitip[0] + '</a>');
  5873. }
  5874. }
  5875. }
  5876. if (hardware.agentvers != null) {
  5877. if (hardware.agentvers.compileTime) {
  5878. try {
  5879. var d = Date.parse(hardware.agentvers.compileTime)
  5880. x += addDetailItem("Compile time", printDateTime(new Date(d)));
  5881. } catch (ex) { }
  5882. }
  5883. }
  5884. if (hardware.time != null) {
  5885. x += addDetailItem("Last details update", printDateTime(new Date(hardware.time)));
  5886. }
  5887. if (x != '') { sections.push({ name: "Mesh Agent", html: x, img: 'meshagent' }); }
  5888. }
  5889. // Mobile
  5890. if (hardware.mobile) {
  5891. var x = '';
  5892. if (hardware.mobile.brand && hardware.mobile.model) { x += addDetailItem("Model", EscapeHtml(hardware.mobile.brand + ', ' + hardware.mobile.model), s); }
  5893. if (hardware.mobile.device) { x += addDetailItem("Device", EscapeHtml(hardware.mobile.device), s); }
  5894. if (hardware.mobile.bootloader) { x += addDetailItem("Bootloader", EscapeHtml(hardware.mobile.bootloader), s); }
  5895. if (hardware.mobile.id) { x += addDetailItem("Identifier", EscapeHtml(hardware.mobile.id), s); }
  5896. if (hardware.mobile.host) { x += addDetailItem("Hostname", EscapeHtml(hardware.mobile.host), s); }
  5897. if (hardware.mobile.androidapi && hardware.mobile.androidrelease) { x += addDetailItem("Android Version", EscapeHtml(hardware.mobile.androidrelease + ', API Level ' + hardware.mobile.androidapi), s); }
  5898. if (x != '') { sections.push({ name: "Mobile Device", html: x, img: 'mobile' }); }
  5899. }
  5900. // Networking
  5901. if (network.netif2 != null) {
  5902. // Display one network interface for each MAC address
  5903. var x = '';
  5904. x += '<table style=width:100%>';
  5905. for (var i in network.netif2) {
  5906. var m = network.netif2[i];
  5907. if ((Array.isArray(m) == false) || (m.length < 1) || (m[0] == null) || ((typeof m[0].mac == 'string') && (m[0].mac.startsWith('00:00:00:00')))) continue;
  5908. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  5909. x += '<div style=margin-bottom:3px><b>' + EscapeHtml(i + (m[0].fqdn ? (', ' + m[0].fqdn) : '')) + '</b></div>';
  5910. if (m.desc) { x += addDetailItem("Description", EscapeHtml(m.desc).split('(R)').join('&reg;')); }
  5911. //if (m.dnssuffix) { x += addDetailItem("DNS Suffix", m.dnssuffix); }
  5912. if (typeof m[0].mac == 'string') {
  5913. if (m[0].gatewaymac) {
  5914. x += addDetailItem("MAC Layer", format("MAC: {0}, Gateway: {1}", EscapeHtml(m[0].mac), EscapeHtml(m[0].gatewaymac)));
  5915. } else {
  5916. x += addDetailItem("MAC Layer", format("MAC: {0}", EscapeHtml(m[0].mac)));
  5917. }
  5918. }
  5919. if (typeof m[0].speed == 'number' && (m[0].speed != 9223372036854775807 && m[0].speed > 0)) {
  5920. x += addDetailItem("Interface Speed", format("{0}", getNetworkSpeed(m[0].speed)));
  5921. }
  5922. for (var j = 0; j < m.length; j++) {
  5923. var iplayer = m[j], items = [];
  5924. if (iplayer.address) { items.push(format("IP: {0}", EscapeHtml(iplayer.address))); }
  5925. if (iplayer.netmask) { items.push(format("Mask: {0}", EscapeHtml(iplayer.netmask))); }
  5926. if (iplayer.gateway) { items.push(format("Gateway: {0}", EscapeHtml(iplayer.gateway))); }
  5927. if (items.length > 0) {
  5928. if (iplayer.family == 'IPv4') { x += addDetailItem("IPv4 Layer", items.join(", ")); }
  5929. if (iplayer.family == 'IPv6') { x += addDetailItem("IPv6 Layer", items.join(", ")); }
  5930. }
  5931. }
  5932. x += '</div></td></tr>';
  5933. }
  5934. if (hardware.network && hardware.network.dns) {
  5935. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  5936. x += addDetailItem('<b>' + "DNS Servers" + '</b>', hardware.network.dns.join(", "));
  5937. x += '</div></td></tr>';
  5938. }
  5939. x += '</table>';
  5940. if (x != '') { sections.push({ name: "Networking", html: x, img: 'networking' }); }
  5941. }
  5942. // Attribute: Intel AMT
  5943. if (node.intelamt != null) {
  5944. var x = '';
  5945. x += addDetailItem("Version", (node.intelamt.ver) ? ('v' + EscapeHtml(node.intelamt.ver)) : ('<i>' + "Unknown" + '</i>'), s);
  5946. x += addDetailItem("Identifier", (node.intelamt.uuid) ? (EscapeHtml(node.intelamt.uuid)) : ('<i>' + "Unknown" + '</i>'), s);
  5947. var provisioningStates = { 0: nobreak("Not Activated (Pre)"), 1: nobreak("Not Activated (In)"), 2: nobreak("Activated") };
  5948. var provisioningMode = '';
  5949. if ((node.intelamt.state == 2) && node.intelamt.flags) { if (node.intelamt.flags & 2) { provisioningMode = (', ' + "Client Control Mode (CCM)"); } else if (node.intelamt.flags & 4) { provisioningMode = (', ' + "Admin Control Mode (ACM)"); } }
  5950. x += addDetailItem("Provisioning State", ((node.intelamt.state) ? (provisioningStates[node.intelamt.state]) : ('<i>' + "Unknown" + '</i>')) + provisioningMode, s);
  5951. x += addDetailItem("Security", (node.intelamt.tls == 1) ? "Secured using TLS" : "TLS is not setup", s);
  5952. // Check that the Intel AMT user is setup and there is no warnings (1 = invalid credentials, 8 = trying)
  5953. x += addDetailItem("Admin Credentials", ((node.intelamt.user) == null || (node.intelamt.user == '') || ((node.intelamt.warn != null) && ((node.intelamt.warn & 9) != 0))) ? "Not Known" : "Known", s);
  5954. if (x != '') {
  5955. if ((typeof node.intelamt.sku == 'number') && ((node.intelamt.sku & 16) != 0)) {
  5956. sections.push({ name: "Intel&reg; Standard Manageability (Intel&reg; SM)", html: x, img: 'amt' });
  5957. } else {
  5958. sections.push({ name: "Intel&reg; Active Management Technology (Intel&reg; AMT)", html: x, img: 'amt' });
  5959. }
  5960. }
  5961. }
  5962. if (hardware.identifiers) {
  5963. var x = '', ident = hardware.identifiers;
  5964. // BIOS
  5965. if (ident.bios_vendor) { x += addDetailItem("Vendor", EscapeHtml(ident.bios_vendor), s); }
  5966. if (ident.bios_version) { x += addDetailItem("Version", EscapeHtml(ident.bios_version), s); }
  5967. if (ident.bios_serial) { x += addDetailItem("Serial", EscapeHtml(ident.bios_serial), s); }
  5968. if (ident.bios_mode) { x += addDetailItem("Mode", EscapeHtml(ident.bios_mode), s); }
  5969. if (x != '') { sections.push({ name: "BIOS", html: x, img: 'chip' }); }
  5970. // Motherboard
  5971. x = '';
  5972. if (ident.board_vendor) { x += addDetailItem("Vendor", EscapeHtml(ident.board_vendor), s); }
  5973. if (ident.board_name) { x += addDetailItem("Name", EscapeHtml(ident.board_name), s); }
  5974. if (ident.board_serial && (ident.board_serial != '')) { x += addDetailItem("Serial", EscapeHtml(ident.board_serial), s); }
  5975. if (ident.board_version) { x += addDetailItem("Version", EscapeHtml(ident.board_version), s); }
  5976. if (ident.product_uuid) { x += addDetailItem("Identifier", EscapeHtml(ident.product_uuid), s); }
  5977. if (ident.cpu_name) { x += addDetailItem("CPU", EscapeHtml(ident.cpu_name).split('(TM)').join('&trade;').split('(R)').join('&reg;'), s); }
  5978. if (ident.gpu_name) { for (var i in ident.gpu_name) { x += addDetailItem("GPU", EscapeHtml(ident.gpu_name[i]).split('(TM)').join('&trade;').split('(R)').join('&reg;'), s); } }
  5979. if (x != '') { sections.push({ name: "Motherboard", html: x, img: 'motherboard' }); }
  5980. // System
  5981. x = '';
  5982. if (ident.chassis_manufacturer) { x += addDetailItem("Manufacturer", EscapeHtml(ident.chassis_manufacturer), s); }
  5983. if (ident.product_name) { x += addDetailItem("Product Name", EscapeHtml(ident.product_name), s); }
  5984. if (ident.chassis_serial) { x += addDetailItem("Serial", EscapeHtml(ident.chassis_serial), s); }
  5985. if (ident.chassis_assettag) { x += addDetailItem("Asset Tag", EscapeHtml(ident.chassis_assettag), s); }
  5986. if (x != '') { sections.push({ name: "System", html: x, img: 'system' }); }
  5987. }
  5988. // TPM
  5989. if (hardware.tpm) {
  5990. var x = '', tpm = hardware.tpm;
  5991. if (tpm.SpecVersion) { x += addDetailItem("SpecVersion", parseFloat(EscapeHtml(tpm.SpecVersion)).toFixed(1), s); }
  5992. if (tpm.ManufacturerId) { x += addDetailItem("Identifier", EscapeHtml(tpm.ManufacturerId), s); }
  5993. if (tpm.ManufacturerVersion) { x += addDetailItem("Version", EscapeHtml(tpm.ManufacturerVersion), s); }
  5994. if (tpm.IsActivated != null) { x += addDetailItem("Activated", (tpm.IsActivated ? "Yes" : "No"), s); }
  5995. if (tpm.IsEnabled != null) { x += addDetailItem("Enabled", (tpm.IsEnabled ? "Yes" : "No"), s); }
  5996. if (tpm.IsOwned != null) { x += addDetailItem("Owned", (tpm.IsOwned ? "Yes" : "No"), s); }
  5997. if (x != '') { sections.push({ name: "TPM", html: x, img: 'tpm' }); }
  5998. }
  5999. // Batteries
  6000. if (hardware.battery) {
  6001. var x = '';
  6002. x += '<table style=width:100%>';
  6003. for (var i in hardware.battery) {
  6004. var battery = hardware.battery[i];
  6005. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  6006. x += '<div style=margin-bottom:3px><b>' + EscapeHtml(battery.DeviceName ? battery.DeviceName : "Unknown") + '</b></div>';
  6007. if (battery.CycleCount) { x += addDetailItem("Cycle Count", EscapeHtml(battery.CycleCount), s); }
  6008. if (battery.FullChargedCapacity) { x += addDetailItem("Full Charged Capacity", format("{0} mWh", battery.FullChargedCapacity), s); }
  6009. if (battery.EstimatedRuntime) { x += addDetailItem("Estimated Runtime", format("{0} minutes", Math.floor((battery.EstimatedRuntime / 60))), s); }
  6010. if (battery.Chemistry) { x += addDetailItem("Chemistry", EscapeHtml(battery.Chemistry), s); }
  6011. if (battery.DesignedCapacity) { x += addDetailItem("Design Capacity", format("{0} mWh", battery.DesignedCapacity), s); }
  6012. if (battery.ManufactureDate) { x += addDetailItem("Manufacture Date", EscapeHtml(battery.ManufactureDate), s); }
  6013. if (battery.ManufactureName) { x += addDetailItem("Manufacture Name", EscapeHtml(battery.ManufactureName), s); }
  6014. if (battery.SerialNumber) { x += addDetailItem("Serial Number", EscapeHtml(battery.SerialNumber), s); }
  6015. if (battery.ChargeRate) { x += addDetailItem("Charge Rate", format("{0} mW", battery.ChargeRate), s); }
  6016. if (battery.Charging != null) { x += addDetailItem("Charging", (battery.Charging ? "Yes" : "No"), s); }
  6017. if (battery.DischargeRate) { x += addDetailItem("Discharge Rate", format("{0} mW", battery.DischargeRate), s); }
  6018. if (battery.Discharging != null) { x += addDetailItem("Discharging", (battery.Discharging ? "Yes" : "No"), s); }
  6019. if (battery.RemainingCapacity) { x += addDetailItem("Remaining Capacity", format("{0} mWh", battery.RemainingCapacity), s); }
  6020. if (battery.Voltage) { x += addDetailItem("Voltage", format("{0} V", (battery.Voltage / 1000)), s); }
  6021. if (battery.Health) { x += addDetailItem("Health", format("{0} %", battery.Health), s); }
  6022. if (battery.BatteryCharge) { x += addDetailItem("Battery Charge", format("{0} %", battery.BatteryCharge), s); }
  6023. x += '</div>';
  6024. }
  6025. x += '</table>';
  6026. if (x != '') { sections.push({ name: "Battery", html: x, img: 'battery'}); }
  6027. }
  6028. if (hardware.windows) {
  6029. if (hardware.windows.memory && (hardware.windows.memory.length > 0)) {
  6030. var x = '';
  6031. // Sort Memory
  6032. hardware.windows.memory.sort(function (a, b) { if (a.BankLabel > b.BankLabel) return 1; if (a.BankLabel < b.BankLabel) return -1; return 0; });
  6033. x += '<table style=width:100%>';
  6034. for (var i in hardware.windows.memory) {
  6035. var m = hardware.windows.memory[i];
  6036. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  6037. x += '<div style=margin-bottom:3px><b>' + EscapeHtml((m.BankLabel ? m.BankLabel : (m.DeviceLocator ? m.DeviceLocator : 'Unknown'))) + '</b></div>';
  6038. if (m.Capacity && m.Speed) { x += addDetailItem("Capacity / Speed", format("{0} Mb, {1} Mhz", (m.Capacity / 1024 / 1024), m.Speed), s); }
  6039. else if (m.Capacity) { x += addDetailItem("Capacity", format("{0} Mb", (m.Capacity / 1024 / 1024)), s); }
  6040. if (m.PartNumber) { x += addDetailItem("Part Number", EscapeHtml((m.Manufacturer && m.Manufacturer != 'Undefined') ? (m.Manufacturer + ', ') : '') + EscapeHtml(m.PartNumber), s); }
  6041. x += '</div>';
  6042. }
  6043. x += '</table>';
  6044. if (x != '') { sections.push({ name: "Memory", html: x, img: 'ram' }); }
  6045. }
  6046. }
  6047. if (hardware.linux) {
  6048. if (hardware.linux.memory && (hardware.linux.memory.Memory_Device.length > 0)) {
  6049. var x = '';
  6050. // Sort Memory
  6051. hardware.linux.memory.Memory_Device.sort(function(a, b) { if (a.Locator > b.Locator) return 1; if (a.Locator < b.Locator) return -1; return 0; });
  6052. x += '<table style=width:100%>';
  6053. for (var i in hardware.linux.memory.Memory_Device) {
  6054. var m = hardware.linux.memory.Memory_Device[i];
  6055. if(m.Size && (m.Size == 'No Module Installed')) continue;
  6056. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  6057. x += '<div style=margin-bottom:3px><b>' + EscapeHtml((m.Locator ? m.Locator : 'Unknown')) + '</b></div>';
  6058. if (m.Size && m.Speed) { x += addDetailItem("Capacity / Speed", format("{0}, {1}", m.Size, m.Speed), s); }
  6059. else if (m.Size) { x += addDetailItem("Capacity", format("{0}", (m.Size)), s); }
  6060. if (m.PartNumber) { x += addDetailItem("Part Number", EscapeHtml((m.Manufacturer && m.Manufacturer != 'Undefined')?(m.Manufacturer + ', '):'') + EscapeHtml(m.PartNumber), s); }
  6061. x += '</div>';
  6062. }
  6063. x += '</table>';
  6064. if (x != '') { sections.push({ name: "Memory", html: x, img: 'ram'}); }
  6065. }
  6066. }
  6067. if (hardware.darwin) {
  6068. if (hardware.darwin.memory && (hardware.darwin.memory.length > 0)) {
  6069. var x = '';
  6070. x += '<table style=width:100%>';
  6071. for (var i in hardware.darwin.memory) {
  6072. var m = hardware.darwin.memory[i];
  6073. if(m.Size && (m.Size == 'No Module Installed')) continue;
  6074. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  6075. x += '<div style=margin-bottom:3px><b>' + EscapeHtml((m.DeviceLocator ? m.DeviceLocator : 'Unknown')) + '</b></div>';
  6076. if (m.Size && m.Speed) { x += addDetailItem("Capacity / Speed", format("{0}, {1}", m.Size, m.Speed), s); }
  6077. else if (m.Size) { x += addDetailItem("Capacity", format("{0}", (m.Size)), s); }
  6078. if (m.PartNumber) { x += addDetailItem("Part Number", EscapeHtml((m.Manufacturer && m.Manufacturer != '')?(m.Manufacturer + ', '):'') + EscapeHtml(m.PartNumber), s); }
  6079. x += '</div>';
  6080. }
  6081. x += '</table>';
  6082. if (x != '') { sections.push({ name: "Memory", html: x, img: 'ram'}); }
  6083. }
  6084. }
  6085. // Storage
  6086. if (hardware.identifiers && ident.storage_devices) {
  6087. var x = '';
  6088. // Sort Storage
  6089. ident.storage_devices.sort(function (a, b) { if (a.Caption > b.Caption) return 1; if (a.Caption < b.Caption) return -1; return 0; });
  6090. x += '<table style=width:100%>';
  6091. for (var i in ident.storage_devices) {
  6092. var m = ident.storage_devices[i];
  6093. if (m.Size) {
  6094. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  6095. x += '<div style=margin-bottom:3px><b>' + EscapeHtml(m.Caption) + '</b></div>';
  6096. if (m.Model && (m.Model != m.Caption)) { x += addDetailItem("Model", EscapeHtml(m.Model), s); }
  6097. if (m.Size) {
  6098. if ((typeof m.Size == 'string') && (parseInt(m.Size) == m.Size)) { m.Size = parseInt(m.Size); }
  6099. if (typeof m.Size == 'number') { x += addDetailItem("Capacity", format("{0} Mb", Math.floor(m.Size / 1024 / 1024)), s); }
  6100. if (typeof m.Size == 'string') { x += addDetailItem("Capacity", EscapeHtml(m.Size), s); }
  6101. }
  6102. if(hardware.windows && hardware.windows.drives && m.Model){
  6103. const foundObject = hardware.windows.drives.find(obj => obj['Model'] === m.Model);
  6104. if(foundObject) x += addDetailItem("Status", EscapeHtml(foundObject.Status), s);
  6105. }
  6106. x += '</div>';
  6107. }
  6108. }
  6109. x += '</table>';
  6110. if (x != '') { sections.push({ name: "Storage", html: x, img: 'storage' }); }
  6111. }
  6112. // Volumes and Bitlocker
  6113. if (hardware.windows && hardware.windows.volumes) {
  6114. var x = '';
  6115. for (var i in hardware.windows.volumes) {
  6116. var m = hardware.windows.volumes[i];
  6117. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  6118. x += '<div style=margin-bottom:3px><b>' + i + ':' + (((m.name == null) || (m.name == '')) ? '' : (' - ' + EscapeHtml(m.name))) + '</b></div>';
  6119. if (m.size) {
  6120. var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
  6121. var j = parseInt(Math.floor(Math.log(Math.abs(m.size)) / Math.log(1024)), 10);
  6122. var fsize = (j === 0 ? `${m.size} ${sizes[j]}` : `${(m.size / (1024 ** j)).toFixed(2)} ${sizes[j]}`);
  6123. x += addDetailItem("Capacity", EscapeHtml(fsize), s);
  6124. }
  6125. if (m.sizeremaining) {
  6126. var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
  6127. var j = parseInt(Math.floor(Math.log(Math.abs(m.sizeremaining)) / Math.log(1024)), 10);
  6128. var fsize = (j === 0 ? `${m.sizeremaining} ${sizes[j]}` : `${(m.sizeremaining / (1024 ** j)).toFixed(2)} ${sizes[j]}`);
  6129. x += addDetailItem("Capacity Remaining", EscapeHtml(fsize), s);
  6130. }
  6131. if (m.type) {
  6132. var type = (m.removable == true ? "Removable" : (m.cdrom == true ? "CD-ROM" : ''));
  6133. x += addDetailItem("File System", (type != '' ? (type + ' / ') : '') + (m.type == 'Unknown' ? "Unknown" : EscapeHtml(m.type)), s);
  6134. }
  6135. if (m.protectionStatus || m.volumeStatus) {
  6136. var bitlockerState = [];
  6137. if (m.protectionStatus) bitlockerState.push("Enabled");
  6138. if (m.volumeStatus && m.volumeStatus == 'FullyDecrypted') bitlockerState.push("Fully Decrypted");
  6139. if (m.volumeStatus && m.volumeStatus == 'EncryptionInProgress') bitlockerState.push("Encryption In Progress");
  6140. if (m.volumeStatus && m.volumeStatus == 'FullyEncrypted') bitlockerState.push("Fully Encrypted");
  6141. bitlockerState = bitlockerState.join(' - ');
  6142. if (m.recoveryPassword) { bitlockerState += addKeyLink('', 'deviceDetailsShowBitlockerInfo(\"' + encodeURIComponentEx(i) + '\",\"' + encodeURIComponentEx(m.identifier) + '\",\"' + encodeURIComponentEx(m.recoveryPassword) + '\")'); }
  6143. x += addDetailItem("BitLocker", bitlockerState, s);
  6144. }
  6145. x += '</div>';
  6146. }
  6147. if (x != '') { sections.push({ name: "Storage Volumes", html: '<table style=width:100%>' + x + '</table>', img: 'storage'}); }
  6148. }
  6149. // Linux Volumes
  6150. if (hardware.linux && hardware.linux.volumes) {
  6151. var x = '';
  6152. for (var i in hardware.linux.volumes) {
  6153. var m = hardware.linux.volumes[i];
  6154. if(m.mount_point.startsWith('/var/lib/docker/overlay2')) continue;
  6155. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  6156. x += '<div style=margin-bottom:3px><b>' + m.mount_point + '</b></div>';
  6157. if (m.size) {
  6158. var sizes = ['KB', 'MB', 'GB', 'TB'];
  6159. var j = parseInt(Math.floor(Math.log(Math.abs(m.size)) / Math.log(1024)), 10);
  6160. var fsize = (j === 0 ? `${m.size} ${sizes[j]}` : `${(m.size / (1024 ** j)).toFixed(2)} ${sizes[j]}`);
  6161. x += addDetailItem("Capacity", EscapeHtml(fsize), s);
  6162. }
  6163. if (m.available) {
  6164. if (Math.abs(m.available) == 0) {
  6165. var fsize = `0 KB`;
  6166. } else {
  6167. var sizes = ['KB', 'MB', 'GB', 'TB'];
  6168. var j = parseInt(Math.floor(Math.log(Math.abs(m.available)) / Math.log(1024)), 10);
  6169. var fsize = (j === 0 ? `${m.available} ${sizes[j]}` : `${(m.available / (1024 ** j)).toFixed(2)} ${sizes[j]}`);
  6170. }
  6171. x += addDetailItem("Capacity Remaining", EscapeHtml(fsize), s);
  6172. }
  6173. if (m.type) {
  6174. var type = (m.removable == true ? "Removable" : (m.cdrom == true ? "CD-ROM" : ''));
  6175. x += addDetailItem("File System", (type != '' ? (type + ' / ') : '') + (m.type == 'Unknown' ? "Unknown" : EscapeHtml(m.type)), s);
  6176. }
  6177. x += '</div>';
  6178. }
  6179. if (x != '') { sections.push({ name: "Storage Volumes", html: '<table style=width:100%>' + x + '</table>', img: 'storage'}); }
  6180. }
  6181. // MacOS Volumes
  6182. if (hardware.darwin && hardware.darwin.volumes) {
  6183. var x = '';
  6184. for (var i in hardware.darwin.volumes) {
  6185. var m = hardware.darwin.volumes[i];
  6186. if(m.mount_point.startsWith('/var/lib/docker/overlay2')) continue;
  6187. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  6188. x += '<div style=margin-bottom:3px><b>' + m.mount_point + '</b></div>';
  6189. if (m.size) {
  6190. x += addDetailItem("Capacity", EscapeHtml(m.size), s);
  6191. }
  6192. if (m.available) {
  6193. x += addDetailItem("Capacity Remaining", EscapeHtml(m.available), s);
  6194. }
  6195. if (m.type) {
  6196. var type = (m.removable == true ? "Removable" : (m.cdrom == true ? "CD-ROM" : ''));
  6197. x += addDetailItem("File System", (type != '' ? (type + ' / ') : '') + (m.type == 'Unknown' ? "Unknown" : EscapeHtml(m.type)), s);
  6198. }
  6199. x += '</div>';
  6200. }
  6201. if (x != '') { sections.push({ name: "Storage Volumes", html: '<table style=width:100%>' + x + '</table>', img: 'storage'}); }
  6202. }
  6203. // Render the sections
  6204. var x = '';
  6205. for (var i in sections) {
  6206. if (sections[i].img == null) {
  6207. x += '<div class=DevSt style=margin-bottom:3px;margin-left:4px><b>' + sections[i].name + '</b></div><div style=margin-bottom:10px;margin-left:4px>' + sections[i].html + '</div>';
  6208. } else {
  6209. x += '<table style=width:100%><tr>';
  6210. x += '<td style=width:32px;vertical-align:top><img src=images/details/' + sections[i].img + '32.png srcset="images/details/' + sections[i].img + '64.png 2x" border=0 width=32 /></td>'; // height=12
  6211. x += '<td><div class=DevSt style=margin-bottom:3px;margin-left:4px><b>' + sections[i].name + '</b></div><div style=margin-bottom:10px;margin-left:4px>' + sections[i].html + '</div></td>';
  6212. x += '</tr></table>';
  6213. }
  6214. }
  6215. if (x == '') {
  6216. QH('p10detailshtml', "No information for this device.");
  6217. } else {
  6218. QH('p10detailshtml', x);
  6219. }
  6220. }
  6221. function deviceDetailsShowBitlockerInfo(drive, identifier, password) {
  6222. if (xxdialogMode) return false;
  6223. var x = '<div><p>' + "Identifier" + '</p><p style=user-select:text;font-weight:bold>' + (identifier ? decodeURIComponent(identifier) : "Unknown") + '</p>';
  6224. x += '<p>' + "Recovery Password" + '</p><p style=user-select:text;font-weight:bold>' + (password ? decodeURIComponent(password) : "Unknown") + '</p></div>';
  6225. setDialogMode(2, decodeURIComponent(drive) + ': ' + "BitLocker Information", 1, null, x, '');
  6226. }
  6227. //
  6228. // CONSOLE
  6229. //
  6230. /*
  6231. function agentConsoleHandleKeys(e) {
  6232. if ((e.ctrlKey) || (e.altKey)) { return true; }
  6233. var processed = 0, box = Q('p15consoleText');
  6234. if (e.key) {
  6235. if (e.keyCode == 13 && consoleFocus == 0) { p15consoleSend(e); processed = 1; }
  6236. else if (e.keyCode == 8 && consoleFocus == 0) { var x = box.value; box.value = x.substring(0, x.length - 1); processed = 1; }
  6237. else if (e.keyCode == 27) { box.value = ''; processed = 1; }
  6238. else if ((e.keyCode == 38) || (e.keyCode == 40)) { // Arrow up || Arrow down
  6239. var hindex = consoleHistory.indexOf(box.value);
  6240. //console.log(hindex, consoleHistory);
  6241. if ((e.keyCode == 38) && ((consoleHistory.length - 1) > hindex)) { box.value = consoleHistory[hindex + 1]; }
  6242. else if ((e.keyCode == 40) && (hindex > 0)) { box.value = consoleHistory[hindex - 1]; }
  6243. else if ((e.keyCode == 40) && (hindex == 0)) { box.value = ''; }
  6244. processed = 1;
  6245. }
  6246. else if (e.key.length === 1) {
  6247. //box.value = ((box.value + e.key));
  6248. insertTextAtCursor(box, e.key);
  6249. processed = 1;
  6250. }
  6251. } else {
  6252. if (e.charCode != 0 && consoleFocus == 0) { box.value = ((box.value + String.fromCharCode(e.charCode))); processed = 1; }
  6253. }
  6254. if (processed > 0) { return haltEvent(e); }
  6255. }
  6256. */
  6257. // Insert text at the cursor location on the
  6258. function insertTextAtCursor(ctrl, val) {
  6259. if (document.selection) { ctrl.focus(); sel = document.selection.createRange(); sel.text = val; }
  6260. else if (ctrl.selectionStart || ctrl.selectionStart == '0') {
  6261. var start = ctrl.selectionStart, end = ctrl.selectionEnd;
  6262. ctrl.value = ctrl.value.substring(0, start) + val + ctrl.value.substring(end, ctrl.value.length);
  6263. ctrl.setSelectionRange(end + 1, end + 1);
  6264. } else { ctrl.value += myValue; }
  6265. }
  6266. var consoleNode;
  6267. var consoleServerText = '';
  6268. function setupConsole() {
  6269. // Setup the console
  6270. var samenode = (consoleNode == currentNode);
  6271. consoleNode = currentNode;
  6272. var mesh = meshes[consoleNode.meshid];
  6273. var rights = GetNodeRights(currentNode);
  6274. if ((rights & 16) != 0) {
  6275. if (consoleNode.consoleText == null) { consoleNode.consoleText = ''; }
  6276. if (samenode == false) {
  6277. QH('p15agentConsoleText', consoleNode.consoleText);
  6278. Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight;
  6279. }
  6280. var online = (((consoleNode.conn & 1) != 0) || ((consoleNode.conn & 16) != 0)) ? true : false;
  6281. var onlineText = ((consoleNode.conn & 1) != 0) ? "Agent is online" : "Agent is offline"
  6282. if ((consoleNode.conn & 16) != 0) { onlineText += ", MQTT is online" }
  6283. QH('p15statetext', onlineText);
  6284. QE('p15uploadCore', ((consoleNode.conn & 1) != 0));
  6285. QV('p15outputselecttd', ((consoleNode.conn & 16) != 0) || ((currentNode.pmt == 1) && ((features2 & 2) != 0)));
  6286. QV('p15outputselect2', ((consoleNode.conn & 16) != 0)); // MQTT channel
  6287. QV('p15outputselect3', ((currentNode.pmt == 1) && ((features2 & 2) != 0))); // Push Notification channel
  6288. var c = Q('p15outputselect').value;
  6289. if (((consoleNode.conn & 16) == 0) && (c == 2)) { c = 1; Q('p15outputselect').value = 1; }
  6290. if (((currentNode.pmt != 1) || ((features2 & 2) == 0)) && (c == 3)) { c = 1; Q('p15outputselect').value = 1; }
  6291. var active = false;
  6292. if (((consoleNode.conn & 1) != 0) && (c == 1)) { active = true; } // Agent
  6293. if (((consoleNode.conn & 16) != 0) && (c == 2)) { active = true; } // MQTT
  6294. if (((currentNode.pmt == 1) && ((features2 & 2) != 0)) && (c == 3)) { active = true; } // Push
  6295. QE('p15consoleText', active);
  6296. } else {
  6297. QH('p15statetext', "Access Denied");
  6298. QE('p15consoleText', false);
  6299. QE('p15uploadCore', false);
  6300. QV('p15outputselecttd', false);
  6301. }
  6302. QV('devListToolbarViewIcons3', ((consoleNode.conn & 1) != 0));
  6303. }
  6304. // Clear the console for this node
  6305. function p15consoleClear() {
  6306. QH('p15agentConsoleText', '');
  6307. Q('id_p15consoleClear').blur();
  6308. consoleNode.consoleText = '';
  6309. }
  6310. // Send a command to the agent
  6311. var consoleHistory = [];
  6312. function p15consoleSend(e) {
  6313. if (e && e.keyCode != 13) return;
  6314. var v = Q('p15consoleText').value, t = '<div style=color:green>&gt; ' + EscapeHtml(v) + '<br/></div>';
  6315. if (((consoleNode.conn & 16) != 0) && (Q('p15outputselect').value == 2)) {
  6316. // Send the command to MQTT
  6317. t = '<div style=color:orange>' + "MQTT" + '&gt; ' + EscapeHtml(v) + '<br/></div>';
  6318. consoleNode.consoleText += t;
  6319. meshserver.send({ action: 'sendmqttmsg', topic: 'console', nodeids: [consoleNode._id], msg: v });
  6320. } else if ((consoleNode.pmt == 1) && (Q('p15outputselect').value == 3) && ((features2 & 2) != 0)) {
  6321. // Send the command using push notification
  6322. t = '<div style=color:violet>' + "PUSH" + '&gt; ' + EscapeHtml(v) + '<br/></div>';
  6323. consoleNode.consoleText += t;
  6324. meshserver.send({ action: 'pushconsole', nodeid: consoleNode._id, console: v });
  6325. } else if ((consoleNode.conn & 1) != 0) {
  6326. // Send the command to the mesh agent
  6327. consoleNode.consoleText += t;
  6328. meshserver.send({ action: 'msg', type: 'console', nodeid: consoleNode._id, value: v });
  6329. }
  6330. Q('p15agentConsoleText').innerHTML += t;
  6331. Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight;
  6332. Q('p15consoleText').value = '';
  6333. // Add command to history list
  6334. if (v.length > 0) {
  6335. // Move this command to the top if it already exists
  6336. var j = consoleHistory.indexOf(v);
  6337. if (j >= 0) { consoleHistory.splice(j, 1); }
  6338. consoleHistory.unshift(v);
  6339. consoleHistory.splice(10);
  6340. }
  6341. }
  6342. // Handle Mesh Agent console data
  6343. function p15consoleReceive(node, data, source) {
  6344. if (node === 'serverconsole') {
  6345. // Server console data
  6346. data = '<div>' + EscapeHtml(data) + '</div>'
  6347. consoleServerText += data;
  6348. if (consoleNode == 'server') {
  6349. Q('p15agentConsoleText').innerHTML += data;
  6350. Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight;
  6351. }
  6352. } else {
  6353. // Agent console data
  6354. if (source == 'MQTT') { data = '<div style=color:red>' + "MQTT" + '&gt; ' + EscapeHtml(data) + '<br/></div>'; } else { data = '<div>' + EscapeHtml(data) + '</div>' }
  6355. if (node.consoleText == null) { node.consoleText = data; } else { node.consoleText += data; }
  6356. if (consoleNode == node) {
  6357. Q('p15agentConsoleText').innerHTML += data;
  6358. Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight;
  6359. }
  6360. }
  6361. }
  6362. // Save console text to file
  6363. function p15downloadConsoleText() {
  6364. saveAs(new Blob([Q('p15agentConsoleText').innerText], { type: 'application/octet-stream' }), "console.txt");
  6365. }
  6366. // Called then user presses the "Change Core" button
  6367. function p15uploadCore(e) {
  6368. if (xxdialogMode) return;
  6369. if (e.shiftKey == true) { meshserver.send({ action: 'uploadagentcore', nodeid: consoleNode._id, type: 'default' }); } // Upload default core
  6370. else if (e.altKey == true) { meshserver.send({ action: 'uploadagentcore', nodeid: consoleNode._id, type: 'clear' }); } // Clear the core
  6371. else if (e.ctrlKey == true) { p15uploadCore2(); } // Upload the core from a file
  6372. else {
  6373. var htmlValue = '<select id=d3coreMode style=width:230px>' +
  6374. '<option value=1>' + "Upload default server core" + '</option>' +
  6375. '<option value=2>' + "Clear the core" + '</option>' +
  6376. '<option value=3>' + "Upload a core file" + '</option>' +
  6377. '<option value=4>' + "Soft disconnect agent" + '</option>' +
  6378. '<option value=5>' + "Hard disconnect agent" + '</option>' +
  6379. '<option value=6>' + "Upload recovery core" + '</option>' +
  6380. '<option value=7>' + "Upload tiny core" + '</option>' +
  6381. '<option value=8>' + "Restart agent service" + '</option>' +
  6382. '<option value=9>' + "Force agent update" + '</option></select>';
  6383. setDialogMode(2, "Perform Agent Action", 3, p15uploadCoreEx, addHtmlValue("Action", htmlValue));
  6384. }
  6385. }
  6386. function p15uploadCoreEx() {
  6387. if (Q('d3coreMode').value == 1) {
  6388. // Upload default core
  6389. meshserver.send({ action: 'uploadagentcore', nodeid: consoleNode._id, type: 'default' });
  6390. } else if (Q('d3coreMode').value == 2) {
  6391. // Clear the core
  6392. meshserver.send({ action: 'uploadagentcore', nodeid: consoleNode._id, type: 'clear' });
  6393. } else if (Q('d3coreMode').value == 3) {
  6394. // Upload file as core
  6395. p15uploadCore2();
  6396. } else if (Q('d3coreMode').value == 4) {
  6397. // Soft disconnect the mesh agent
  6398. meshserver.send({ action: 'agentdisconnect', nodeid: consoleNode._id, disconnectMode: 1 });
  6399. } else if (Q('d3coreMode').value == 5) {
  6400. // Hard disconnect the mesh agent
  6401. meshserver.send({ action: 'agentdisconnect', nodeid: consoleNode._id, disconnectMode: 2 });
  6402. } else if (Q('d3coreMode').value == 6) {
  6403. // Upload a recovery core
  6404. meshserver.send({ action: 'uploadagentcore', nodeid: consoleNode._id, type: 'recovery' });
  6405. } else if (Q('d3coreMode').value == 7) {
  6406. // Upload a tiny core
  6407. meshserver.send({ action: 'uploadagentcore', nodeid: consoleNode._id, type: 'tiny' });
  6408. } else if (Q('d3coreMode').value == 8) {
  6409. // Restart MeshAgent service
  6410. meshserver.send({ action: 'msg', type: 'console', nodeid: consoleNode._id, value:'service restart' });
  6411. } else if (Q('d3coreMode').value == 9) {
  6412. // Update mesh agent
  6413. meshserver.send({ action: 'updateAgents', nodeids: [consoleNode._id] });
  6414. }
  6415. }
  6416. // Called then user opts to upload a file as core
  6417. function p15uploadCore2() {
  6418. if (xxdialogMode) return;
  6419. Q('d3localmodeform').action = 'uploadmeshcorefile.ashx';
  6420. Q('d3auth').value = authCookie;
  6421. Q('d3attrib').value = currentNode._id;
  6422. setDialogMode(4, "Upload Mesh Agent Core", 3, p15uploadCoreEx2);
  6423. d3init();
  6424. }
  6425. function p15uploadCoreEx2() {
  6426. var mode = Q('d3uploadMode').value;
  6427. if (mode == 1) {
  6428. // Upload local mesh agent core
  6429. Q('d3submit').click();
  6430. } else {
  6431. // Upload server mesh agent code
  6432. var files = d3getFileSel();
  6433. if (files.length == 1) { meshserver.send({ action: 'uploadagentcore', nodeid: consoleNode._id, type: 'custom', path: d3filetreelocation.join('/') + '/' + files[0] }); }
  6434. }
  6435. }
  6436. //
  6437. // MY MESHS
  6438. //
  6439. var currentMesh;
  6440. function p20updateMesh() {
  6441. if (currentMesh == null) return;
  6442. QH('p20meshName', EscapeHtml(currentMesh.name));
  6443. var meshtype = format("Unknown #{0}", currentMesh.mtype);
  6444. var meshrights = GetMeshRights(currentMesh);
  6445. if (currentMesh.mtype == 1) meshtype = "Intel&reg; AMT only, no agent";
  6446. if (currentMesh.mtype == 2) meshtype = "Managed using a software agent";
  6447. if (currentMesh.mtype == 3) { if (currentMesh.relayid == null) { meshtype = "Local devices, no agent"; } else { meshtype = "No agent devices relayed thru agent"; } }
  6448. if (currentMesh.mtype == 4) { if (currentMesh.relayid == null) { meshtype = "IP-KVM device"; } else { meshtype = "IP-KVM device relayed thru agent"; } if (currentMesh.kvm.model == 1) { meshtype += ', ' + 'Raritan KX III'; } }
  6449. var x = '';
  6450. x += addHtmlValue("Name", addLinkConditional(EscapeHtml(currentMesh.name), 'p20editmesh(1)', (meshrights & 1) != 0));
  6451. x += addHtmlValue("Description", addLinkConditional(((currentMesh.desc && currentMesh.desc != '') ? EscapeHtml(currentMesh.desc) : ('<i>' + "None" + '</i>')), 'p20editmesh(2)', (meshrights & 1) != 0));
  6452. x += addHtmlValue("Type", meshtype);
  6453. //x += addHtmlValue('Identifier', currentMesh._id.split('/')[2]);
  6454. // Display the relay device if applicable
  6455. if (((currentMesh.mtype == 3) || (currentMesh.mtype == 4)) && (currentMesh.relayid != null)) {
  6456. var relayName = '<i>' + "Unknown" + '</i>';
  6457. var relayNode = getNodeFromId(currentMesh.relayid);
  6458. if (relayNode != null) { relayName = EscapeHtml(relayNode.name); }
  6459. x += addHtmlValue("Relay Device", addLinkConditional(relayName, 'p20editmeshrelay()', (meshrights & 1) != 0));
  6460. }
  6461. // Display IP-KVM information if needed
  6462. if (currentMesh.mtype == 4) {
  6463. x += addHtmlValue("Hostname", currentMesh.kvm.host);
  6464. x += addHtmlValue("Username", currentMesh.kvm.user);
  6465. }
  6466. x += '<br><input type=button value="' + "Notes" + '" onclick=showNotes(false,"' + encodeURIComponent(currentMesh._id) + '") />';
  6467. x += '<br style=clear:both><br>';
  6468. var currentMeshLinks = currentMesh.links[userinfo._id];
  6469. if (currentMeshLinks && ((currentMeshLinks.rights & 2) != 0)) { x += '<div style=margin-bottom:6px;float:left;margin-right:10px><a onclick=p20showAddMeshUserDialog() style=cursor:pointer><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add User" + '</a></div>'; }
  6470. if (navigator.userAgent.toLowerCase().indexOf('android') >= 0) {
  6471. x += '<div style=margin-bottom:6px;float:left;margin-right:10px><a onclick=p20installAndroidDialog() style=cursor:pointer><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Install on this device" + '</a></div>';
  6472. }
  6473. /*
  6474. if ((meshrights & 4) != 0) {
  6475. if (currentMesh.mtype == 1) {
  6476. x += '<a onclick=addCiraDeviceToMesh("' + currentMesh._id + '") style=cursor:pointer;margin-right:10px><img src=images/icon-installmesh.png border=0 height=12 width=12> Install CIRA</a>';
  6477. x += '<a onclick=addDeviceToMesh("' + currentMesh._id + '") style=cursor:pointer;margin-right:10px><img src=images/icon-installmesh.png border=0 height=12 width=12> Install local</a>';
  6478. }
  6479. if (currentMesh.mtype == 2) {
  6480. x += '<a onclick=addAgentToMesh("' + currentMesh._id + '") style=cursor:pointer;margin-right:10px><img src=images/icon-addnew.png border=0 height=12 width=12> Install</a>';
  6481. }
  6482. }
  6483. */
  6484. /*
  6485. function getMeshActions(mesh, meshrights) {
  6486. if ((meshrights & 4) == 0) return '';
  6487. var r = '';
  6488. if (mesh.mtype == 1) {
  6489. r += ' <a style=cursor:pointer;font-size:10px onclick=addCiraDeviceToMesh("' + mesh._id + '")>Add CIRA</a>';
  6490. r += ' <a style=cursor:pointer;font-size:10px onclick=addDeviceToMesh("' + mesh._id + '")>Add Local</a>';
  6491. }
  6492. if (mesh.mtype == 2) {
  6493. r += ' <a style=cursor:pointer;font-size:10px onclick=addAgentToMesh("' + mesh._id + '")>Add Agent</a>';
  6494. }
  6495. return r;
  6496. }
  6497. */
  6498. x += '<table style="color:black;background-color:#EEE;border-color:#AAA;border-width:1px;border-style:solid;border-collapse:collapse" border=0 cellpadding=2 cellspacing=0 width=100%><tbody><tr style=background-color:#AAAAAA;font-weight:bold><th scope=col style=text-align:left;width:430px>' + "User Authorizations" + '</th></tr>';
  6499. // Sort the users for this mesh
  6500. var count = 1, sortedusers = [];
  6501. for (var i in currentMesh.links) {
  6502. var uname = i.split('/')[2];
  6503. if (currentMesh.links[i].name) { uname = currentMesh.links[i].name; }
  6504. if (i == userinfo._id) { uname = userinfo.name; }
  6505. if ((usergroups != null) && (usergroups[i] != null)) { uname = usergroups[i].name; }
  6506. sortedusers.push({ id: i, name: uname, rights: currentMesh.links[i].rights });
  6507. }
  6508. sortedusers.sort(function (a, b) { if (a.name > b.name) return 1; if (a.name < b.name) return -1; return 0; });
  6509. // Display all users for this mesh
  6510. for (var i in sortedusers) {
  6511. var trash = '', rights = "Partial Rights", r = sortedusers[i].rights, icon = 2;
  6512. if (r == 0xFFFFFFFF) rights = "Full Administrator"; else if (r == 0) rights = "No Rights";
  6513. if ((i != userinfo._id) && (meshrights == 0xFFFFFFFF || (((meshrights & 2) != 0)))) { trash = '<a onclick=p20deleteUser(event,"' + encodeURIComponent(sortedusers[i].id) + '") style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
  6514. if (sortedusers[i].id.startsWith('ugrp/')) { icon = 4; }
  6515. x += '<tr onclick=p20viewuser("' + encodeURIComponent(sortedusers[i].id) + '") style=height:32px;cursor:pointer' + (((count % 2) == 0) ? ';background-color:#DDD' : '') + '><td>';
  6516. x += '<div style=float:right>' + trash + '</div><div style=float:right;padding-right:4px>' + rights + '</div><div class=m' + icon + '></div><div>&nbsp;' + EscapeHtml(decodeURIComponent(sortedusers[i].name)) + '<div></div></div>';
  6517. x += '</td></tr>';
  6518. ++count;
  6519. }
  6520. x += '</tbody></table>';
  6521. // If we are full administrator on this mesh, allow deletion of the mesh
  6522. if (meshrights == 0xFFFFFFFF) { x += '<div style=font-size:small;text-align:right;margin-top:6px><span><a onclick=p20showDeleteMeshDialog() style=cursor:pointer>' + "Delete Group" + '</a></span></div>'; }
  6523. QH('p20info', x);
  6524. }
  6525. function p20showDeleteMeshDialog() {
  6526. if (xxdialogMode) return false;
  6527. var x = format("Are you sure you want to delete group {0}? Deleting the device group will also delete all information about devices within this group.", EscapeHtml(currentMesh.name)) + '<br /><br />';
  6528. x += '<label><input id=p20check type=checkbox onchange=p20validateDeleteMeshDialog() />' + "Confirm" + '</label>';
  6529. setDialogMode(2, "Delete Group", 3, p20showDeleteMeshDialogEx, x);
  6530. p20validateDeleteMeshDialog();
  6531. return false;
  6532. }
  6533. function p20validateDeleteMeshDialog() {
  6534. QE('idx_dlgOkButton', Q('p20check').checked);
  6535. }
  6536. function p20showDeleteMeshDialogEx(buttons, tag) {
  6537. meshserver.send({ action: 'deletemesh', meshid: currentMesh._id, meshname: currentMesh.name });
  6538. }
  6539. function p20editmeshrelay() {
  6540. if (xxdialogMode) return;
  6541. // Look for all relay devices
  6542. var relayDevices = [];
  6543. if ((features & 2) == 0) { for (var i in nodes) { var node = nodes[i]; if ((node.mtype == 2) && (node.agent != null) && (GetNodeRights(node) == 0xFFFFFFFF)) { relayDevices.push(node); } } }
  6544. relayDevices.sort(nameSort);
  6545. if (relayDevices.length == 0) {
  6546. // Relay relay devices available
  6547. setDialogMode(2, "Edit Device Group", 1, null, "No relay devices available.");
  6548. } else {
  6549. var relayDevices2 = [];
  6550. for (var i in relayDevices) { relayDevices2.push('<option value="' + (relayDevices[i]._id + '"' + ((currentMesh.relayid == relayDevices[i]._id) ? ' selected' : '')) + '>' + EscapeHtml(relayDevices[i].name) + '</option>'); }
  6551. var x = addHtmlValue("Relay Device", '<div style=width:170px><select id=d2devrelay style=width:100%>' + relayDevices2.join('') + '</select></div>');
  6552. setDialogMode(2, "Edit Device Group", 3, p20editmeshrelayEx, x);
  6553. }
  6554. }
  6555. function p20editmeshrelayEx() {
  6556. meshserver.send({ action: 'editmesh', meshid: currentMesh._id, relayid: Q('d2devrelay').value });
  6557. }
  6558. function p20editmesh(focus) {
  6559. if (xxdialogMode) return;
  6560. var x = addHtmlValue("Name", '<input id=dp20meshname style=width:170px maxlength=32 onchange=p20editmeshValidate() onkeyup=p20editmeshValidate() />');
  6561. x += addHtmlValue("Description", '<input id=dp20meshdesc style=width:170px maxlength=1024 onkeyup=p20editmeshValidate() />');
  6562. setDialogMode(2, "Edit Device Group", 3, p20editmeshEx, x);
  6563. Q('dp20meshname').value = currentMesh.name;
  6564. if (currentMesh.desc) Q('dp20meshdesc').value = currentMesh.desc;
  6565. p20editmeshValidate();
  6566. if (focus == 2) { Q('dp20meshdesc').focus(); } else { Q('dp20meshname').focus(); }
  6567. }
  6568. function p20editmeshEx() {
  6569. meshserver.send({ action: 'editmesh', meshid: currentMesh._id, meshname: Q('dp20meshname').value, desc: Q('dp20meshdesc').value });
  6570. }
  6571. function p20editmeshValidate() {
  6572. QE('idx_dlgOkButton', Q('dp20meshname').value.length > 0);
  6573. }
  6574. function p20installAndroidDialog() {
  6575. if (xxdialogMode) return;
  6576. var x = '<div style=text-align:center><p>' + "Install the MeshCentral Agent on your Android device. Once installed, click the pairing link to connect your device to this server." + '</p>';
  6577. x += '<p><a rel=\"noreferrer noopener\" target=_blank href=\"https://play.google.com/store/apps/details?id=com.meshcentral.agent2\"><img style=cursor:pointer src=\"images/google-play-140.png\" width=140 srcset=\"images/google-play-280.png 2x\" /></a></p>';
  6578. x += '<p><a rel=\"noreferrer noopener\" target=_blank href=\"https://www.amazon.co.uk/gp/product/B097Z4Q7SK/\"><img style=cursor:pointer src=\"images/amazon-appstore-140.png\" width=140 srcset=\"images/amazon-appstore-280.png 2x\" /></a></p>';
  6579. x += '<p><a rel=\"noreferrer noopener\" target=_blank href="meshagents?id=14' + (urlargs.key?('&key=' + urlargs.key):'') + '" title="' + "APK version of the MeshAgent" + '">' + "Android APK" + '</a></p>';
  6580. x += '<p><a href="' + serverinfo.magenturl + ',' + serverinfo.agentCertHash + ',' + currentMesh._id.split('/')[2] + '"><b>' + "Device Pairing Link" + '</b></a></p></div>';
  6581. setDialogMode(2, "Android Installation", 1, null, x);
  6582. }
  6583. function p20showAddMeshUserDialog() {
  6584. if (xxdialogMode) return;
  6585. var x = addHtmlValue('User ID', '<input id=dp20username style=width:170px maxlength=256 onchange=p20validateAddMeshUserDialog() onkeyup=p20validateAddMeshUserDialog() />');
  6586. x += '<div style="border:2px groove gray;background-color:white;max-height:120px;overflow-y:scroll">';
  6587. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20fulladmin>' + "Full Administrator" + '</label><br>';
  6588. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20editmesh>' + "Edit Device Group" + '</label><br>';
  6589. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20manageusers>' + "Manage Device Group Users" + '</label><br>';
  6590. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20managecomputers>' + "Manage Device Group Computers" + '</label><br>';
  6591. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20remotecontrol>' + "Remote Control" + '</label><br>';
  6592. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20remoteview style=margin-left:12px>' + "Remote View Only" + '</label><br>';
  6593. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20remotelimitedinput style=margin-left:12px>' + "Limited Input Only" + '</label><br>';
  6594. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20noterminal style=margin-left:12px>' + "No Terminal Access" + '</label><br>';
  6595. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20nofiles style=margin-left:12px>' + "No File Access" + '</label><br>';
  6596. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20noamt style=margin-left:12px>' + "No Intel&reg; AMT" + '</label><br>';
  6597. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20meshagentconsole>' + "Mesh Agent Console" + '</label><br>';
  6598. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20meshserverfiles>' + "Server Files" + '</label><br>';
  6599. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20wakedevices>' + "Wake Devices" + '</label><br>';
  6600. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20editnotes>' + "Edit Device Notes" + '</label><br>';
  6601. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20limitevents>' + "Show Only Own Events" + '</label><br>';
  6602. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20chatnotify>' + "Chat & Notify" + '</label><br>';
  6603. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20uninstall>' + "Uninstall Agent" + '</label><br>';
  6604. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20commands>' + "Remote Commands" + '</label><br>';
  6605. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20resetoff>' + "Reset / Power Off" + '</label><br>';
  6606. x += '</div>';
  6607. setDialogMode(2, "Add User to Device Group", 3, p20showAddMeshUserDialogEx, x);
  6608. p20validateAddMeshUserDialog();
  6609. Q('dp20username').focus();
  6610. }
  6611. function p20validateAddMeshUserDialog() {
  6612. var meshrights = GetMeshRights(currentMesh);
  6613. var nc = !Q('p20fulladmin').checked;
  6614. QE('p20fulladmin', meshrights == 0xFFFFFFFF);
  6615. QE('p20editmesh', nc && (meshrights == 0xFFFFFFFF));
  6616. QE('p20manageusers', nc);
  6617. QE('p20managecomputers', nc);
  6618. QE('p20remotecontrol', nc);
  6619. QE('p20meshagentconsole', nc);
  6620. QE('p20meshserverfiles', nc);
  6621. QE('p20wakedevices', nc);
  6622. QE('p20editnotes', nc);
  6623. QE('p20limitevents', nc);
  6624. QE('p20remoteview', nc && Q('p20remotecontrol').checked);
  6625. QE('p20remotelimitedinput', nc && Q('p20remotecontrol').checked && !Q('p20remoteview').checked);
  6626. QE('p20noterminal', nc && Q('p20remotecontrol').checked);
  6627. QE('p20nofiles', nc && Q('p20remotecontrol').checked);
  6628. QE('p20noamt', nc && Q('p20remotecontrol').checked);
  6629. QE('p20chatnotify', nc);
  6630. QE('p20uninstall', nc);
  6631. QE('p20commands', nc);
  6632. QE('p20resetoff', nc);
  6633. }
  6634. function p20showAddMeshUserDialogEx() {
  6635. var meshadmin = 0;
  6636. if (Q('p20fulladmin').checked == true) { meshadmin = 0xFFFFFFFF; } else {
  6637. if (Q('p20editmesh').checked == true) meshadmin += 1;
  6638. if (Q('p20manageusers').checked == true) meshadmin += 2;
  6639. if (Q('p20managecomputers').checked == true) meshadmin += 4;
  6640. if (Q('p20remotecontrol').checked == true) meshadmin += 8;
  6641. if (Q('p20meshagentconsole').checked == true) meshadmin += 16;
  6642. if (Q('p20meshserverfiles').checked == true) meshadmin += 32;
  6643. if (Q('p20wakedevices').checked == true) meshadmin += 64;
  6644. if (Q('p20editnotes').checked == true) meshadmin += 128;
  6645. if (Q('p20remoteview').checked == true) meshadmin += 256;
  6646. if (Q('p20noterminal').checked == true) meshadmin += 512;
  6647. if (Q('p20nofiles').checked == true) meshadmin += 1024;
  6648. if (Q('p20noamt').checked == true) meshadmin += 2048;
  6649. if (Q('p20remotelimitedinput').checked == true) meshadmin += 4096;
  6650. if (Q('p20limitevents').checked == true) meshadmin += 8192;
  6651. if (Q('p20chatnotify').checked == true) meshadmin += 16384;
  6652. if (Q('p20uninstall').checked == true) meshadmin += 32768;
  6653. if (Q('p20commands').checked == true) meshadmin += 131072;
  6654. if (Q('p20resetoff').checked == true) meshadmin += 262144;
  6655. }
  6656. var users = Q('dp20username').value.split(','), users2 = [];
  6657. for (var i in users) { users2.push(users[i].trim()); }
  6658. meshserver.send({ action: 'addmeshuser', meshid: currentMesh._id, meshname: currentMesh.name, usernames: users2, meshadmin: meshadmin });
  6659. }
  6660. function p20viewuser(userid) {
  6661. if (xxdialogMode) return;
  6662. userid = decodeURIComponent(userid);
  6663. var r = [], cmeshrights = GetMeshRights(currentMesh), meshrights = GetMeshRights(currentMesh, userid);
  6664. if (meshrights == 0xFFFFFFFF) r.push("Full Administrator"); else {
  6665. if ((meshrights & 1) != 0) r.push("Edit Device Group");
  6666. if ((meshrights & 2) != 0) r.push("Manage Device Group Users");
  6667. if ((meshrights & 4) != 0) r.push("Manage Device Group Computers");
  6668. if ((meshrights & 8) != 0) r.push("Remote Control");
  6669. if ((meshrights & 16) != 0) r.push("Agent Console");
  6670. if ((meshrights & 32) != 0) r.push("Server Files");
  6671. if ((meshrights & 64) != 0) r.push("Wake Devices");
  6672. if ((meshrights & 128) != 0) r.push("Edit Notes");
  6673. if ((meshrights & 256) != 0) r.push("Remote View Only");
  6674. if ((meshrights & 512) != 0) r.push("No Terminal");
  6675. if ((meshrights & 1024) != 0) r.push("No Files");
  6676. if ((meshrights & 2048) != 0) r.push("No Intel&reg; AMT");
  6677. if (((meshrights & 8) != 0) && ((meshrights & 4096) != 0) && ((meshrights & 256) == 0)) r.push("Limited Input");
  6678. if ((meshrights & 8192) != 0) r.push("Self Events Only");
  6679. if ((meshrights & 16384) != 0) r.push("Chat & Notify");
  6680. if ((meshrights & 32768) != 0) r.push("Uninstall");
  6681. if ((meshrights & 131072) != 0) r.push("Commands");
  6682. if ((meshrights & 262144) != 0) r.push("Reset/Off");
  6683. }
  6684. if (r.length == 0) { r.push("No Rights"); }
  6685. var buttons = 1, uname = userid.split('/')[2];
  6686. if (currentMesh.links[userid].name) { uname = currentMesh.links[userid].name; }
  6687. var x = addHtmlValue("User Name", EscapeHtml(uname));
  6688. if (uname != userid.split('/')[2]) { x += addHtmlValue("User ID", EscapeHtml(userid.split('/')[2])); }
  6689. x += addHtmlValue("Permissions", r.join(", "));
  6690. if (((userinfo._id) != userid) && (cmeshrights == 0xFFFFFFFF || (((cmeshrights & 2) != 0) && (meshrights != 0xFFFFFFFF)))) buttons += 4;
  6691. setDialogMode(2, "Device Group User", buttons, p20viewuserEx, x, userid);
  6692. }
  6693. function p20viewuserEx(button, userid) {
  6694. if (button != 2) return;
  6695. var uname = userid.split('/')[2];
  6696. if (users && users[userid]) { uname = users[userid].name; }
  6697. if (usergroups && usergroups[userid]) { uname = usergroups[userid].name; }
  6698. if (userinfo._id == userid) { uname = userinfo.name; }
  6699. setDialogMode(2, "Remote Mesh User", 3, p20viewuserEx2, format("Confirm removal of user {0}?", uname), userid);
  6700. }
  6701. function p20deleteUser(e, userid) { haltEvent(e); p20viewuserEx(2, decodeURIComponent(userid)); }
  6702. function p20viewuserEx2(button, userid) { meshserver.send({ action: 'removemeshuser', meshid: currentMesh._id, meshname: currentMesh.name, userid: userid }); }
  6703. //
  6704. // NOTIFICATIONS
  6705. //
  6706. var notifications = [];
  6707. // Toggle showing notifications
  6708. function clickNotificationIcon(show) {
  6709. //addNotification({ icon:0, text:'test' });
  6710. if (show == true) { QV('notifiyBox', true); } else if (show == false) { QV('notifiyBox', false); } else { QV('notifiyBox', QS('notifiyBox')['display'] == 'none'); }
  6711. drawNotifications();
  6712. }
  6713. // Set the notification count on the upper right oft he screen
  6714. function setNotificationCount(c) {
  6715. if (parseInt(Q('notificationCount').innerHTML) == c) return; // If the count did not change, exit now.
  6716. QH('notificationCount2', c);
  6717. QV('notificationCount', c > 0);
  6718. }
  6719. // Refresh the notification box
  6720. function drawNotifications() {
  6721. var notifySettings = getstore('notifications', 0);
  6722. var r = '';
  6723. if (notifications.length == 0) {
  6724. r = '<div style=margin:5px>' + "There are currently no notifications" + '</div>';
  6725. } else {
  6726. for (var i in notifications) {
  6727. var n = notifications[i], t = '', d = new Date(n.time), icon = 0;
  6728. if (n.title != null) { t = '<b>' + EscapeHtml(n.title) + '</b>: ' }
  6729. if (n.nodeid != null) {
  6730. var node = getNodeFromId(n.nodeid);
  6731. if (node != null) {
  6732. icon = node.icon;
  6733. if (notifySettings & 16) { t = '<b>' + EscapeHtml(meshes[node.meshid].name) + ' / ' + EscapeHtml(node.name) + '</b>: '; } else { t = '<b>' + EscapeHtml(node.name) + '</b>: '; } // Display with or without group name
  6734. }
  6735. }
  6736. r += '<div title="' + format("Occured at {0}", printDateTime(d)) + '" id="notifyx' + n.id + '" class=notification style="cursor:pointer;border-top:1px solid ' + ((r == '') ? 'transparent' : 'orange') + '">';
  6737. if (icon) { r += '<div class=j' + icon + ' onclick="notificationSelected(' + n.id + ')" style=margin:5px;float:left></div>'; }
  6738. r += '<div onclick="notificationDelete(' + n.id + ')" class=unselectable title="' + "Clear this notification" + '" style=margin:5px;float:right;color:orange><b>X</b></div><div onclick="notificationSelected(' + n.id + ')" style=margin:5px>' + t + EscapeHtml(n.text) + '</div><div style=margin-left:5px;margin-bottom:5px;color:gray;font-size:10px>' + printDateTime(d) + '</div></div>';
  6739. }
  6740. }
  6741. var deleteall = '';
  6742. if (notifications.length > 1) { deleteall = '<div id="notifyRemoveAll" onclick="deleteAllNotifications()" style="cursor:pointer;border-top:1px solid orange;margin:5px;color:orange;text-align:right;padding-right:3px">' + "Clear all" + '</div>'; }
  6743. QH('notifiyBox', '<div class=customScroll style="max-height:170px;overflow-y:auto;margin:5px">' + r + deleteall + '</div>');
  6744. }
  6745. // A notification was selected
  6746. function notificationSelected(id, del) {
  6747. var j = -1;
  6748. for (var i in notifications) { if (notifications[i].id == id) { j = i; } }
  6749. if (j != -1) {
  6750. notificationSelectedEx(notifications[j], id);
  6751. if (del && notifications[j]) {
  6752. if (notifications[j].notification) { notifications[j].notification.close(); delete notifications[j].notification; }
  6753. notificationDelete(id);
  6754. }
  6755. }
  6756. }
  6757. function notificationSelectedEx(n, id) {
  6758. if (n.nodeid != null) {
  6759. if (n.tag == 'desktop') gotoDevice(n.nodeid, 12); // Desktop
  6760. else if (n.tag == 'terminal') gotoDevice(n.nodeid, 11); // Terminal
  6761. else if (n.tag == 'files') gotoDevice(n.nodeid, 13); // Files
  6762. else if (n.tag == 'intelamt') gotoDevice(n.nodeid, 14); // Intel AMT
  6763. else if (n.tag == 'console') gotoDevice(n.nodeid, 15); // Files
  6764. else gotoDevice(n.nodeid, 10); // General
  6765. } else {
  6766. if ((n.tag == 'backupcodes') && !xxdialogMode) { account_manageOtp(0); notificationDelete(id); } // 2FA backup codes
  6767. else if ((n.tag != null) && n.tag.startsWith('meshmessenger/')) {
  6768. safeNewWindow('/messenger?id=' + n.tag + '&title=' + encodeURIComponentEx(n.username), n.tag.split('/')[2]);
  6769. notificationDelete(id);
  6770. } else if (n.url != null) {
  6771. safeNewWindow(n.url);
  6772. notificationDelete(id);
  6773. }
  6774. }
  6775. }
  6776. // Remove one notification
  6777. function notificationDelete(id) {
  6778. var j = -1, e = Q('notifyx' + id);
  6779. if (e != null) {
  6780. for (var i in notifications) { if (notifications[i].id == id) { j = i; } }
  6781. if (j != -1) {
  6782. meshserver.send({ action: 'intersession', subaction: 'removeNotify', id: id }); // Remove the notification in other sessions of the same user.
  6783. if (notifications[j].notification) { notifications[j].notification.close(); delete notifications[j].notification; }
  6784. notifications.splice(j, 1);
  6785. e.parentNode.removeChild(e);
  6786. setNotificationCount(notifications.length);
  6787. if (notifications.length == 0) { QV('notifiyBox', false); }
  6788. if (notifications.length == 1) { QV('notifyRemoveAll', false); }
  6789. if ((notifications.length > 0) && (j == 0)) {
  6790. var n = notifications[0];
  6791. QS('notifyx' + n.id)['border-top'] = '1px solid transparent';
  6792. }
  6793. }
  6794. }
  6795. }
  6796. // Add a new notification and play the notification sound
  6797. function addNotification(n) {
  6798. // Perform message translation
  6799. var translatedTitles = [
  6800. null,
  6801. "New Account", // 1
  6802. "Server Limit",
  6803. "Security Warning",
  6804. "Account Settings",
  6805. "Device Group",
  6806. "Invite Codes"
  6807. ];
  6808. var translatedMessages = [
  6809. null,
  6810. "Permission denied", // 1
  6811. "Invalid username",
  6812. "Invalid password",
  6813. "Invalid email",
  6814. "Invalid domain",
  6815. "Invalid site permissions",
  6816. "User already exists",
  6817. "Unable to add user in this mode",
  6818. "Validation exception",
  6819. "Account limit reached.", // 10
  6820. "Chat Request, Click here to accept.",
  6821. "There has been {0} failed login attempts on this account since the last login.",
  6822. "Failed to change email address, another account already using: {0}.",
  6823. "Email sent.",
  6824. "User {0} not found.",
  6825. "Users {0} not found.",
  6826. "Error, unable to change to previously used password.",
  6827. "Error, unable to change to commonly used password.",
  6828. "Error, password not changed.",
  6829. "Password changed.", // 20
  6830. "Current password not correct.",
  6831. "Error, invite code \"{0}\" already in use.",
  6832. "SMS gateway not enabled",
  6833. "No user management rights",
  6834. "Invalid SMS message",
  6835. "No phone number for this user",
  6836. "SMS succesfuly sent.",
  6837. "SMS error",
  6838. "SMS error: {0}",
  6839. "Email domain \"{0}\" is not allowed. Only ({1}) are allowed" // 30
  6840. ];
  6841. if (typeof n.titleid == 'number') { try { n.title = translatedTitles[n.titleid]; } catch (ex) { } }
  6842. if (typeof n.msgid == 'number') { try { n.text = translatedMessages[n.msgid]; if (Array.isArray(n.args)) { n.text = format(n.text, n.args[0], n.args[1], n.args[2], n.args[3], n.args[4], n.args[5]); } } catch (ex) { } }
  6843. // Show notification within the web page.
  6844. if (n.time == null) { n.time = Date.now(); }
  6845. if (n.id == null) { n.id = Math.random(); }
  6846. notifications.unshift(n);
  6847. setNotificationCount(notifications.length);
  6848. clickNotificationIcon(true);
  6849. var notifySettings = getstore('notifications', 0);
  6850. if (notifySettings & 1) { Q('chimes').play(); }
  6851. // If web notifications are granted, use it.
  6852. var notification = null;
  6853. if (Notification && (Notification.permission == 'granted')) {
  6854. var text = n.text.split('&reg;').join('').split('<b>').join('').split('</b>').join('').split('<br />').join('\r\n'); // Clean up any HTML codes
  6855. if (n.nodeid) {
  6856. var node = getNodeFromId(n.nodeid);
  6857. if (node) {
  6858. if (notifySettings & 16) { // Notify with group name
  6859. notification = new Notification(decodeURIComponent('{{{extitle}}}') + ' - ' + meshes[node.meshid].name + ' - ' + node.name, { tag: n.tag, body: text, icon: '/images/notify/icons128-' + node.icon + '.png' });
  6860. } else {
  6861. notification = new Notification(decodeURIComponent('{{{extitle}}}') + ' - ' + node.name, { tag: n.tag, body: text, icon: '/images/notify/icons128-' + node.icon + '.png' });
  6862. }
  6863. }
  6864. } else {
  6865. if (n.icon == null) { n.icon = 0; }
  6866. var title = n.title;
  6867. if (title == null) { title = ''; } else { title = ' - ' + n.title; }
  6868. notification = new Notification(decodeURIComponent('{{{extitle}}}') + title, { tag: n.tag, body: text, icon: '/images/notify/icons128-' + n.icon + '.png' });
  6869. }
  6870. notification.id = n.id;
  6871. notification.xtag = n.tag;
  6872. notification.url = n.url;
  6873. notification.nodeid = n.nodeid;
  6874. notification.username = n.username;
  6875. notification.onclick = function (e) { notificationSelected(e.target.id, true); }
  6876. n.notification = notification;
  6877. }
  6878. // If the notification has a max time, setup the timer here.
  6879. if ((typeof n.maxtime == 'number') && (n.maxtime > 0)) { var trigger = function notifyRemoveTrigger() { notificationDelete(notifyRemoveTrigger.xid); }; trigger.xid = n.id; setTimeout(trigger, n.maxtime * 1000); }
  6880. }
  6881. // Remove all notifications
  6882. function deleteAllNotifications() {
  6883. notifications = [];
  6884. setNotificationCount(0);
  6885. drawNotifications();
  6886. QV('notifiyBox', false);
  6887. }
  6888. //
  6889. // PANELS
  6890. //
  6891. var xxcurrentView = -1;
  6892. function go(x) {
  6893. setSessionActivity();
  6894. if (xxdialogMode || xxcurrentView == x) return;
  6895. updateFooterMenu();
  6896. setDialogMode(0);
  6897. // Edit this line when adding a new screen
  6898. for (var i = 0; i < 32; i++) { QV('p' + i, i == x); }
  6899. xxcurrentView = x;
  6900. updateCurrentUrl();
  6901. }
  6902. // Change the URL
  6903. function updateCurrentUrl() {
  6904. if (((features & 0x10000000) == 0) && (xxcurrentView > 0)) {
  6905. var urlviewmode = '';
  6906. if ((xxcurrentView >= 10) && (xxcurrentView <= 19)) { // Device Link
  6907. if (currentNode != null) { urlviewmode = '?viewmode=' + xxcurrentView + '&gotonode=' + currentNode._id.split('/')[2] + ((currentDevicePanel > 0)?('&panel=' + currentDevicePanel):''); }
  6908. } else if ((xxcurrentView >= 20) && (xxcurrentView <= 29)) { // Device Group Link
  6909. if (currentMesh != null) { urlviewmode = '?viewmode=' + xxcurrentView + '&gotomesh=' + currentMesh._id.split('/')[2]; }
  6910. } else if (xxcurrentView > 1) { urlviewmode = '?viewmode=' + xxcurrentView; }
  6911. for (var i in urlargs) { urlviewmode += (((urlviewmode == '') ? '?' : '&') + i + '=' + urlargs[i]); }
  6912. try { window.history.replaceState({}, document.title, window.location.pathname + urlviewmode); } catch (ex) { }
  6913. }
  6914. }
  6915. //
  6916. // POPUP DIALOG
  6917. //
  6918. // undefined = Hidden, 1 = Generic Message
  6919. var xxdialogMode;
  6920. var xxdialogFunc;
  6921. var xxdialogButtons;
  6922. var xxdialogTag;
  6923. // Display a dialog box
  6924. // Parameters: Dialog Mode (0 = none), Dialog Title, Buttons (1 = OK, 2 = Cancel, 3 = OK & Cancel), Call back function(0 = Cancel, 1 = OK), Dialog Content (Mode 2 only)
  6925. function setDialogMode(x, y, b, f, c, tag) {
  6926. setSessionActivity();
  6927. xxdialogMode = x;
  6928. xxdialogFunc = f;
  6929. xxdialogButtons = b;
  6930. xxdialogTag = tag;
  6931. QE('idx_dlgOkButton', true);
  6932. QV('idx_dlgOkButton', b & 1);
  6933. QV('idx_dlgCancelButton', b & 2);
  6934. QV('id_dialogclose', (b & 2) || (b & 8));
  6935. QV('idx_dlgDeleteButton', b & 4);
  6936. QV('idx_dlgButtonBar', b & 7);
  6937. if (y) QH('id_dialogtitle', y);
  6938. for (var i = 1; i < 24; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
  6939. QV('dialog', x);
  6940. if (c) { if (x == 2) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
  6941. }
  6942. function dialogclose(x) {
  6943. setSessionActivity();
  6944. var f = xxdialogFunc;
  6945. var b = xxdialogButtons;
  6946. var t = xxdialogTag;
  6947. setDialogMode();
  6948. if (((b & 8) || x) && f) f(x, t);
  6949. }
  6950. //
  6951. // Access Control Functions
  6952. // These must match server
  6953. //
  6954. // Remove user rights
  6955. function removeUserRights(rights, userid) {
  6956. if ((userid != userinfo._id) || (userinfo.removeRights == null)) return rights;
  6957. var add = 0, substract = 0;
  6958. if ((userinfo.removeRights & 0x00000008) != 0) { substract += 0x00000008; } // No Remote Control
  6959. if ((userinfo.removeRights & 0x00010000) != 0) { add += 0x00010000; } // No Desktop
  6960. if ((userinfo.removeRights & 0x00000100) != 0) { add += 0x00000100; } // Desktop View Only
  6961. if ((userinfo.removeRights & 0x00000200) != 0) { add += 0x00000200; } // No Terminal
  6962. if ((userinfo.removeRights & 0x00000400) != 0) { add += 0x00000400; } // No Files
  6963. if ((userinfo.removeRights & 0x00000010) != 0) { substract += 0x00000010; } // No Console
  6964. if ((userinfo.removeRights & 0x00008000) != 0) { substract += 0x00008000; } // No Uninstall
  6965. if ((userinfo.removeRights & 0x00020000) != 0) { substract += 0x00020000; } // No Remote Command
  6966. if ((userinfo.removeRights & 0x00000040) != 0) { substract += 0x00000040; } // No Wake
  6967. if ((userinfo.removeRights & 0x00040000) != 0) { substract += 0x00040000; } // No Reset/Off
  6968. if (rights != 0xFFFFFFFF) {
  6969. // If not administrator, add and subsctract restrictions
  6970. rights |= add;
  6971. rights &= (0xFFFFFFFF - substract);
  6972. } else {
  6973. // If administrator for a device group, start with permissions and add and subsctract restrictions
  6974. rights = 1 + 2 + 4 + 8 + 32 + 64 + 128 + 16384 + 32768 + 131072 + 262144 + 524288 + 1048576;
  6975. rights |= add;
  6976. rights &= (0xFFFFFFFF - substract);
  6977. }
  6978. return rights;
  6979. }
  6980. // Get the right of a user on a given device group
  6981. function GetMeshRights(mesh, userid) {
  6982. if (mesh == null) { return 0; }
  6983. if (userid == null) { userid = userinfo._id; }
  6984. if (typeof mesh == 'string') { mesh = meshes[mesh] }
  6985. if ((mesh == null) || (mesh.links == null)) { return 0; }
  6986. // Check if super user
  6987. if (serverinfo.manageAllDeviceGroups && (userid == userinfo._id)) return removeUserRights(0xFFFFFFFF, userid);
  6988. // Check device group link permission
  6989. var rights = 0, r = mesh.links[userid];
  6990. if (r != null) {
  6991. if (r.rights == 0xFFFFFFFF) { return removeUserRights(0xFFFFFFFF, userid); } // User has full rights thru a device group link, stop here.
  6992. rights = r.rights;
  6993. }
  6994. // Check permissions thru user groups
  6995. var user = null;
  6996. if (userid == userinfo._id) { user = userinfo; } else { if (users != null) { user = users[userid]; } }
  6997. if (user != null) {
  6998. for (var i in user.links) {
  6999. if (i.startsWith('ugrp/')) {
  7000. r = mesh.links[i];
  7001. if (r != null) {
  7002. if (r.rights == 0xFFFFFFFF) { return removeUserRights(0xFFFFFFFF, userid); } // User has full rights thru a user group, stop here.
  7003. rights |= r.rights; // TODO: Deal with reverse permissions
  7004. }
  7005. }
  7006. }
  7007. }
  7008. return removeUserRights(rights, userid);
  7009. }
  7010. // Returns true if the user can view the given device group
  7011. function IsMeshViewable(mesh, userid) {
  7012. if (mesh == null) { return false; }
  7013. if (userid == null) { userid = userinfo._id; }
  7014. if (typeof mesh == 'string') { mesh = meshes[mesh] }
  7015. if ((mesh == null) || (mesh.links == null)) { return false; }
  7016. if (mesh.links[userid] != null) { return true; } // User has visilibity thru a direct link
  7017. // Check if user user
  7018. if (serverinfo.manageAllDeviceGroups && (userid == userinfo._id)) return true;
  7019. // Check permissions thru user groups
  7020. var user = null;
  7021. if (userid == userinfo._id) { user = userinfo; } else { if (users != null) { user = users[userid]; } }
  7022. if (user != null) {
  7023. for (var i in user.links) {
  7024. if ((i.startsWith('ugrp/')) && (mesh.links[i] != null)) { return true; } // User has visilibity thru a user group
  7025. }
  7026. }
  7027. return false;
  7028. }
  7029. // Return the user rights for a given node
  7030. function GetNodeRights(node, userid) {
  7031. if (node == null) { return 0; }
  7032. if (userid == null) { userid = userinfo._id; }
  7033. if (typeof node == 'string') { node = getNodeFromId(node); if (node == null) { return 0; } }
  7034. var r = GetMeshRights(node.meshid, userid);
  7035. if (r == 0xFFFFFFFF) return removeUserRights(r, userid);
  7036. // Check direct device rights using device data
  7037. if ((node.links != null) && (node.links[userid] != null)) { r |= node.links[userid].rights; } // TODO: Deal with reverse permissions
  7038. // Check direct device rights thru user groups
  7039. if ((node.links != null) && (userinfo.links != null)) {
  7040. for (var i in node.links) {
  7041. if (i.startsWith('ugrp/') && (userinfo.links[i] != null) && (node.links[i].rights != null)) { r |= node.links[i].rights; }
  7042. }
  7043. }
  7044. // Check direct device rights using user data
  7045. /*
  7046. var user = null;
  7047. if (userid == userinfo._id) { user = userinfo; } else { if (users != null) { user = users[userid]; } }
  7048. if ((user != null) && (user.links != null)) {
  7049. var r2 = user.links[node._id];
  7050. if (r2 != null) {
  7051. if (r2.rights == 0xFFFFFFFF) { return 0xFFFFFFFF; } // User has full rights thru a device link, stop here.
  7052. r |= r2.rights; // TODO: Deal with reverse permissions
  7053. }
  7054. }
  7055. */
  7056. return removeUserRights(r, userid);
  7057. }
  7058. // Return true if the device is visible to the user
  7059. function IsNodeViewable(node, userid) {
  7060. if (node == null) { return false; }
  7061. if (userid == null) { userid = userinfo._id; }
  7062. if (typeof node == 'string') { node = getNodeFromId(node); if (node == null) { return false; } }
  7063. if (IsMeshViewable(node.meshid, userid)) return true;
  7064. // Check direct device visibility using device data
  7065. if ((node.links != null) && (node.links[userid] != null)) { return true; }
  7066. // Check direct device visibility thru user groups
  7067. if ((node.links != null) && (userinfo.links != null)) {
  7068. for (var i in node.links) { if (i.startsWith('ugrp/') && (userinfo.links[i] != null) && (node.links[i].rights != null)) { return true; } }
  7069. }
  7070. return false;
  7071. }
  7072. //
  7073. // Generic Methods
  7074. //
  7075. function nameSort(a, b) { var aa = a.name.toLowerCase(), bb = b.name.toLowerCase(); return sortCollator.compare(aa, bb); }
  7076. function getNodeAmtVersion(node) { if ((node == null) || (node.intelamt == null) || (typeof node.intelamt.ver != 'string')) return 0; var verSplit = node.intelamt.ver.split('.'); if (verSplit.length < 2) return 0; return parseInt(verSplit[0]) + (parseInt(verSplit[1]) / 100); }
  7077. function putstore(name, val) { try { if ((typeof (localStorage) === 'undefined') || (localStorage.getItem(name) == val)) return; if (val == null) { localStorage.removeItem(name); } else { localStorage.setItem(name, val); } } catch (e) { } if (name[0] != '_') { var s = {}; for (var i = 0, len = localStorage.length; i < len; ++i) { var k = localStorage.key(i); if (k[0] != '_') { s[k] = localStorage.getItem(k); } } meshserver.send({ action: 'userWebState', state: JSON.stringify(s) }); } }
  7078. function getstore(name, val) { try { if (typeof (localStorage) === 'undefined') return val; var v = localStorage.getItem(name); if ((v == null) || (v == null)) return val; return v; } catch (e) { return val; } }
  7079. function center() { if (xtermfit) xtermfit.fit(); onDevicesScroll(); QS('dialog').left = ((((getDocWidth() - 300) / 2)) + 'px'); deskAdjust(); if (currentNode != null) { drawDeviceTimeline(); } }
  7080. function messagebox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
  7081. function statusbox(t, m) { QH('id_dialogMessage', m); setDialogMode(1, t); }
  7082. function getDocWidth() { if (window.innerWidth) return window.innerWidth; if (document.documentElement && document.documentElement.clientWidth && document.documentElement.clientWidth != 0) return document.documentElement.clientWidth; return document.getElementsByTagName('body')[0].clientWidth; }
  7083. function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
  7084. function haltReturn(e) { if (e.keyCode == 13) { haltEvent(e); } }
  7085. function validateEmail(v) { var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return emailReg.test(v); }
  7086. function reload() { window.location.href = window.location.href; }
  7087. function getNodeFromId(id) { for (var i in nodes) { if (nodes[i]._id == id) return nodes[i]; } return null; }
  7088. function addHtmlValue(t, v) { return '<table><td style=width:120px>' + t + '<td><b>' + v + '</b></table>'; }
  7089. function addHtmlValue2(t, v) { return '<div><div style=display:inline-block;float:right>' + v + '</div><div style=display:inline-block>' + t + '</div></div>'; }
  7090. function addHtmlValue4(t, v) { return '<table style=width:100%><td style=width:120px>' + t + '<td style=text-align:right><b>' + v + '</b></table>'; }
  7091. function addLink(x, f) { return '<a style=cursor:pointer;text-decoration:none onclick=\'' + f + '\'>&diams; ' + x + '</a>'; }
  7092. function addLinkConditional(x, f, c) { if (c) return addLink(x, f); return x; }
  7093. function addKeyLink(x, f) { return '<span tabindex=0 style=cursor:pointer;text-decoration:none onclick=' + f + ' onkeypress="if (event.key==\'Enter\') { ' + f + ' } ">' + x + ' <img class=hoverButton src=images/key16.png></span>'; }
  7094. function addKeyLinkConditional(x, t, c) { if (c) return '<span title=\'' + t + '\'>' + x + ' <img class=hoverButton src=images/key16.png></span>'; return x }
  7095. function passwordcheck(p) { var re = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()]).{8,}/; return re.test(p); }
  7096. function getFileSizeStr(size) { if (typeof size != 'number') { size = 0; } if (size == 1) return "1 byte"; return format('{0} bytes', size); }
  7097. function focusTextBox(x) { setTimeout(function () { Q(x).selectionStart = Q(x).selectionEnd = 65535; Q(x).focus(); }, 0); }
  7098. var isFilenameValid = (function () { var x1 = /^[^\\/:\*\?"<>\|]+$/, x2 = /^\./, x3 = /^(nul|prn|con|lpt[0-9]|com[0-9])(\.|$)/i; return function isFilenameValid(fname) { return x1.test(fname) && !x2.test(fname) && !x3.test(fname) && (fname[0] != '.'); } })();
  7099. function printDate(d) { return d.toLocaleDateString(args.locale); }
  7100. function printTime(d) { return d.toLocaleTimeString(args.locale); }
  7101. function printDateTime(d) { return d.toLocaleString(args.locale); }
  7102. function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };
  7103. function nobreak(x) { return x.split(' ').join('&nbsp;'); }
  7104. function getUserName(userid) {
  7105. var useridsplit = userid.split('/'), userid2 = useridsplit[0] + '/' + useridsplit[1] + '/' + useridsplit[2], guestname = '';
  7106. if ((useridsplit.length == 4) && (useridsplit[3].startsWith('guest:'))) { guestname = ' - ' + decode_utf8(atob(useridsplit[3].substring(6))); }
  7107. if (users && users[userid2] != null) { if (users[userid2].realname != null) return (users[userid2].realname + guestname); else return (users[userid2].name + guestname); }
  7108. if (currentNode && currentNode.links && currentNode.links[userid] && currentNode.links[userid].name != null) { return (currentNode.links[userid].name + guestname); }
  7109. if (userid == userinfo._id) { return (userinfo.name + guestname); }
  7110. if (nodes) { for (var a in nodes) { if (nodes[a].links) { for (var b in nodes[a].links) { if (nodes[a].links[b].name && b == userid) return (nodes[a].links[b].name + guestname); } } } }
  7111. if (meshes) { for (var a in meshes) { if (meshes[a].links) { for (var b in meshes[a].links) { if (meshes[a].links[b].name && b == userid) return (meshes[a].links[b].name + guestname); } } } }
  7112. return (useridsplit[2] + guestname);
  7113. }
  7114. function addDetailItem(title, value, state) { return '<table style=width:100%><td>' + nobreak(title) + '<td style=text-align:right>' + value + '</table>'; }
  7115. function isPrivateIP(a) { return (a.startsWith('10.') || a.startsWith('172.16.') || a.startsWith('192.168.')); }
  7116. function encodeURIComponentEx(txt) { return encodeURIComponent(txt).replace(/'/g, '%27'); };
  7117. function safeNewWindow(url, target) { var newWindow = window.open(url, target, 'noopener,noreferrer'); if (newWindow) { newWindow.opener = null; } }
  7118. function isWindowsNode(node) { if ((node.mtype != 2) || (node.agent == null) || (node.agent.id == null)) return false; return ([1,2,3,4,21,22,34,42,43].indexOf(node.agent.id) >= 0); }
  7119. </script>
  7120. </body>
  7121. </html>