default.handlebars 1.3 MB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535
  1. <!DOCTYPE html>
  2. <html lang="en" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
  3. <head>
  4. <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  5. <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  6. <meta name="viewport" content="user-scalable=1.0,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0" />
  7. <meta name="apple-mobile-web-app-capable" content="yes" />
  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 type="image/x-icon" href="{{{domainurl}}}favicon.ico" rel="shortcut icon" />
  12. <link keeplink=1 type="text/css" href="styles/style.css" media="screen" rel="stylesheet" title="CSS" />
  13. <link type="text/css" href="styles/ol.css" media="screen" rel="stylesheet" title="CSS" />
  14. <link type="text/css" href="styles/ol3-contextmenu.min.css" media="screen" rel="stylesheet" title="CSS" />
  15. <link type="text/css" href="styles/xterm.css" media="screen" rel="stylesheet" title="CSS" />
  16. <link type="text/css" href="styles/flatpickr.min.css" media="screen" rel="stylesheet" title="CSS">
  17. {{{customCSSTags}}}
  18. <link rel="apple-touch-icon" href="/favicon-303x303.png" />
  19. <script type="text/javascript" src="scripts/common-0.0.1{{{min}}}.js"></script>
  20. <script type="text/javascript" src="scripts/meshcentral{{{min}}}.js"></script>
  21. <script type="text/javascript" src="scripts/amt-0.2.0{{{min}}}.js"></script>
  22. <script type="text/javascript" src="scripts/amt-wsman-0.2.0{{{min}}}.js"></script>
  23. <script type="text/javascript" src="scripts/amt-desktop-0.0.2{{{min}}}.js"></script>
  24. <script type="text/javascript" src="scripts/amt-terminal-0.0.2{{{min}}}.js"></script>
  25. <script type="text/javascript" src="scripts/zlib{{{min}}}.js"></script>
  26. <script type="text/javascript" src="scripts/zlib-inflate{{{min}}}.js"></script>
  27. <script type="text/javascript" src="scripts/zlib-adler32{{{min}}}.js"></script>
  28. <script type="text/javascript" src="scripts/zlib-crc32{{{min}}}.js"></script>
  29. <script type="text/javascript" src="scripts/amt-redir-ws-0.1.0{{{min}}}.js"></script>
  30. <script type="text/javascript" src="scripts/amt-wsman-ws-0.2.0{{{min}}}.js"></script>
  31. <script type="text/javascript" src="scripts/agent-redir-ws-0.1.1{{{min}}}.js"></script>
  32. <script type="text/javascript" src="scripts/agent-redir-rtc-0.1.0{{{min}}}.js"></script>
  33. <script type="text/javascript" src="scripts/agent-desktop-0.0.2{{{min}}}.js"></script>
  34. <script type="text/javascript" src="scripts/agent-rdp-0.0.1{{{min}}}.js"></script>
  35. <script type="text/javascript" src="scripts/qrcode.min.js"></script>
  36. <script type="text/javascript" src="scripts/xterm{{{min}}}.js"></script>
  37. <script type="text/javascript" src="scripts/xterm-addon-fit{{{min}}}.js"></script>
  38. <script type="text/javascript" src="scripts/flatpickr.js"></script>
  39. <script type="text/javascript" src="mstsc/mstsc.js"></script>
  40. <script type="text/javascript" src="mstsc/keyboard.js"></script>
  41. <script type="text/javascript" src="mstsc/rle.js"></script>
  42. <script type="text/javascript" src="mstsc/client.js"></script>
  43. <script type="text/javascript" src="mstsc/canvas.js"></script>
  44. <script keeplink=1 type="text/javascript" src="scripts/u2f-api{{{min}}}.js"></script>
  45. <script keeplink=1 type="text/javascript" src="scripts/charts{{{min}}}.js"></script>
  46. <script keeplink=1 type="text/javascript" src="scripts/moment{{min}}.js"></script>
  47. <script keeplink=1 type="text/javascript" src="scripts/chartjs-adapter-moment{{{min}}}.js"></script>
  48. <script keeplink=1 type="text/javascript" src="scripts/filesaver.min.js"></script>
  49. <script keeplink=1 type="text/javascript" src="scripts/ol{{{min}}}.js"></script>
  50. <script keeplink=1 type="text/javascript" src="scripts/ol3-contextmenu{{{min}}}.js"></script>
  51. <script keeplink=1 type="text/javascript" src="scripts/purify{{{min}}}.js"></script>
  52. <script keeplink=1 type="text/javascript" src="scripts/marked{{{min}}}.js"></script>
  53. {{{customJSTags}}}
  54. <title>{{{title}}}</title>
  55. </head>
  56. <body id="body" oncontextmenu="handleContextMenu(event)" style="display:none;min-width:495px" onload="if (typeof(startup) !== 'undefined') startup();">
  57. <!-- right click menu -->
  58. <div id="contextMenu" class="contextMenu noselect" style="display:none">
  59. <div id="cxinfo" class="cmtext" onclick="cmaction(1,event)"><b>Information</b></div>
  60. <div id="cxdesktop" class="cmtext" onclick="cmaction(3,event)">Desktop</div>
  61. <div id="cxterminal" class="cmtext" onclick="cmaction(2,event)">Terminal</div>
  62. <div id="cxfiles" class="cmtext" onclick="cmaction(4,event)">Files</div>
  63. <div id="cxevents" class="cmtext" onclick="cmaction(5,event)">Events</div>
  64. <div id="cxdetails" class="cmtext" onclick="cmaction(6,event)">Details</div>
  65. <div id="cxconsole" class="cmtext" onclick="cmaction(7,event)">Console</div>
  66. <div id="cxwebrdp" class="cmtext" onclick="cmaction(11,event)">Web-RDP</div>
  67. <div id="cxwebvnc" class="cmtext" onclick="cmaction(12,event)">Web-VNC</div>
  68. <div id="cxwebssh" class="cmtext" onclick="cmaction(13,event)">Web-SSH</div>
  69. <div id="cxrdp" class="cmtext" onclick="cmaction(14,event)">RDP</div>
  70. <div id="cxplugins" class="cmtext" onclick="cmaction(8,event)" style="display:none">Plugins</div>
  71. <hr id="cxmgroupsplit" />
  72. <div id="cxstar" class="cmtext" onclick="cmaction(10,event)" style="display:none">Toggle Star</div>
  73. <div id="cxmdesktop" class="cmtext" onclick="cmaction(9,event)" style="display:none">Multi-Desktop</div>
  74. </div>
  75. <div id="meshContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  76. <div id="cxselectall" class="cmtext" onclick="cmmeshaction(1,event)">Select All</div>
  77. <div id="cxselectnone" class="cmtext" onclick="cmmeshaction(2,event)">Select None</div>
  78. <!--
  79. <hr id="cxmgroupsplit2" style="display:none" />
  80. <div id="cxmmdesktop" class="cmtext" style="display:none" onclick="cmmeshaction(3,event)">Multi-Desktop</div>
  81. -->
  82. </div>
  83. <div id="filesShellContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  84. <div class="cmtext" onclick="cmconnectfilesaction()">Ask Consent</div>
  85. </div>
  86. <div id="termShellContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  87. <div class="cmtext" onclick="cmtermaction(1,0,event)"><b>Admin Shell</b></div>
  88. <div class="cmtext" onclick="cmtermaction(6,0,event)">Admin PowerShell</div>
  89. <div class="cmtext" onclick="cmtermaction(8,0,event)">User Shell</div>
  90. <div class="cmtext" onclick="cmtermaction(9,0,event)">User PowerShell</div>
  91. <div class="cmtext" onclick="cmtermaction(1,0x10,event)">Ask Admin Shell</div>
  92. <div class="cmtext" onclick="cmtermaction(6,0x10,event)">Ask Admin PowerShell</div>
  93. <div class="cmtext" onclick="cmtermaction(8,0x10,event)">Ask User Shell</div>
  94. <div class="cmtext" onclick="cmtermaction(9,0x10,event)">Ask User PowerShell</div>
  95. </div>
  96. <div id="termShellContextMenu2" class="contextMenu noselect" style="display:none;min-width:0px">
  97. <div class="cmtext" onclick="cmtermaction(8,0,event)"><b>User Shell</b></div>
  98. <div class="cmtext" onclick="cmtermaction(9,0,event)">User PowerShell</div>
  99. </div>
  100. <div id="termShellContextMenuLinux" class="contextMenu noselect" style="display:none;min-width:0px">
  101. <div class="cmtext" onclick="cmtermaction(1,0,event)"><b>Root Shell</b></div>
  102. <div class="cmtext" onclick="cmtermaction(8,0,event)">User Shell</div>
  103. <div class="cmtext" onclick="cmtermaction(100,0,event)">Login Shell</div>
  104. </div>
  105. <div id="deskConnectContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  106. <div class="cmtext" onclick="cmdeskaction(1,event)">Ask Consent + Bar</div>
  107. <div class="cmtext" onclick="cmdeskaction(2,event)">Ask Consent</div>
  108. <div class="cmtext" onclick="cmdeskaction(3,event)">Privacy Bar</div>
  109. </div>
  110. <div id="deskDisconnectContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  111. <div class="cmtext" onclick="cmdeskaction(10,event)">Disconnect and Lock</div>
  112. <div class="cmtext" onclick="cmdeskaction(11,event)">Disconnect</div>
  113. </div>
  114. <div id="altPortContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  115. <div class="cmtext" onclick="cmaltportaction(1,event)">Alternate Port</div>
  116. </div>
  117. <div id="sshPortContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  118. <div class="cmtext" onclick="cmsshportaction(1,event)">Alternate Port</div>
  119. </div>
  120. <div id="rfbPortContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  121. <div class="cmtext" onclick="cmrfbportaction(1,event)">Alternate Port</div>
  122. </div>
  123. <div id="httpPortContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  124. <div class="cmtext" onclick="cmhttpportaction(1,event)">Alternate Port</div>
  125. </div>
  126. <div id="httpsPortContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  127. <div class="cmtext" onclick="cmhttpsportaction(1,event)">Alternate Port</div>
  128. </div>
  129. <div id="filesContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  130. <div class="cmtext" onclick="cmfilesaction(1,event)">Rename</div>
  131. <div class="cmtext" onclick="cmfilesaction(2,event)">Edit</div>
  132. <div class="cmtext" onclick="cmfilesaction(3,event)">Delete</div>
  133. </div>
  134. <div id="expandAllContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  135. <div class="cmtext" onclick="cmexpandaction(1,event)">Expand All</div>
  136. <div class="cmtext" onclick="cmexpandaction(2,event)">Collapse All</div>
  137. </div>
  138. <div id="deskPlayerContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  139. <div class="cmtext" onclick="cmdeskplayeraction(1,event)">Open Player...</div>
  140. </div>
  141. <div id="deskKeyShortcutContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  142. <div class="cmtext" onclick="cmdeskshortcutaction(1,event)">Customize...</div>
  143. </div>
  144. <div id="deskPreConfigShortcutContextMenu" class="contextMenu noselect" style="display:none;min-width:0px;max-height:calc(80% - 50px);overflow-y:auto">
  145. <span id="deskPreConfigShortcutContextMenu1"></span>
  146. <span id="deskPreConfigShortcutContextMenu2"></span>
  147. <div class="cmtext" onclick="cmdeskpreconfigtypeaction(-1,event)">Customize...</div>
  148. </div>
  149. <div id="deskPreConfigScriptContextMenu" class="contextMenu noselect" style="display:none;min-width:0px;max-height:calc(80% - 50px);overflow-y:auto">
  150. <span id="deskPreConfigScriptContextMenu1"></span>
  151. <span id="deskPreConfigScriptContextMenu2"></span>
  152. </div>
  153. <!--
  154. <div id="pluginTabContextMenu" class="contextMenu noselect" style="display:none;min-width:0px">
  155. <div id="cxclose" class="cmtext" onclick="pluginTabClose(event)">Close Tab</div>
  156. </div>
  157. -->
  158. <!-- main page -->
  159. <div id=container>
  160. <div id="notifiyBox" class="notifiyBox" style="display:none"></div>
  161. <div id=masthead class=noselect>
  162. <div style="float:left">{{{titlehtml}}}</div>
  163. <div class="title" onclick="go(1,event)">{{{title1}}}</div>
  164. <div class="title2">{{{title2}}}</div>
  165. <div style="float:right">
  166. <div id=notificationCount onclick="clickNotificationIcon()" class="unselectable" style="display: none;" title="Click to view current notifications">0</div>
  167. </div>
  168. <p id="logoutControl"><span id=logoutControlSpan class="logoncontrolspan"></span><span id=idleTimeoutNotify style="color:yellow"></span></p>
  169. <div class=textnewui id=textnewui onmouseup=toggleBootstrapUIMode() onkeypress="if (event.key=='Enter') { toggleBootstrapUIMode(); }">
  170. <b>Try the new MeshCentral UI</b>
  171. </div>
  172. </div>
  173. <div id="page_leftbar">
  174. <div style="height:16px"></div>
  175. <div id=LeftMenuMyDevices tabindex=0 class="lbbutton lbbuttonsel" title="My Devices" onmouseup=go(1,event) onkeypress="if (event.key=='Enter') { go(1); }">
  176. <div class="lbtg lb2"></div>
  177. </div>
  178. <div id=LeftMenuMyAccount tabindex=0 class="lbbutton" title="My Account" onmouseup=go(2,event) onkeypress="if (event.key=='Enter') { go(2); }">
  179. <div class="lbtg lb1"></div>
  180. </div>
  181. <div id=LeftMenuMyEvents tabindex=0 class="lbbutton" title="My Events" onmouseup=go(3,event) onkeypress="if (event.key=='Enter') { go(3); }">
  182. <div class="lbtg lb3"></div>
  183. </div>
  184. <div id=LeftMenuMyFiles tabindex=0 class="lbbutton" style="display:none" title="My Files" onmouseup=go(5,event) onkeypress="if (event.key=='Enter') { go(5); }">
  185. <div class="lbtg lb4"></div>
  186. </div>
  187. <div id=LeftMenuMyUsers tabindex=0 class="lbbutton" style="display:none" title="My Users" onmouseup=go(4,event) onkeypress="if (event.key=='Enter') { go(4); }">
  188. <div class="lbtg lb5"></div>
  189. </div>
  190. <div id=LeftMenuMyServer tabindex=0 class="lbbutton" style="display:none" title="My Server" onmouseup=go(6,event) onkeypress="if (event.key=='Enter') { go(6); }">
  191. <div class="lbtg lb6"></div>
  192. </div>
  193. </div>
  194. <div id=topbar class=noselect>
  195. <div>
  196. <div style="position:relative">
  197. <span id=logoutControlSpan2></span>
  198. <div tabindex=0 id=uiMenuButton title="User interface selection" onclick="showUserInterfaceSelectMenu()" onkeypress="if (event.key == 'Enter') showUserInterfaceSelectMenu()">
  199. &diams;
  200. <div id=uiMenu style="display:none">
  201. <table>
  202. <tr>
  203. <td>
  204. <div tabindex=0 id=uiViewButton1 class=uiSelector onclick=userInterfaceSelectMenu(1) title="Left bar interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(1)"><div class="uiSelector1"></div></div>
  205. <div tabindex=0 id=uiViewButton2 class=uiSelector onclick=userInterfaceSelectMenu(2) title="Top bar interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(2)"><div class="uiSelector2"></div></div>
  206. <div tabindex=0 id=uiViewButton3 class=uiSelector onclick=userInterfaceSelectMenu(3) title="Fixed width interface" onkeypress="if (event.key == 'Enter') userInterfaceSelectMenu(3)"><div class="uiSelector3"></div></div>
  207. <div tabindex=0 id=uiViewButton7 class=uiSelector onclick=toggleBootstrapUIMode() title="Toggle Modern UI" onkeypress="if (event.key == 'Enter') toggleBootstrapUIMode()"><div class="uiSelector7"></div></div>
  208. </td>
  209. <td>
  210. <div tabindex=0 id=uiViewButton6 class=uiSelector onclick="showNotes(false)" title="Personal Notes" onkeypress="if (event.key == 'Enter') showNotes(false)"><div class="uiSelector6"></div></div>
  211. <div tabindex=0 id=uiViewButton4 class=uiSelector onclick=toggleNightMode() title="Toggle night mode" onkeypress="if (event.key == 'Enter') toggleNightMode()"><div class="uiSelector4"></div></div>
  212. <div tabindex=0 id=uiViewButton5 class=uiSelector onclick=toggleFooterBarMode() title="Toggle footer bar" onkeypress="if (event.key == 'Enter') toggleFooterBarMode()"><div class="uiSelector5"></div></div>
  213. <div class=uiSelector_end>&nbsp;</div>
  214. </td>
  215. </tr>
  216. </table>
  217. </div>
  218. </div>
  219. <table id=MainMenuSpan cellpadding=0 cellspacing=0 class=style1>
  220. <tr>
  221. <td tabindex=0 id=MainMenuMyDevices class="topbar_td style3x" onmouseup=go(1,event) onkeypress="if (event.key == 'Enter') go(1)">My Devices</td>
  222. <td tabindex=0 id=MainMenuMyAccount class="topbar_td style3x" onmouseup=go(2,event) onkeypress="if (event.key == 'Enter') go(2)">My Account</td>
  223. <td tabindex=0 id=MainMenuMyEvents class="topbar_td style3x" onmouseup=go(3,event) onkeypress="if (event.key == 'Enter') go(3)">My Events</td>
  224. <td tabindex=0 id=MainMenuMyFiles class="topbar_td style3x" onmouseup=go(5,event) onkeypress="if (event.key == 'Enter') go(5)">My Files</td>
  225. <td tabindex=0 id=MainMenuMyUsers class="topbar_td style3x" onmouseup=go(4,event) onkeypress="if (event.key == 'Enter') go(4)">My Users</td>
  226. <td tabindex=0 id=MainMenuMyServer class="topbar_td style3x" onmouseup=go(6,event) onkeypress="if (event.key == 'Enter') go(6)">My Server</td>
  227. <td class="topbar_td_end style3">&nbsp;</td>
  228. </tr>
  229. </table>
  230. <div id=MainSubMenuSpan style="display:none">
  231. <table id=MainSubMenu cellpadding=0 cellspacing=0 class=style1>
  232. <tr>
  233. <td tabindex=0 id=MainDev class="topbar_td style3x" onmouseup=go(10,event) onkeypress="if (event.key == 'Enter') go(10)">General</td>
  234. <td tabindex=0 id=MainDevDesktop class="topbar_td style3x" onmouseup=go(11,event) onkeypress="if (event.key == 'Enter') go(11)">Desktop</td>
  235. <td tabindex=0 id=MainDevTerminal class="topbar_td style3x" onmouseup=go(12,event) onkeypress="if (event.key == 'Enter') go(12)">Terminal</td>
  236. <td tabindex=0 id=MainDevFiles class="topbar_td style3x" onmouseup=go(13,event) onkeypress="if (event.key == 'Enter') go(13)">Files</td>
  237. <td tabindex=0 id=MainDevEvents class="topbar_td style3x" onmouseup=go(16,event) onkeypress="if (event.key == 'Enter') go(16)">Events</td>
  238. <td tabindex=0 id=MainDevInfo class="topbar_td style3x" onmouseup=go(17,event) onkeypress="if (event.key == 'Enter') go(17)">Details</td>
  239. <td tabindex=0 id=MainDevAmt class="topbar_td style3x" onmouseup=go(14,event) onkeypress="if (event.key == 'Enter') go(14)">Intel&reg;AMT</td>
  240. <td tabindex=0 id=MainDevConsole class="topbar_td style3x" onmouseup=go(15,event) onkeypress="if (event.key == 'Enter') go(15)">Console</td>
  241. <td tabindex=0 id=MainDevPlugins class="topbar_td style3x" onmouseup=go(19,event) onkeypress="if (event.key == 'Enter') go(19)">Plugins</td>
  242. <td class="topbar_td_end style3">&nbsp;</td>
  243. </tr>
  244. </table>
  245. </div>
  246. <div id=MeshSubMenuSpan style="display:none">
  247. <table id=MeshSubMenu cellpadding=0 cellspacing=0 class=style1>
  248. <tr>
  249. <td tabindex=0 id=MeshGeneral class="topbar_td style3x" onmouseup=go(20,event) onkeypress="if (event.key == 'Enter') go(20)">General</td>
  250. <td tabindex=0 id=MeshSummary class="topbar_td style3x" onmouseup=go(21,event) onkeypress="if (event.key == 'Enter') go(21)">Summary</td>
  251. <td class="topbar_td_end style3">&nbsp;</td>
  252. </tr>
  253. </table>
  254. </div>
  255. <div id=EventsSubMenuSpan style="display:none">
  256. <table id=EventsSubMenu cellpadding=0 cellspacing=0 class=style1>
  257. <tr>
  258. <td tabindex=0 id=EventsLive class="topbar_td style3x" onmouseup=go(3,event) onkeypress="if (event.key == 'Enter') go(3)">Events</td>
  259. <td tabindex=0 id=EventsReport class="topbar_td style3x" onmouseup=go(60,event) onkeypress="if (event.key == 'Enter') go(60)">Reports</td>
  260. <td class="topbar_td_end style3">&nbsp;</td>
  261. </tr>
  262. </table>
  263. </div>
  264. <div id=UserSubMenuSpan style="display:none">
  265. <table id=UserSubMenu cellpadding=0 cellspacing=0 class=style1>
  266. <tr>
  267. <td tabindex=0 id=UserGeneral class="topbar_td style3x" onmouseup=go(30,event) onkeypress="if (event.key == 'Enter') go(30)">General</td>
  268. <td tabindex=0 id=UserEvents class="topbar_td style3x" onmouseup=go(31,event) onkeypress="if (event.key == 'Enter') go(31)">Events</td>
  269. <td class="topbar_td_end style3">&nbsp;</td>
  270. </tr>
  271. </table>
  272. </div>
  273. <div id=UsersSubMenuSpan style="display:none">
  274. <table id=UsersSubMenu cellpadding=0 cellspacing=0 class=style1>
  275. <tr>
  276. <td tabindex=0 id=UsersGeneral class="topbar_td style3x" onmouseup=go(4,event) onkeypress="if (event.key == 'Enter') go(4)">Users</td>
  277. <td tabindex=0 id=UsersGroups class="topbar_td style3x" onmouseup=go(50,event) onkeypress="if (event.key == 'Enter') go(50)">Groups</td>
  278. <td tabindex=0 id=UsersRecordings class="topbar_td style3x" onmouseup=go(52,event) onkeypress="if (event.key == 'Enter') go(52)">Recordings</td>
  279. <td class="topbar_td_end style3">&nbsp;</td>
  280. </tr>
  281. </table>
  282. </div>
  283. <div id=ServerSubMenuSpan style="display:none">
  284. <table id=ServerSubMenu cellpadding=0 cellspacing=0 class=style1>
  285. <tr>
  286. <td tabindex=0 id=ServerGeneral class="topbar_td style3x" onmouseup=go(6,event) onkeypress="if (event.key == 'Enter') go(6)">General</td>
  287. <td tabindex=0 id=ServerStats class="topbar_td style3x" onmouseup=go(40,event) onkeypress="if (event.key == 'Enter') go(40)">Stats</td>
  288. <td tabindex=0 id=ServerConsole class="topbar_td style3x" onmouseup=go(115,event) onkeypress="if (event.key == 'Enter') go(115)">Console</td>
  289. <td tabindex=0 id=ServerTrace class="topbar_td style3x" onmouseup=go(41,event) onkeypress="if (event.key == 'Enter') go(41)">Trace</td>
  290. <td tabindex=0 id=ServerPlugins class="topbar_td style3x" onmouseup=go(42,event) onkeypress="if (event.key == 'Enter') go(42)">Plugins</td>
  291. <td class="topbar_td_end style3">&nbsp;</td>
  292. </tr>
  293. </table>
  294. </div>
  295. <div id=UserDummyMenuSpan>
  296. <table id=UserDummyMenu cellpadding=0 cellspacing=0 class=style1>
  297. <tr><td class=style3 style="">&nbsp;</td></tr>
  298. </table>
  299. </div>
  300. </div>
  301. </div>
  302. </div>
  303. <div id="column_l">
  304. <div id=p0 style="display:none">
  305. <div id=p0message><span id=p0span>Server disconnected</span>, <href onclick=reload() style=cursor:pointer><u>click to reconnect</u></href>.</div>
  306. </div>
  307. <div id=p1 style="display:none">
  308. <div id="p1title">
  309. <div style="display:none" id="devListToolbarViewIcons">
  310. <div id=devViewPageState style="float:left;line-height:32px;height:32px;font-size:16px;display:none"></div>
  311. <div tabindex=0 id=devViewPageButton1 class=viewSelector onclick=onDeviceViewPageChange(1) onkeypress="if (event.key == 'Enter') { onDeviceViewPageChange(1); }" title="Go to first page" style="display:none"><div class="viewSelector9"></div></div>
  312. <div tabindex=0 id=devViewPageButton2 class=viewSelector onclick=onDeviceViewPageChange(2) onkeypress="if (event.key == 'Enter') { onDeviceViewPageChange(2); }" title="Go to previous page" style="display:none"><div class="viewSelector10"></div></div>
  313. <div tabindex=0 id=devViewPageButton3 class=viewSelector onclick=onDeviceViewPageChange(3) onkeypress="if (event.key == 'Enter') { onDeviceViewPageChange(3); }" title="Go to next page" style="display:none"><div class="viewSelector11"></div></div>
  314. <div tabindex=0 id=devViewPageButton4 class=viewSelector onclick=onDeviceViewPageChange(4) onkeypress="if (event.key == 'Enter') { onDeviceViewPageChange(4); }" title="Go to last page" style="display:none;margin-right:12px"><div class="viewSelector8"></div></div>
  315. <div tabindex=0 id=devViewButton1 class=viewSelector onclick=onDeviceViewChange(1) onkeypress="if (event.key == 'Enter') { onDeviceViewChange(1); }" title="Columns"><div class="viewSelector2"></div></div>
  316. <div tabindex=0 id=devViewButton2 class=viewSelector onclick=onDeviceViewChange(2) onkeypress="if (event.key == 'Enter') { onDeviceViewChange(2); }" title="List"><div class="viewSelector1"></div></div>
  317. <div tabindex=0 id=devViewButton3 class=viewSelector onclick=onDeviceViewChange(3) onkeypress="if (event.key == 'Enter') { onDeviceViewChange(3); }" title="Desktops"><div class="viewSelector3"></div></div>
  318. <div tabindex=0 id=devViewButton5 class=viewSelector onclick=onDeviceViewChange(5) onkeypress="if (event.key == 'Enter') { onDeviceViewChange(5); }" title="Desktops"><div class="viewSelector7"></div></div>
  319. <div tabindex=0 id=devViewButton4 class=viewSelector onclick=onDeviceViewChange(4) onkeypress="if (event.key == 'Enter') { onDeviceViewChange(4); }" title="Map" style="display:none"><div class="viewSelector4"></div></div>
  320. </div><div><h1>My Devices</h1></div>
  321. </div>
  322. <table id="devListToolbarSpan" class="noselect">
  323. <tr>
  324. <td class=h1></td>
  325. <td id=devListToolbar class=style14 style="display:none">
  326. <img style="cursor:pointer;margin-top:4px;display:none" title="Collapse All" id="CollapseAllButton" src="images/icon-collapse.png" width=9 height=11 onclick="cmexpandaction(2)" />
  327. <img style="cursor:pointer;margin-top:4px;display:none" title="Expand All" id="ExpandAllButton" src="images/icon-expand.png" width=9 height=11 onclick="cmexpandaction(1)" />
  328. <input type=button id=SelectAllButton onclick="selectallButtonFunction();" value="Select All" />&nbsp;
  329. <input type=button id=GroupActionButton disabled="disabled" value="Group Action" onclick=groupActionFunction() />&nbsp;
  330. <input type=button id=ScrollToTopButton onclick="onDevicesScroll(true);" style="display:none;margin-right:4px" value="Scroll To Top" />
  331. <input id=SearchInput type=input autocomplete=off placeholder=Filter onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) onfocus=onSearchFocus(1) onblur=onSearchFocus(0) title="Filter: user:xxx or u:xxx ip:xxx group:xxx or g:xxx tag:xxx or t: xxx atag:xxx or a:xxx os:xxx amt:xxx desc:xxx wsc:ok wsc:noav wsc:noupdate wsc:nofirewall wsc:any connectivity:xxx c:xxx"/>&nbsp;
  332. <span id=SearchInputClearButton style="display:none;position:relative"><img src="images/x16.png" type="button" onclick="clearDeviceSearch()" style="position:absolute;cursor:pointer;left:-18px;top:-8px" srcset="images/x32.png 2x"/></span>
  333. <select id=DevFilterSelect onchange=onOnlineCheckBox(event) title="Device Filter">
  334. <option value=0>All</option>
  335. <option value=1>Online</option>
  336. <option value=5>Offline</option>
  337. <option value=2>Sessions</option>
  338. <option value=3>Starred</option>
  339. <option value=4>Intel&reg; AMT</option>
  340. <option value=6>Help</option>
  341. <option value=7>Tagged</option>
  342. <option value=8>Untagged</option>
  343. </select>
  344. <label><input type=checkbox id=RealNameCheckBox onclick=onRealNameCheckBox() /><span title="Show devices operating system name">OS Name</span></label>
  345. <label style="display:none"><input type=checkbox id=OnlineCheckBox onclick=onOnlineCheckBox(event) /><span title="Only show devices that are online">Online</span></label>
  346. <span id="devsCustomUIBar"></span>
  347. </td>
  348. <td id=kvmListToolbar class=style14 style="display:none">
  349. <img style="cursor:pointer;margin-top:4px;display:none" title="Collapse All" id="CollapseAllButton2" src="images/icon-collapse.png" width=9 height=11 onclick="cmexpandaction(2)" />
  350. <img style="cursor:pointer;margin-top:4px;display:none" title="Expand All" id="ExpandAllButton2" src="images/icon-expand.png" width=9 height=11 onclick="cmexpandaction(1)" />
  351. <input type="button" onclick="connectAllKvmFunction()" value="Connect All" />&nbsp;
  352. <input type="button" onclick="disconnectAllKvmFunction()" value="Disconnect All" />&nbsp;
  353. <input type="button" onclick="onDevicesScroll(true);" value="Scroll To Top" />&nbsp;
  354. <label><input type="checkbox" id="autoConnectDesktopCheckbox" onclick="autoConnectDesktops(event)" title="Automatic connect" />Auto&nbsp;</label>
  355. <input type="button" onclick="showMultiDesktopSettings()" value="Settings" />&nbsp;
  356. <input id=KvmSearchInput type=search placeholder=Filter onchange=onDeviceSearchChanged(event) onclick=onDeviceSearchChanged(event) onkeyup=onDeviceSearchChanged(event) autocomplete=off onfocus=onSearchFocus(1) onblur=onSearchFocus(0) />&nbsp;
  357. <span id=KvmSearchInputClearButton style="display:none;position:relative"><img src="images/x16.png" type="button" onclick="clearDeviceSearch()" style="position:absolute;cursor:pointer;left:-18px;top:-8px" srcset="images/x32.png 2x" /></span>
  358. </td>
  359. <td id=devMapToolbar class=style14 style="display:none">
  360. &nbsp;&nbsp;<input type=search id=mapSearchLocation placeholder="Search Location" onfocus=onMapSearchFocus(1) onblur=onMapSearchFocus(0) />
  361. <input type=button value=Search title="Search for location" onclick=getSearchLocation() />
  362. <input type=button id=refreshmap title="Reset map view" value=Reset onclick=refreshMap(false,true) />
  363. </td>
  364. <td class="auto-style1" style=height:100%>
  365. <img style="display:none;cursor:pointer;margin-top:3px" id=devListToolbarSettings src="images/icon-gear.png" width=16 height=16 onclick="onDeviceViewSettings()" />
  366. <div style="display:none" id=devListToolbarView>
  367. View
  368. <select id=viewselect onchange=onDeviceViewChange()>
  369. <option value=1>Columns</option>
  370. <option value=2>List</option>
  371. <option value=3>Desktops Fixed Width</option>
  372. <option id=viewselectmapoption value=4 style="display:none">Map</option>
  373. <option value=5>Desktops</option>
  374. </select>
  375. </div>
  376. <div style="display:none" id=devListToolbarSort>
  377. Sort
  378. <select id=sortselect onchange=mainUpdate(6)>
  379. <option>Group</option>
  380. <option>Power</option>
  381. <option>Device</option>
  382. <option>Tags</option>
  383. <option>Group-Tags</option>
  384. <option>Last Seen</option>
  385. </select>
  386. &nbsp;
  387. </div>
  388. <div style="display:none" id=devListToolbarSize>
  389. Size
  390. <select id=sizeselect onchange=onDeviceViewChange()>
  391. <option value=0>Small</option>
  392. <option value=1>Medium</option>
  393. <option value=2>Large</option>
  394. </select>
  395. &nbsp;
  396. </div>
  397. </td>
  398. <td class=h2></td>
  399. </tr>
  400. </table>
  401. <div id=NoMeshesPanel style="display:none">
  402. <table>
  403. <tr>
  404. <td valign="top" style="width:50px">
  405. <img src="images/info.png" width="47" height="48" />
  406. </td>
  407. <td>
  408. <div id="getStarted1">To get started, <a href=# onclick="return account_createMesh()"><strong>click here to create a device group</strong></a>.</div>
  409. <div id="getStarted2">No device groups.</div>
  410. </td>
  411. </tr>
  412. </table>
  413. </div>
  414. <div id="xdevices" class="noselect" style="display:none" onscroll="onDevicesScroll(false)"></div>
  415. <div id="xdevicesmap" style="display:none">
  416. <div id=xmapSearchResultsDlg style="display:none">
  417. <div id=xmapSearchResultsBck>
  418. <div id=xmapSearchClose onclick=mapCloseSearchWindow()><b>X</b></div>
  419. <div style=padding:5px>Location Results</div>
  420. <div style=width:100%;margin:6px></div>
  421. </div>
  422. <div id=xmapSearchResults style=margin:6px></div>
  423. </div>
  424. </div>
  425. <div id="xmap-info-window"></div>
  426. </div>
  427. <div id=p2 style="display:none">
  428. <div id="p2title"><h1>My Account</h1></div>
  429. <div id="p2info" style="overflow-y:auto">
  430. <div id="p2AccountImageFrame">
  431. <img id="p2AccountImage" alt="" width="128" height="128" onclick="account_manageImage(0)" src="images/user-256.png" />
  432. <div><a onclick="account_manageImage(0)">Change image</a></div>
  433. </div>
  434. <div id="p2AccountSecurity" style="display:none">
  435. <p><strong>Account security</strong></p>
  436. <div style="margin-left:25px">
  437. <div id="managePhoneNumber1"><div class="p2AccountActions"><span id="authPhoneNumberCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_managePhone()">Manage phone number</a><br /></span></div>
  438. <div id="manageEmail2FA"><div class="p2AccountActions"><span id="authEmailSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_manageAuthEmail()">Manage email authentication</a><br /></span></div>
  439. <div id="manageAuthApp"><div class="p2AccountActions"><span id="authAppSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_manageAuthApp()">Manage authenticator app</a><br /></span></div>
  440. <div id="manageDuoApp"><div class="p2AccountActions"><span id="authDuoSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_manageAuthDuo()">Manage Duo authentication</a><br /></span></div>
  441. <div id="manageHardwareOtp"><div class="p2AccountActions"><span id="authKeySetupCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_manageHardwareOtp(0)">Manage security keys</a><br /></span></div>
  442. <div id="managePushAuthDev"><div class="p2AccountActions"><span id="authPushAuthDevCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_managePushAuthDev()">Manage push authentication</a><br /></span></div>
  443. <div id="manageMessaging1"><div class="p2AccountActions"><span id="authMessagingCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_manageMessaging()">Manage messaging</a><br /></span></div>
  444. <div id="manageOtp"><div class="p2AccountActions"><span id="authCodesSetupCheck"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return account_manageOtp(0)">Manage backup codes</a><br /></span></div>
  445. </div>
  446. </div>
  447. <div id="p2AccountActions">
  448. <p><strong>Account actions</strong></p>
  449. <p class="mL">
  450. <span id="viewPreviousLogins"><a href=# onclick="return account_viewPreviousLogins()">View previous logins</a><br /></span>
  451. <span id="managePhoneNumber2" style="display:none"><a href=# onclick="return account_managePhone()">Manage phone number</a><br /></span>
  452. <span id="manageMessaging2" style="display:none"><a href=# onclick="return account_manageMessaging()">Manage messaging</a><br /></span>
  453. <span id="verifyEmailId" style="display:none"><a href=# onclick="return account_showVerifyEmail()">Verify email</a><br /></span>
  454. <span id="accountEnableNotificationsSpan" style="display:none"><a href=# onclick="return account_enableNotifications()">Enable web notifications</a><br /></span>
  455. <a href=# onclick="return account_showLocalizationSettings()">Localization Settings</a><br />
  456. <a href=# onclick="return account_showAccountNotifySettings()">Notification Settings</a><br />
  457. <span id="p2AccountPassActions">
  458. <span id="accountChangeEmailAddressSpan" style="display:none"><a href=# onclick="return account_showChangeEmail()">Change email address</a><br /></span>
  459. <a href=# onclick="return account_showChangePassword()">Change password</a><span id="p2nextPasswordUpdateTime"></span><br />
  460. <a href=# onclick="return account_showDeleteAccount()">Delete account</a><br />
  461. </span>
  462. <span id="accountCreateLoginTokenSpan" style="display:none"><a href=# onclick="return account_createLoginToken()">Create login token</a><br /></span>
  463. </p>
  464. <br style=clear:both />
  465. </div>
  466. <div id=p2logintokens></div>
  467. <strong>Device Groups</strong>
  468. <span id="p2createMeshLink1"> - <a href=# onclick="return account_createMesh()" class="newMeshBtn"> New</a></span>
  469. <br /><br />
  470. <div id=p2meshes></div>
  471. <div id=p2noMeshFound style="display:none">No device groups.<span id="p2createMeshLink2"> <a href=# onclick="return account_createMesh()"><strong>Get started here!</strong></a></span></div>
  472. <br style=clear:both />
  473. </div>
  474. </div>
  475. <div id=p3 style="display:none">
  476. <div id="p3title"><h1>My Events</h1></div>
  477. <table class="pTable">
  478. <tr>
  479. <td class="h1"></td>
  480. <td class="auto-style1">
  481. <input type="button" style="display:none" value="Download Report" onclick=p3showReportDialog() />
  482. </td>
  483. <td class="auto-style1">
  484. Filter
  485. <select id=p3filterevents onchange=refreshEvents()>
  486. <option notransval=1 value="">All Logs</option>
  487. <option notransval=1 value=agentlog>Agent Logs</option>
  488. <option notransval=1 value=relaylog>Relay Logs</option>
  489. <option notransval=1 value=manual>Manual Logs</option>
  490. <option notransval=1 value=runcommands>Run Command Logs</option>
  491. <option notransval=1 value=batchupload>Batch Upload Logs</option>
  492. <option notransval=1 value=changenode>Change Node Logs</option>
  493. <option notransval=1 value=removenode>Remove Node Logs</option>
  494. </select>
  495. Show
  496. <select id=p3limitdropdown onchange=refreshEvents()>
  497. <option notransval=1 value=60>Last 60</option>
  498. <option notransval=1 value=120>Last 120</option>
  499. <option notransval=1 value=250>Last 250</option>
  500. <option notransval=1 value=500>Last 500</option>
  501. <option notransval=1 value=1000>Last 1000</option>
  502. <option notransval=1 value="">No limit</option>
  503. </select>&nbsp;
  504. <a href=# onclick=p3showDownloadEventsDialog(2)><img src=images/link4.png height=10 width=10 title="Download Events" style=cursor:pointer></a>&nbsp;
  505. </td>
  506. <td class="h2"></td>
  507. </tr>
  508. </table>
  509. <div id=p3events></div>
  510. </div>
  511. <div id=p4 style="display:none">
  512. <div id="p4title"><h1>My Users</h1></div>
  513. <table class="pTable">
  514. <tr>
  515. <td class="h1"></td>
  516. <td class="style14">
  517. <div style="float:right">
  518. <input type=button onclick=showUserBroadcastDialog() style=margin-right:6px value="Broadcast" />
  519. <a href=# onclick=p4downloadUserInfo()><img style="cursor:pointer" title="Download user information" src="images/link4.png" /></a>
  520. <a href=# onclick=p4batchAccountCreate()><img id=p4UserBatchCreate style="cursor:pointer;display:none" title="Batch create many user accounts" src="images/link6.png" /></a>
  521. <img style="cursor:pointer;margin-top:3px;margin-left:6px" id=usersListToolbarSettings src="images/icon-gear.png" width=16 height=16 onclick="onUsersViewSettings()" />
  522. </div>
  523. <div>
  524. <input type=button id=UsersSelectAllButton onclick="p3usersSelectallButtonFunction()" value="Select All" />
  525. <input type=button id=UsersGroupActionButton disabled="disabled" value="Group Action" onclick=p3usersGroupActionFunction() />
  526. <input id=UserNewAccountButton type=button onclick=showCreateNewAccountDialog() value="New Account..." />
  527. <input id=UserSearchInput type=search style=width:120px;margin-left:6px placeholder=Filter onchange=onUserSearchInputChanged() onkeyup=onUserSearchInputChanged() autocomplete=off onfocus=onUserSearchFocus(1) onblur=onUserSearchFocus(0) />
  528. </div>
  529. </td>
  530. <td class="h2"></td>
  531. </tr>
  532. </table>
  533. <div id="p3users"></div>
  534. </div>
  535. <div id=p5 style="display:none">
  536. <div id="p5title"><h1>My Files</h1></div>
  537. <table id="p5toolbar" cellpadding="0" cellspacing="0">
  538. <tr>
  539. <td id="p5filehead" valign=bottom>
  540. <div id="p5rightOfButtons"></div>
  541. <div>
  542. <input type=button id=p5FolderUp disabled="disabled" onclick="return p5folderup();" value="Up" />&nbsp;
  543. <input type=button id=p5SelectAllButton disabled="disabled" onclick="p5selectallfile();" value="Select All" />&nbsp;
  544. <input type=button id=p5RenameFileButton disabled="disabled" value="Rename" onclick="p5renamefile();" />&nbsp;
  545. <input type=button id=p5DeleteFileButton disabled="disabled" value="Delete" onclick="p5deletefile();" />&nbsp;
  546. <input type=button id=p5ViewFileButton disabled="disabled" value="Edit" onclick="p5viewfile()" />&nbsp;
  547. <input type=button id=p5NewFolderButton disabled="disabled" value="New Folder" onclick="p5createfolder();" />&nbsp;
  548. <input type=button id=p5UploadButton disabled="disabled" value="Upload" onclick="p5uploadFile()" />&nbsp;
  549. <input type=button id=p5DownloadButton disabled="disabled" value="Download" onclick="p5downloadButton()" />&nbsp;
  550. <input type=button id=p5CutButton disabled="disabled" value="Cut" onclick="p5copyFile(1)" />&nbsp;
  551. <input type=button id=p5CopyButton disabled="disabled" value="Copy" onclick="p5copyFile(0)" />&nbsp;
  552. <input type=button id=p5PasteButton disabled="disabled" value="Paste" onclick="p5pasteFile()" />&nbsp;
  553. </div>
  554. </td>
  555. </tr>
  556. <tr>
  557. <td id="p5filesubhead">
  558. <div style=float:right>
  559. <select id=p5sortdropdown onchange=updateFiles()>
  560. <option value="1" selected="selected">Sort by name</option>
  561. <option value="2">Sort by size</option>
  562. <option value="3">Sort by date</option>
  563. <option value="4">Descend by name</option>
  564. <option value="5">Descend by size</option>
  565. <option value="6">Descend by date</option>
  566. </select>
  567. </div>
  568. <div>&nbsp;&nbsp;<span id="p5currentpath"></span></div>
  569. </td>
  570. </tr>
  571. </table>
  572. <div id="p5filetable">
  573. <!--
  574. <form id=p5fileCatchAll method=post enctype=multipart/form-data action=uploadfile.ashx target=fileUploadFrame>
  575. <input type=file id=p5fileCatchAllInput name=files style="position:absolute;left:0;width:100%;top:0;bottom:0;opacity:0;display:none" onchange="p5fileCatchAllInputChanged(event)" />
  576. <input id=p5fileDragLink2 name="link" style="display:none" />
  577. <input type=submit id=p5fileCatchAllSubmit style="display:none" />
  578. </form>
  579. -->
  580. <div id="p5PublicShare" style=""><div>These files are shared publicly, click "link" to get public url.</div></div>
  581. <div id="bigok" style="display:none"><b>&checkmark;</b></div>
  582. <div id="bigfail" style="display:none"><b>&#10007;</b></div>
  583. <span id="p5files"></span>
  584. </div>
  585. <table id="p5toolbarBottom" style=width:100% cellpadding=0 cellspacing=0>
  586. <tr><td class=style6>&nbsp;<span id="p5bottomstatus"></span></td></tr>
  587. </table>
  588. </div>
  589. <div id=p6 style="display:none">
  590. <div id="p6info" style="overflow-y:auto">
  591. <div id="p6title">
  592. <img id=MainMeshImage src="serverpic.ashx">
  593. <h1>My Server</h1>
  594. </div>
  595. <div id="p2ServerActions">
  596. <p><strong>Server actions</strong></p>
  597. <div style="margin-left:25px">
  598. <div id="p2ServerActionsBackup"><div class="p2AccountActions"><span style="display:none"><strong>&#x2713;</strong></span></div><a id="p6backuplink" href="{{{domainurl}}}backup.zip" rel="noreferrer noopener" target="_blank">Download server backup</a></div>
  599. <div id="p2ServerActionsRestore"><div class="p2AccountActions"><span style="display:none"><strong>&#x2713;</strong></span></div><a href=# onclick="return server_showRestoreDlg()">Restore server with backup</a></div>
  600. <div id="p2ServerActionsGoogleBackup" style="display:none"><div class="p2AccountActions"><span id="p2ServerActionsGoogleBackupCheck" style="display:none"><strong>&#x2713;</strong></span></div><span><a href=# onclick="return server_setupGoogleDriveBackup()">Google Drive backup</a><br /></span></div>
  601. <div id="p2ServerActionsVersion"><div class="p2AccountActions"><span style="display:none"><strong>&#x2713;</strong></span></div><a href=# onclick="return server_showVersionDlg()">Check server version</a></div>
  602. <div id="p2ServerActionsErrors"><div class="p2AccountActions"><span style="display:none"><strong>&#x2713;</strong></span></div><a href=# onclick="return server_showErrorsDlg()">Show server error log</a></div>
  603. <div id="p2ServerActionsConfig"><div class="p2AccountActions"><span style="display:none"><strong>&#x2713;</strong></span></div><a href=# onclick="return server_showConfigDlg()">Show server configuration</a></div>
  604. </div>
  605. <br />
  606. </div>
  607. <strong>Server Statistics</strong><br /><br />
  608. <div id="serverStats">
  609. <div id="serverCpuChartView" style="display:none">
  610. <div class="chartViewCanvas" style="height:40px; text-align:center"><canvas id="serverCpuChart" style="display:inline-block"></canvas></div>
  611. <div class="chartViewText" id="serverCpuChartText"></div>
  612. </div>
  613. <div id="serverMemoryChartView" style="display:none">
  614. <div class="chartViewCanvas" style="height:40px; text-align:center"><canvas id="serverMemoryChart" style="display:inline-block"></canvas></div>
  615. <div class="chartViewText" id="serverMemoryChartText"></div>
  616. </div><br /><br />
  617. <div id="serverStatsTable"></div>
  618. </div>
  619. <div id="serverWarningsDiv" style="display:none">
  620. <br /><strong>Server Warnings</strong><br /><br />
  621. <div id="serverCertWarnings"></div>
  622. <div id="serverWarnings"></div>
  623. </div>
  624. </div>
  625. </div>
  626. <div id=p10 style="display:none">
  627. <div id=p10title>
  628. <div id="p10BackButton"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
  629. <h1>General - <span id=p10deviceName></span></h1>
  630. </div>
  631. <div id=p10info style="overflow-y:auto">
  632. <table style="width:100%" cellpadding="0" cellspacing="0">
  633. <tr>
  634. <td style=width:auto valign=top>
  635. <div id=p10html></div>
  636. </td>
  637. <td style=width:100px;display:none id=notesPanel valign=top>
  638. <table>
  639. <thead>
  640. <tr><th>Notes</th></tr>
  641. </thead>
  642. <tbody>
  643. <tr>
  644. <td><div id=notesPanelArea style=width:300px;height:200px;resize:none;overflow-y:scroll></div></td>
  645. </tr>
  646. </tbody>
  647. </table>
  648. </td>
  649. <td style=width:20px></td>
  650. <td style=width:200px;vertical-align:top;position:relative valign=top>
  651. <div class="deviceNotifyLargeDot">
  652. <div id="p10deviceMsg" onclick=showDeviceMessages(null,null,event) class=deviceNotifyDotSub></div>
  653. <img id="p10deviceStar" class=deviceNotifyDotSub src=images/icon-star-notify-16.png width=16 height=16>
  654. <img id="p10deviceNotify" onclick=showDeviceSessions(null,null,event) class=deviceNotifyDotSub src=images/icon-relay-notify.png width=16 height=16>
  655. <img id="p10deviceHelp" onclick=showDeviceHelpRequests(null,null,event) class=deviceNotifyDotSub src=images/icon-help-notify-16.png width=16 height=16>
  656. </div>
  657. <div id="p10deviceBattery" class="deviceBatteryLarge deviceBatteryLarge1"></div>
  658. <a href=# onclick=p10showiconselector()><img id=MainComputerImage></a>
  659. <div id=MainComputerState></div>
  660. </td>
  661. </tr>
  662. </table><br />
  663. <div id=p10html2></div>
  664. <div id=p10html3></div>
  665. <div id=p10html4></div>
  666. <div id=p10html5></div>
  667. </div>
  668. </div>
  669. <div id=p11 class="noselect" style="display:none">
  670. <div id="p11title">
  671. <div id=p11deviceNameHeader>
  672. <div id="p11BackButton"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
  673. <div id="devListToolbarViewIcons"><div class="viewSelector" onclick=deskToggleFull(event) title="Full Screen. Hold shift to browser full screen."><div class="viewSelector5"></div></div></div>
  674. <h1>Desktop - <span id=p11deviceName></span></h1>
  675. </div>
  676. </div>
  677. <div id="p11warning" onclick="showFeaturesDlg()">
  678. <div class="icon2"></div>
  679. <div class="warningbox">Redirection port or KVM feature is disabled<span id="p11warninga">, click here to enable it.</span></div>
  680. </div>
  681. <div id="p11warning2" onclick="showPowerActionDlg()">
  682. <div class="icon2"></div>
  683. <div class="warningbox">Remote computer is not powered on, click here to issue a power command.</div>
  684. </div>
  685. <div style="position:absolute;right:16px;margin-top:-14px;font-size:x-small;color:black">
  686. <div id="p11capslock" style="display:inline-block;margin-left:1px;border-radius:5px;background-color:#A3FFB8;padding:2px">
  687. CAPS
  688. </div>
  689. <div id="p11scrolllock" style="display:inline-block;margin-left:1px;border-radius:5px;background-color:#A3FFB8;padding:2px">
  690. SCROLL
  691. </div>
  692. <div id="p11numlock" style="display:inline-block;margin-left:1px;border-radius:5px;background-color:#A3FFB8;padding:2px">
  693. NUM
  694. </div>
  695. </div>
  696. <div id=deskarea0 cellpadding=0 cellspacing=0>
  697. <div id=deskarea1 class="areaHead">
  698. <div class="toright2">
  699. <div id="idx_deskFullBtn2" onclick=deskToggleFull(event) style="float:right">&nbsp;&#x2716;</div>
  700. <span id="p11power"></span>&nbsp;
  701. <div class='deskareaicon' title="Toggle View Mode" onclick="toggleAspectRatio(1)">&#8690;</div>
  702. <div class='deskareaicon' title="Rotate Left" onclick="drotate(-1)">&olarr;</div>
  703. <div class='deskareaicon' title="Rotate Right" onclick="drotate(1)">&orarr;</div>
  704. <div id="deskRecordIcon" class='deskareaicon' title="Server is recording this session" style="display:none;background-color:red;width:12px;height:12px;border-radius:6px;margin-top:5px"></div>
  705. <input id="deskFocusBtn" type="button" title="Toggle focus mode, when active only the region around the mouse is updated" onkeypress="return false" onkeydown="return false" value="Focus All" onclick="deskToggleFocus()" style="margin-right:3px;display:none" />
  706. <input id="deskActionsBtn" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() class="mR" />
  707. <input id="deskActionsSettings" type="button" value="Settings..." title="Edit remote desktop settings" onkeypress="return false" onkeydown="return false" onclick="showDesktopSettings()" class="mR" />
  708. <input type="button" title="Change the power state of the remote machine" onkeypress="return false" onkeydown="return false" value="Power Actions..." onclick="showPowerActionDlg()" style="display:none" />
  709. <div id="desktopCustomUpperRight" style="float:left;margin-right:6px"></div>
  710. <div id="desktopCustomUiButtons" style="float:left"></div>
  711. </div>
  712. <div>
  713. <input type="button" id="autoconnectbutton1" value="AutoConnect" onclick=autoConnectDesktop(event) onkeypress="return false" onkeydown="return false" style="display:none;margin-right:4px" />
  714. <span id=connectbutton1span><input type=button id=connectbutton1 cmenu="deskConnectButton" title="Connect using MeshAgent remote desktop" value="Connect" onclick=connectDesktop(event,3) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span>
  715. <span id=connectbutton1rspan><input type=button id=connectbutton1r cmenu="altPortContextMenu" value="RDP Connect" title="Connect using RDP" onclick=askRdpCredentials() onkeypress="return false" onkeydown="return false" disabled="disabled" /></span>
  716. <span id=connectbutton1hspan><input type=button id=connectbutton1h value="HW Connect" title="Connect using hardware KVM" onclick=connectDesktop(event,2) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span>
  717. <span id=disconnectbutton1span><input type=button id=disconnectbutton1 cmenu="deskDisconnectButton" value="Disconnect" onclick=connectDesktop(event,0) onkeypress="return false" onkeydown="return false" /></span>
  718. <span id="deskstatus" style="line-height:22px">Disconnected</span><span id="deskmetadata"></span>
  719. </div>
  720. </div>
  721. <div id=deskarea3x>
  722. <div id="p11bigok" style="display:none"><b>&checkmark;</b></div>
  723. <div id="p11bigfail" style="display:none"><b>&#10007;</b></div>
  724. <div id=DeskFocus oncontextmenu="return false" onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event)></div>
  725. <div id=DeskParent>
  726. <canvas id=Desk width=640 height=480 oncontextmenu="return false" onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event) onmousewheel=dmousewheel(event)></canvas>
  727. </div>
  728. <div id=DeskTools>
  729. <div id=deskToolsAreaTop>
  730. <a id=DeskToolsRefreshButton style="right:2px" onclick="refreshDeskTools()">Refresh</a>
  731. <div id=deskToolsTopTabProcess class="deskToolsTopTab" onclick="changeDeskToolTab(0)" style="left:0px;bottom:0px">Processes</div>
  732. <div id=deskToolsTopTabService class="deskToolsTopTab" onclick="changeDeskToolTab(1)" style="display:none;left:90px;color:gray">Services</div>
  733. </div>
  734. <div id=deskToolsArea>
  735. <div id="DeskToolsProcessTab">
  736. <div id=deskToolsHeader>
  737. <a class="colmn1" title="Sort by process id" onclick=sortProcess(0)>PID</a>
  738. <a class="colmn2" title="Sort by name" onclick=sortProcess(1)>Name</a>
  739. </div>
  740. <div id="DeskToolsProcesses" style=""></div>
  741. </div>
  742. <div id="DeskToolsServiceTab" style="display:none">
  743. <div id=deskToolsServiceHeader>
  744. <a class="colmn1" style="width:70px" title="Sort by state" onclick=sortService(0)>State</a>
  745. <a class="colmn2" title="Sort by name" onclick=sortService(1)>Name</a>
  746. </div>
  747. <div id="DeskToolsServices" style=""></div>
  748. </div>
  749. </div>
  750. </div>
  751. <div id=p11DeskConsoleMsg style="display:none;text-align:left;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>
  752. <div id=p11DeskSessionSelector style="display:none;position:absolute;left:30px;top:17px;right:30px;bottom:17px;overflow-y:auto"></div>
  753. </div>
  754. <div id=deskarea4 class="areaFoot">
  755. <div class="toright2">
  756. <span id="DeskLatency" style="line-height:22px;width:50px" title="Desktop Session Latency"></span>
  757. <span id="DeskTimer" style="line-height:22px" title="Session time"></span>
  758. <input id=DeskToolsButton type=button value=Tools title="Toggle tools view" onkeypress="return false" onkeydown="return false" onclick="toggleDeskTools()" />
  759. <span>&nbsp;</span>
  760. <span id=DeskRunButton cmenu="deskPreConfigScriptContextMenu" class="deskarea" title="Run a script on this computer"><img class="desktopButtons" src='images/icon-play.png' onclick=runDeviceCmd() height=16 width=16 style=padding-top:2px /></span>
  761. <span id=DeskChatButton class="deskarea" title="Open chat window to this computer"><img class="desktopButtons" src='images/icon-chat.png' onclick=deviceChat(event) height=16 width=16 style=padding-top:2px /></span>
  762. <span id=DeskNotifyButton title="Display a notification on the remote computer"><img class="desktopButtons" src='images/icon-notify.png' onclick=deviceToastFunction() height=16 width=16 style=padding-top:2px /></span>
  763. <span id=DeskLockButton title="Lock the remote computer"><img src='images/icon-lock.png' class="desktopButtons" onclick=deviceLockFunction() height=16 width=16 /></span>
  764. <span id=DeskOpenWebButton title="Open a web address on the remote computer"><img class="desktopButtons" src='images/icon-url2.png' onclick=deviceUrlFunction() height=16 width=16 /></span>
  765. <span id=DeskBackgroundButton title="Toggle remote desktop background"><img class="desktopButtons" id=DeskBackgroundButtonImage src='images/icon-background.png' onclick=deviceToggleBackground(event) height=16 width=16 /></span>
  766. <span id=DeskSaveImageButton title="Save a screenshot of the remote desktop"><img class="desktopButtons" src='images/icon-camera.png' onclick=deskSaveImage() height=16 width=16 /></span>
  767. <span id=DeskRecordButton cmenu=deskPlayerContextMenu title="Record remote desktop session to file" style="display:none"><img class="desktopButtons" id=DeskRecordButtonImage src='images/icon-film.png' onclick=deskRecordSession() height=16 width=16 /></span>
  768. <span id=DeskClipboardInButton title="Download remote clipboard to local clipboard" style="display:none"><img class="desktopButtons" id=DeskClipboardInButtonImage src='images/icon-clipboard-in.png' onclick=deskClipboardInFunction() height=16 width=16 /></span>
  769. <span id=DeskClipboardOutButton title="Upload local clipboard to remote device"><img class="desktopButtons" id=DeskClipboardOutButtonImage src='images/icon-clipboard-out.png' onclick=deskClipboardOutFunction() height=16 width=16 /></span>
  770. <span id=DeskRefreshButton title="Refresh the desktop"><img class="desktopButtons" id=DeskRefreshButtonImage src='images/icon-refresh.png' onclick=deskRefreshFunction() height=16 width=16 /></span>
  771. <span id=DeskInputLockedButton title="Remote input is locked"><img class="desktopButtons" id=DeskInputLockedButtonImage src='images/icon-keylock-red.png' onclick=deskInputLockFunction(0) height=16 width=16 /></span>
  772. <span id=DeskInputUnLockedButton title="Remote input is unlocked"><img class="desktopButtons" id=DeskInputUnLockedButtonImage src='images/icon-keylock.png' onclick=deskInputLockFunction(1) height=16 width=16 /></span>
  773. <span id=DeskGuestShareButton title="Share the device with a guest"><img class="desktopButtons" id=DeskGuestShareButtonImage src='images/icon-share2.png' onclick=showShareDevice() height=16 width=16 /></span>
  774. </div>
  775. <div class="toright2"><span id=DeskMonitorSelectionSpan></span></div>
  776. <div>
  777. <select id="deskkeys" cmenu=deskKeyShortcutContextMenu></select>
  778. <input id="DeskWD" type=button value="Send" onkeypress="return false" onkeydown="return false" onclick="deskSendKeys()" />
  779. <input id="DeskESC" style="display:none" type="button" value="ESC" onkeypress="return false" onkeydown="return false" onclick="sendDeskEsc()" />
  780. <input id="DeskClip" type="button" value="Clipboard" onkeypress="return false" onkeydown="return false" onclick="showDeskClip()" />
  781. <input id="DeskType" cmenu="deskPreConfigShortcutContextMenu" type="button" value="Type" onkeypress="return false" onkeydown="return false" onclick="showDeskType()" />
  782. <label><span id="DeskControlSpan" title="Toggle mouse and keyboard input"><input id="DeskControl" type="checkbox" onkeypress="return false" onkeydown="return false" onclick="toggleKvmControl()" />Input</span></label>&nbsp;
  783. </div>
  784. </div>
  785. </div>
  786. </div>
  787. <div id=p12 style="display:none">
  788. <div id="p12title">
  789. <div id="p12BackButton"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
  790. <div id="devListToolbarViewIcons2" style="float:right"><div class="viewSelector" onclick=deskToggleFull(event) title="Full Screen. Hold shift to browser full screen."><div class="viewSelector5"></div></div></div>
  791. <h1>Terminal - <span id=p12deviceName></span></h1>
  792. </div>
  793. <div id="p12warning" onclick=showFeaturesDlg()>
  794. <div class="icon2"></div>
  795. <div class="warningbox">Redirection port or KVM feature is disabled<span id="p12warninga">, click here to enable it.</span></div>
  796. </div>
  797. <div id="p12warning2" onclick=showPowerActionDlg()>
  798. <div class="icon2"></div>
  799. <div class="warningbox">Remote computer is not powered on, click here to issue a power command.</div>
  800. </div>
  801. <div id=termTable style="position:relative">
  802. <table style="width:100%" cellpadding=0 cellspacing=0>
  803. <tr>
  804. <td class="areaHead" style="line-height:22px">
  805. <div class="toright2">
  806. <div id="idx_termFullBtn2" onclick=deskToggleFull(event) style="float:right">&nbsp;&#x2716;</div>
  807. <div id="termRecordIcon" class='deskareaicon' title="Server is recording this session" style="display:none;background-color:red;width:12px;height:12px;border-radius:6px;margin-top:5px;margin-left:5px"></div>
  808. <input id="termActionsBtn" type=button title="Perform power actions on the device" onkeypress="return false" onkeydown="return false" value=Actions onclick=deviceActionFunction() />
  809. <div id="terminalCustomUpperRight" style="float:left;margin-right:6px"></div>
  810. <div id="terminalCustomUiButtons" style="float:left"></div>
  811. </div>
  812. <div>
  813. <input type="button" id="autoconnectbutton2" value="AutoConnect" onclick=autoConnectTerminal(event) onkeypress="return false" onkeydown="return false" style="display:none" /><span id="connectbutton2span" style="margin-right:4px"><input type="button" id="connectbutton2" cmenu="termConnectButton" value="Connect" onclick=connectTerminal(event,1) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span><span id="connectbutton2sspan" style="margin-right:4px"><input type="button" id="connectbutton2s" cmenu=sshPortContextMenu value="SSH Connect" onclick=connectTerminal(event,3) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span><span id="connectbutton2hspan" style="margin-right:4px"><input type="button" id="connectbutton2h" value="HW Connect" title="Connect using Intel&reg; AMT hardware KVM" onclick=connectTerminal(event,2) onkeypress="return false" onkeydown="return false" disabled="disabled" /></span><span id="disconnectbutton2span" style="margin-right:4px"><input type="button" id="disconnectbutton2" value="Disconnect" onclick=connectTerminal(event,0) onkeypress="return false" onkeydown="return false" /></span>
  814. <span id="termstatus" style="line-height:22px">Disconnected</span><span id="termtitle"></span>
  815. </div>
  816. </td>
  817. </tr>
  818. <tr>
  819. <td id="termarea3x">
  820. <div id="termarea3xdiv" style="width:100%;height:100%;text-align:left"></div>
  821. <pre id="Term"></pre>
  822. </td>
  823. </tr>
  824. <tr>
  825. <td class="areaFoot">
  826. <div class="toright2">
  827. <span id="TermTimer" title="Session time"></span>&nbsp;
  828. <span id="terminalSettingsButtons" style="display:none">
  829. <input id="id_tcrbutton" type="button" onkeypress="return false" onkeydown="return false" class="bottombutton" value="CR+LF" title="Toggle what the return key will send" onclick="termToggleCr()" />
  830. <input id="id_tfxkeysbutton" type="button" onkeypress="return false" onkeydown="return false" class="bottombutton" value="Intel (F10 = ESC+[OM)" title="Toggle F1 to F10 keys emulation type" onclick="termToggleFx()" />
  831. <input id="id_ttypebutton" type="button" onkeypress="return false" onkeydown="return false" class="bottombutton" value="Extended Ascii" title="Toggle terminal emulation type" onclick="termToggleType()" />
  832. </span>
  833. <span id="terminalSizeDropDown" style="display:none">
  834. <select id="termSizeList" onkeypress="return false"><option value="1">80x25</option><option value="2">100x30</option></select>
  835. </span>
  836. <span id="specialKeyDropDown">
  837. <select id="specialkeylist" onkeypress="return false"></select>
  838. <input id="specialkeylistinput" type="button" onkeypress="return false" class="bottombutton" value="Send" title="Send the selected special key" onclick="sendSpecialKey()" />
  839. </span>
  840. </div>
  841. <div>
  842. &nbsp;
  843. <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="ctrlcbutton" value="Ctl-C" onclick="termSendKey(3,'ctrlcbutton')" />
  844. <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="ctrlxbutton" value="Ctl-X" onclick="termSendKey(24,'ctrlxbutton')" />
  845. <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="escbutton" value="ESC" onclick="termSendKey(27,'escbutton')" />
  846. <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="bsbutton" value="Backspace" onclick="termSendKey(8,'bsbutton')" style="display:none" />
  847. <input type=button onkeypress="return false" onkeydown="return false" class="bottombutton" id="pastebutton" value="Paste" title="Paste text into the terminal" onclick="showTermPasteDialog()" style="display:none" />
  848. </div>
  849. </td>
  850. </tr>
  851. </table>
  852. <div id=p12TermConsoleMsg style="display:none;text-align:left;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>
  853. </div>
  854. </div>
  855. <div id=p13 style="display:none">
  856. <div id="p13title">
  857. <div id="p13BackButton" style="float:left"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
  858. <h1>Files - <span id=p13deviceName></span></h1>
  859. </div>
  860. <table id="p13toolbar" cellpadding="0" cellspacing="0">
  861. <tr>
  862. <td class="areaHead" style="line-height:22px">
  863. <div class="toright2">
  864. <input id="filesActionsBtn" type=button title="Perform power actions on the device" value=Actions onclick=deviceActionFunction() />
  865. <div id="filesRecordIcon" class='deskareaicon' title="Server is recording this session" style="display:none;background-color:red;width:12px;height:12px;border-radius:6px;margin-top:5px;margin-left:5px"></div>
  866. <div id="filesCustomUpperRight" style="float:left;margin-right:6px"></div>
  867. <div id="filesCustomUiButtons" style="float:left"></div>
  868. </div>
  869. <div>
  870. <input id=p13AutoConnect value="AutoConnect" onclick=autoConnectFiles(event) type="button" style="display:none" />
  871. <input id=p13Connect value="Connect" cmenu="filesConnectButton" onclick=connectFiles(event,1) type="button" />
  872. <input id=p13Disconnect value="Disconnect" onclick=connectFiles(event) type="button" />
  873. <input id=p13Connects value="SFTP Connect" cmenu=sshPortContextMenu onclick=connectFiles(event,2) type="button" />
  874. <span id=p13Status>Disconnected</span>
  875. </div>
  876. </td>
  877. </tr>
  878. <tr>
  879. <td class="areaHead2" valign=bottom>
  880. <div id="p13rightOfButtons" class="toright2"></div>
  881. <div>
  882. <input type=button style="margin-right:2px" disabled="disabled" id=p13FolderUp value="Up" onclick="p13folderup()" />
  883. <input type=button style="margin-right:2px" disabled="disabled" id=p13SelectAllButton value="Select All" onclick="p13selectallfile()" />
  884. <input type=button style="margin-right:2px" disabled="disabled" id=p13RenameFileButton value="Rename" onclick="p13renamefile()" />
  885. <input type=button style="margin-right:2px" disabled="disabled" id=p13DeleteFileButton value="Delete" onclick="p13deletefile()" />
  886. <input type=button style="margin-right:2px" disabled="disabled" id=p13ViewFileButton value="Edit" onclick="p13viewfile()" />
  887. <input type=button style="margin-right:2px" disabled="disabled" id=p13NewFolderButton value="New Folder" onclick="p13createfolder()" />
  888. <input type=button style="margin-right:2px" disabled="disabled" id=p13NewFileButton value="New File" onclick="p13createfile()" />
  889. <input type=button style="margin-right:2px" disabled="disabled" id=p13UploadButton value="Upload" onclick="p13uploadFile()" />
  890. <input type=button style="margin-right:2px" disabled="disabled" id=p13DownloadButton value="Download" onclick="p13downloadButton()" />
  891. <input type=button style="margin-right:2px" disabled="disabled" id=p13CutButton value="Cut" onclick="p13copyFile(1)" />
  892. <input type=button style="margin-right:2px" disabled="disabled" id=p13CopyButton value="Copy" onclick="p13copyFile(0)" />
  893. <input type=button style="margin-right:2px" disabled="disabled" id=p13PasteButton value="Paste" onclick="p13pasteFile()" />
  894. <input type=button style="margin-right:2px" disabled="disabled" id=p13ZipButton value="Zip" onclick="p13zipFiles()" />
  895. <input type=button style="margin-right:2px" disabled="disabled" id=p13UnzipButton value="Unzip" onclick="p13unzipFile()" />
  896. <input type=button style="margin-right:2px" disabled="disabled" id=p13RefreshButton value="Refresh" onclick="p13folderup(9999)" />
  897. <input type=button style="margin-right:2px" disabled="disabled" id=p13FindButton value="Find" onclick="p13findfile()" />
  898. <input type=button style="margin-right:2px" disabled="disabled" id=p13GoToFolderButton value="GoTo" onclick="p13gotofolder()" />
  899. <input type=button style="margin-right:2px" disabled="disabled" id=p13OpenButton value="Open" onclick="p13openfilefolder()" />
  900. </div>
  901. </td>
  902. </tr>
  903. <tr>
  904. <td class="areaHead3">
  905. <div class="toright2">
  906. <select id=p13sizedropdown onchange=p13updateFiles()>
  907. <option value=0 selected="selected">Human Readable</option>
  908. <option value=1 title=Bytes>Bytes</option>
  909. <option value=10 title=KB>Kilobytes</option>
  910. <option value=20 title=MB>Megabytes</option>
  911. <option value=30 title=GB>Gigabyte</option>
  912. </select>
  913. <select id=p13sortdropdown onchange=p13updateFiles()>
  914. <option value=1 selected="selected">Sort by name</option>
  915. <option value=2>Sort by size</option>
  916. <option value=3>Sort by date</option>
  917. <option value=4>Descend by name</option>
  918. <option value=5>Descend by size</option>
  919. <option value=6>Descend by date</option>
  920. </select>
  921. </div>
  922. <div>&nbsp;&nbsp;<span id="p13currentpath"></span></div>
  923. </td>
  924. </tr>
  925. </table>
  926. <div id=p13FilesConsoleMsg style="display:none;text-align:left;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>
  927. <div id="p13filetable" style="">
  928. <div id="p13bigok" style="display:none"><b>&checkmark;</b></div>
  929. <div id="p13bigfail" style="display:none"><b>&#10007;</b></div>
  930. <span id="p13files"></span>
  931. </div>
  932. <table id="p13toolbarBottom" cellpadding=0 cellspacing=0>
  933. <tr><td class=style6>&nbsp;<span id="p13bottomstatus"></span></td></tr>
  934. </table>
  935. </div>
  936. <div id=p14 style="display:none">
  937. <div id="p14title">
  938. <div id="p14BackButton" style="float:left"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
  939. <div id="devListToolbarViewIcons"><div class="viewSelector" onclick=deskToggleFull(event) title="Full Screen. Hold shift to browser full screen."><div class="viewSelector5"></div></div></div>
  940. <h1><span id=p14deviceNamePrefix>Intel&reg; AMT</span> - <span id=p14deviceName></span></h1>
  941. </div>
  942. <iframe id=p14iframe src="{{{domainurl}}}commander.ashx"></iframe>
  943. </div>
  944. <div id=p15 style="display:none">
  945. <div id="p15title">
  946. <div id="p15BackButton" style="float:left"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
  947. <h1><span id=p15deviceName></span></h1>
  948. </div>
  949. <table id="consoleTable" cellpadding=0 cellspacing=0>
  950. <tr>
  951. <td class="areaHead">
  952. <div class="toright2">
  953. <div id=p15coreName title="Information about current core running on this agent"></div>
  954. <input type=button id=p15uploadCore value="Agent Action" onclick=p15uploadCore(event) title="Change the agent Java Script code module" />
  955. <img onclick=p15downloadConsoleText() style="cursor:pointer;margin-top:6px" title="Download console text" src="images/link4.png" />
  956. </div>
  957. <div id="p15statetext"></div>
  958. </td>
  959. </tr>
  960. <tr>
  961. <td>
  962. <div class="areaProgress"><div id="consoleprogressbar" style=""></div></div>
  963. </td>
  964. </tr>
  965. <tr>
  966. <td id=p15agentConsole>
  967. <pre id=p15agentConsoleText></pre>
  968. </td>
  969. </tr>
  970. <tr>
  971. <td class="areaFoot">
  972. <table style="width:100%">
  973. <tr>
  974. <td style="width:99%">
  975. <input id=p15consoleText style=width:100% onkeyup=p15consoleSend(event) onfocus=onConsoleFocus(1) onblur=onConsoleFocus(0) />
  976. </td>
  977. <td>&nbsp;</td>
  978. <td id="p15outputselecttd">
  979. <select id=p15outputselect onchange="setupConsole()">
  980. <option id="p15outputselect1" value=1>Agent</option>
  981. <option id="p15outputselect3" value=3>Push</option>
  982. <option id="p15outputselect2" value=2>MQTT</option>
  983. </select>
  984. </td>
  985. <td style="width:1%"><input id="id_p15consoleClear" type="button" class="bottombutton" value="Clear" onclick="p15consoleClear()" /></td>
  986. </tr>
  987. </table>
  988. </td>
  989. </tr>
  990. </table>
  991. </div>
  992. <div id=p16 style="display:none">
  993. <div id="p16title">
  994. <div id="p16BackButton" style="float:left"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
  995. <h1>Events - <span id=p16deviceName></span></h1>
  996. </div>
  997. <table class="pTable">
  998. <tr>
  999. <td class="h1"></td>
  1000. <!--<td>&nbsp;<input type=button onclick=refreshDeviceEvents() value="Refresh" /></td>-->
  1001. <td class="auto-style1">
  1002. Filter
  1003. <select id=p16filterevents onchange=refreshDeviceEvents()>
  1004. <option notransval=1 value="">All Logs</option>
  1005. <option notransval=1 value=agentlog>Agent Logs</option>
  1006. <option notransval=1 value=relaylog>Relay Logs</option>
  1007. <option notransval=1 value=manual>Manual Logs</option>
  1008. <option notransval=1 value=runcommands>Run Command Logs</option>
  1009. <option notransval=1 value=batchupload>Batch Upload Logs</option>
  1010. <option notransval=1 value=changenode>Change Node Logs</option>
  1011. <option notransval=1 value=removenode>Remove Node Logs</option>
  1012. </select>
  1013. Show
  1014. <select id=p16limitdropdown onchange=refreshDeviceEvents()>
  1015. <option notransval=1 value=60>Last 60</option>
  1016. <option notransval=1 value=120>Last 120</option>
  1017. <option notransval=1 value=250>Last 250</option>
  1018. <option notransval=1 value=500>Last 500</option>
  1019. <option notransval=1 value=1000>Last 1000</option>
  1020. </select>
  1021. <a href=# onclick=p3showDownloadEventsDialog(1)><img src=images/link4.png height=10 width=10 title="Download Events" style=cursor:pointer></a>&nbsp;
  1022. </td>
  1023. <td class="h2"></td>
  1024. </tr>
  1025. </table>
  1026. <div id=p16events></div>
  1027. </div>
  1028. <div id=p17 style="display:none;margin-left:-18px">
  1029. <div id="p17title" style="margin-left:18px">
  1030. <div id="p17BackButton" style="float:left"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
  1031. <div id="devListToolbarViewIcons3">
  1032. <div class="viewSelector" onclick=refreshDetails(event) title="Refresh details information."><div class="viewSelector12"></div></div>
  1033. <div class="viewSelector" onclick=deskToggleCpuGraph(event) title="Show device CPU and memory usage."><div class="viewSelector6"></div></div>
  1034. </div>
  1035. <h1>Details - <span id=p17deviceName></span></h1>
  1036. </div>
  1037. <div id=p17info style="overflow-y:auto">
  1038. <div id=p17graph style="width:100%">
  1039. <table style=width:100%>
  1040. <tr>
  1041. <td style=width:64px;vertical-align:top>
  1042. <img src=images/details/graph64.png border=0 width=64 />
  1043. </td>
  1044. <td>
  1045. <div class=DevSt style=margin-bottom:3px;margin-left:16px><b>Live Graph</b></div>
  1046. <div style="margin-bottom:10px;margin-left:16px;height:240px;width:calc(100% - 16px);position:relative">
  1047. <canvas id=deviceDetailsStats style="position:absolute;top:0;left:0;right:0;bottom:0"></canvas>
  1048. </div>
  1049. <div id="extraGraphValues" style="display:none;margin-left:30px;margin-bottom:15px"></div>
  1050. </td>
  1051. </tr>
  1052. </table>
  1053. </div>
  1054. <div id=p17info2></div>
  1055. </div>
  1056. </div>
  1057. <div id=p19 style="display:none">
  1058. <div id="p19title">
  1059. <div id="p19BackButton" style="float:left"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
  1060. <h1>Plugins - <span id=p19deviceName></span></h1>
  1061. </div>
  1062. <div id="p19headers"></div>
  1063. <div id=p19pages></div>
  1064. </div>
  1065. <div id=p20 style="display:none">
  1066. <div id=p20main style="overflow-y:auto">
  1067. <div id="p20title">
  1068. <picture id=MainMeshImage style=border-width:0px;height:200px;width:200px;float:right>
  1069. <source type="image/webp" width=200 height=200 srcset="images/webp/mesh-256.webp" />
  1070. <img alt="" width=200 height=200 src=images/mesh-256.png />
  1071. </picture>
  1072. <div style="float:left"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
  1073. <h1>General - <span id=p20meshName></span></h1>
  1074. </div>
  1075. <p id=p20info></p>
  1076. <p id=p20info2></p>
  1077. </div>
  1078. </div>
  1079. <div id=p21 style="display:none">
  1080. <div id="p21title">
  1081. <div style="float:left"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
  1082. <h1>Summary - <span id=p21meshName></span></h1>
  1083. </div>
  1084. <div id=p21main style="overflow-y:auto">
  1085. <div style="width:100%">
  1086. <div style="display:table;width:93%">
  1087. <div id="meshPowerChartDiv" style="width:23%;display:inline-block;text-align:center;max-width:250px">
  1088. <div style="margin:10px;font-size:16px">Power States</div>
  1089. <canvas id="meshPowerChart" style="width:250px;height:250px"></canvas>
  1090. </div>
  1091. <div id="meshOsChartDiv" style="width:23%;display:inline-block;text-align:center;max-width:250px">
  1092. <div style="margin:10px;font-size:16px">Agent Types</div>
  1093. <canvas id="meshOsChart" style="width:250px;height:250px"></canvas>
  1094. </div>
  1095. <div id="meshConnChartDiv" style="width:23%;display:inline-block;text-align:center;max-width:250px">
  1096. <div style="margin:10px;font-size:16px">Connectivity</div>
  1097. <canvas id="meshConnChart" style="width:250px;height:250px"></canvas>
  1098. </div>
  1099. <div id="meshSecurityChartDiv" style="width:23%;display:inline-block;text-align:center;max-width:250px">
  1100. <div style="margin:10px;font-size:16px">Security</div>
  1101. <canvas id="meshSecurityChart" style="width:250px;height:250px"></canvas>
  1102. </div>
  1103. </div>
  1104. </div>
  1105. <p id=p21info style="overflow-y:auto"></p>
  1106. </div>
  1107. </div>
  1108. <div id=p30 style="display:none">
  1109. <div id="p30title">
  1110. <div style="float:left"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
  1111. <h1>General - <span id=p30userName></span></h1>
  1112. </div>
  1113. <div id="p30info" style="overflow-y:auto">
  1114. <table style="width:100%" cellpadding="0" cellspacing="0">
  1115. <tr>
  1116. <td style=width:auto valign=top>
  1117. <div id=p30html></div>
  1118. </td>
  1119. <td style=width:20px></td>
  1120. <td style=width:200px;position:relative valign=top>
  1121. <img id="p30userAuthServiceLogo" style="display:none" class=userAuthStrategyLogo width=64 height=64>
  1122. <picture id=MainUserImage style="display:none;border-width:0px;height:200px;width:200px;float:right" onclick="account_manageImage(1)">
  1123. <source type="image/webp" width=200 height=200 srcset="images/webp/user-256.webp" />
  1124. <img alt="" width=200 height=200 src=images/user-256.png />
  1125. </picture>
  1126. <img id=MainUserImageEx alt="" width=200 height=200 src=images/user-256.png onclick="account_manageImage(1)" />
  1127. <div style="width:100%;text-align:center"><strong><span id=MainUserState></span></strong></div>
  1128. </td>
  1129. </tr>
  1130. </table><br />
  1131. <div id=p30html2></div>
  1132. <div id=p30html3></div>
  1133. </div>
  1134. </div>
  1135. <div id=p31 style="display:none">
  1136. <div id="p31title">
  1137. <div style="float:left"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
  1138. <h1>Events - <span id=p31userName></span></h1>
  1139. </div>
  1140. <table class="pTable">
  1141. <tr>
  1142. <td class="h1"></td>
  1143. <!--<td>&nbsp;<input type=button onclick=refreshUsersEvents() value="Refresh" /></td>-->
  1144. <td class="auto-style1">
  1145. Filter
  1146. <select id=p31filterevents onchange=refreshUsersEvents()>
  1147. <option notransval=1 value="">All Logs</option>
  1148. <option notransval=1 value=agentlog>Agent Logs</option>
  1149. <option notransval=1 value=relaylog>Relay Logs</option>
  1150. <option notransval=1 value=manual>Manual Logs</option>
  1151. <option notransval=1 value=runcommands>Run Command Logs</option>
  1152. <option notransval=1 value=batchupload>Batch Upload Logs</option>
  1153. <option notransval=1 value=changenode>Change Node Logs</option>
  1154. <option notransval=1 value=removenode>Remove Node Logs</option>
  1155. </select>
  1156. Show
  1157. <select id=p31limitdropdown onchange=refreshUsersEvents()>
  1158. <option notransval=1 value=60>Last 60</option>
  1159. <option notransval=1 value=120>Last 120</option>
  1160. <option notransval=1 value=250>Last 250</option>
  1161. <option notransval=1 value=500>Last 500</option>
  1162. <option notransval=1 value=1000>Last 1000</option>
  1163. <option notransval=1 value="">No limit</option>
  1164. </select>
  1165. <a href=# onclick=p3showDownloadEventsDialog(3)><img src=images/link4.png height=10 width=10 title="Download Events" style=cursor:pointer></a>&nbsp;
  1166. </td>
  1167. <td class="h2"></td>
  1168. </tr>
  1169. </table>
  1170. <div id=p31events style=""></div>
  1171. </div>
  1172. <div id=p40 style="display:none">
  1173. <div id="p40title"><h1>My Server Stats</h1></div>
  1174. <div class="areaHead">
  1175. <div class="toright2">
  1176. <select id=p40server style="display:none" onchange=updateServerTimelineStats()>
  1177. </select>&nbsp;
  1178. <select id=p40type onchange=updateServerTimelineStats()>
  1179. <option value=0>Connections</option>
  1180. <option value=1>Memory</option>
  1181. <option value=5>CPU</option>
  1182. <option value=3>Inbound Traffic</option>
  1183. <option value=4>Outbound Traffic</option>
  1184. </select>&nbsp;
  1185. <select id=p40time onchange=updateServerTimelineHours()>
  1186. <option value=3>Last 3 hours</option>
  1187. <option value=8>Last 8 hours</option>
  1188. <option value=24>Last day</option>
  1189. <option value=168>Last week</option>
  1190. <option value=720>Last 30 days</option>
  1191. </select>&nbsp;
  1192. <img src=images/link4.png height=10 width=10 title="Download data points (.csv)" style=cursor:pointer onclick=p40downloadEvents()>&nbsp;
  1193. </div>
  1194. <div>
  1195. <input value="Refresh" type="button" onclick="refreshServerTimelineStats()" />
  1196. &nbsp;<label><input id=p40log type="checkbox" onclick="updateServerTimelineHours()" />Log-X</label>
  1197. </div>
  1198. </div>
  1199. <canvas id=serverMainStats style=""></canvas>
  1200. </div>
  1201. <div id=p41 style="display:none">
  1202. <div id="p41title"><h1>My Server Tracing</h1></div>
  1203. <div class="areaHead">
  1204. <div class="toright2">
  1205. Show
  1206. <select id=p41limitdropdown onchange=displayServerTrace()>
  1207. <option value=100>Last 100</option>
  1208. <option value=250>Last 250</option>
  1209. <option value=500>Last 500</option>
  1210. <option value=1000>Last 1000</option>
  1211. </select>
  1212. <input value="Clear" type="button" onclick="clearServerTracing()" />
  1213. <img src=images/link4.png height=10 width=10 title="Download trace (.csv)" style=cursor:pointer onclick=p41downloadServerTrace()>&nbsp;
  1214. </div>
  1215. <div>
  1216. <input value="Tracing" type="button" onclick="setServerTracing()" />
  1217. <span id="p41traceStatus">None</span>
  1218. </div>
  1219. </div>
  1220. <div id=p41events style=""></div>
  1221. </div>
  1222. <div id=p42 style="display:none">
  1223. <h1>My Server Plugins</h1>
  1224. <div class="areaHead">
  1225. <div class="toright2">
  1226. </div>
  1227. <div>
  1228. <input value="Download Plugin" type="button" onclick="return pluginHandler.addPluginDlg();" />
  1229. </div>
  1230. </div>
  1231. <div id="pluginRestartNotice" class="areaHead" style="background-color:gold;display:none">
  1232. <div class="toright2">
  1233. <input value="Refresh Agent Cores" type="button" onclick="distributeCore();return false" />
  1234. </div>
  1235. <div style="padding:2px">
  1236. <div style="padding:2px"><b>Notice:</b> Plugins have been altered, this may require agent core update.</div>
  1237. </div>
  1238. </div>
  1239. <table id="p42tbl">
  1240. <tr class="DevSt"><th style="width:26px"></th><th style="width:10px"></th><th class="chName">Name</th><th class="chDescription">Description</th><th class="chSite" style="text-align:center">Link</th><th class="chVersion" style="text-align:center">Version</th><th class="chUpgradeAvail" style="text-align:center">Latest</th><th class="chStatus" style="text-align:center">Status</th><th class="chAction" style="text-align:center">Action</th><th style="width:10px"></th></tr>
  1241. </table>
  1242. <div id="pluginNoneNotice" style="width:100%;text-align:center;padding-top:10px;display:none"><i>No plugins on server.</i></div>
  1243. </div>
  1244. <div id=p43 style="display:none">
  1245. <div id="p43BackButton"><div class="backButton" tabindex=0 onmouseup=go(42) title="Back" onkeypress="if (event.key == 'Enter') go(42)"><div class="backButtonEx"></div></div></div>
  1246. <h1>My Server Plugins - <span id="p43title"></span></h1>
  1247. <iframe id="p43iframe" frameBorder=0 style="width:100%;height:calc(100vh - 245px);max-height:calc(100vh - 245px)"></iframe>
  1248. </div>
  1249. <div id=p50 style="display:none">
  1250. <div id="p50title"><h1>My User Groups</h1></div>
  1251. <table class="pTable">
  1252. <tr>
  1253. <td class="h1"></td>
  1254. <td class="style14">
  1255. <div style="float:right">
  1256. </div>
  1257. <div id="p50userGroupOps">
  1258. <input type=button id=UsersGroupsSelectAllButton onclick="p50usersSelectallButtonFunction()" value="Select All" />
  1259. <input type=button id=UsersGroupsGroupActionButton disabled="disabled" value="Group Action" onclick=p50usersGroupActionFunction() />
  1260. <input id=NewUserGroupButton type=button onclick=showCreateUserGroupDialog(1) value="New Group..." />
  1261. <input id=DuplicateUserGroupButton type=button style=display:none onclick=showCreateUserGroupDialog(2) value="Duplicate Group..." />
  1262. </div>
  1263. </td>
  1264. <td class="h2"></td>
  1265. </tr>
  1266. </table>
  1267. <div id="p50groups"></div>
  1268. </div>
  1269. <div id=p51 style="display:none">
  1270. <div id="p51title">
  1271. <div style="float:left"><div class="backButton" tabindex=0 onclick=goBack() title="Back" onkeypress="if (event.key == 'Enter') goBack()"><div class="backButtonEx"></div></div></div>
  1272. <h1>User Group - <span id=p51groupName></span></h1>
  1273. </div>
  1274. <div id="p51info" style="overflow-y:auto">
  1275. <table style="width:100%" cellpadding="0" cellspacing="0">
  1276. <tr>
  1277. <td style=width:auto valign=top>
  1278. <div id=p51group></div>
  1279. </td>
  1280. <td style=width:20px></td>
  1281. <td style=width:200px valign=top>
  1282. <picture id=MainUserImage style=border-width:0px;height:200px;width:200px;float:right>
  1283. <source type="image/webp" width=200 height=200 srcset="images/webp/group-256.webp" />
  1284. <img alt="" width=200 height=200 src=images/group-256.png />
  1285. </picture>
  1286. <div style="width:100%;text-align:center"><strong><span id=MainUserState></span></strong></div>
  1287. </td>
  1288. </tr>
  1289. </table>
  1290. <div id=p51group2></div>
  1291. <br />
  1292. </div>
  1293. </div>
  1294. <div id=p52 style="display:none">
  1295. <div id="p52title">
  1296. <h1>My User Recordings</h1>
  1297. </div>
  1298. <table class="pTable">
  1299. <tr>
  1300. <td class="h1"></td>
  1301. <td class="style14">
  1302. <div style="float:right">
  1303. <input type=button onclick=openRecodringPlayer() value="Open Player..." />
  1304. </div>
  1305. <div>
  1306. <input type=button onclick=refreshRecodings() value="Refresh" />
  1307. </div>
  1308. </td>
  1309. <td class="h2"></td>
  1310. </tr>
  1311. </table>
  1312. <div id=p52recordings style="overflow-y:auto"></div>
  1313. </div>
  1314. <div id=p60 style="display:none">
  1315. <div id="p60title">
  1316. <h1>My Reports</h1>
  1317. </div>
  1318. <table class="pTable">
  1319. <tr>
  1320. <td class="h1"></td>
  1321. <td class="style14">
  1322. <div style="float:right;line-height:22px">
  1323. <a id="p60downloadReportDiv" style="display:none" href=# onclick=p60downloadReport()><img src=images/link4.png height=10 width=10 title="Download Report" style=cursor:pointer></a>&nbsp;
  1324. </div>
  1325. <div>
  1326. <input type=button onclick=generateReportDialog() value="Generate Report..." />
  1327. </div>
  1328. </td>
  1329. <td class="h2"></td>
  1330. </tr>
  1331. </table>
  1332. <div id=p60report style="overflow-y:auto"></div>
  1333. </div>
  1334. <br id="column_l_bottomgap" />
  1335. </div>
  1336. <div id="footer">
  1337. <div class="footer1">{{{footer}}}</div>
  1338. <div class="footer2">
  1339. <a id="verifyEmailId2" style="display:none" href=# onclick="account_showVerifyEmail()">Verify Email</a>
  1340. &nbsp;<a id="termsLinkFooter" href=terms>Terms &amp; Privacy</a>
  1341. </div>
  1342. </div>
  1343. <div id=dialog class="noselect" style="display:none">
  1344. <div id=dialogHeader>
  1345. <div tabindex=0 id=id_dialogclose onclick=setDialogMode() onkeypress="if (event.key == 'Enter') setDialogMode()">&#x2716;</div>
  1346. <div id=id_dialogtitle></div>
  1347. </div>
  1348. <div id=dialogBody>
  1349. <div id=dialog1>
  1350. <div id=id_dialogMessage style=""></div>
  1351. </div>
  1352. <div id=dialog2 style="">
  1353. <div id=id_dialogOptions></div>
  1354. </div>
  1355. <div id=dialog3 style="">
  1356. <div id=d3upload>
  1357. <div>File Selection</div>
  1358. <select id=d3uploadMode onchange=d3modechange()>
  1359. <option value=1>Local file upload</option>
  1360. <option value=2>Server file selection</option>
  1361. </select>
  1362. </div>
  1363. <div id=d3localmode style="display:none">
  1364. <div>Upload File</div>
  1365. <form id=d3localmodeform method=post enctype=multipart/form-data action=uploadfile.ashx target=fileUploadFrame>
  1366. <input type=text id=d3auth name=auth style="display:none" />
  1367. <input type=text id=d3filter name=filter style="display:none" />
  1368. <input type=text id=d3attrib name=attrib style="display:none" />
  1369. <input type=file id=d3localFile name=files onchange=d3setActions() />
  1370. <input type=submit id=d3submit style="display:none" />
  1371. </form>
  1372. </div>
  1373. <div id=d3servermode>
  1374. <div id=d3serveraction valign=bottom>
  1375. <input type=button id=p3FolderUp disabled="disabled" onclick=d3folderup() value="Up" />&nbsp;<span id=p3CurrentFolder></span>
  1376. </div>
  1377. <div id=d3serverfiles></div>
  1378. </div>
  1379. </div>
  1380. <div id=dialog4 style="">
  1381. <input id="d4WrapButton" type="button" value="Wrap On" onclick="d4ToggleWrap()" />
  1382. <input id="d4SizeButton" type="button" value="Small" onclick="d4ToggleSize()" />
  1383. <input id="d4EncodingButton" type="button" value="Raw" onclick="d4ToggleEncoding()" />
  1384. <input id="d4LineBreakButton" type="button" value="Windows" onclick="d4ToggleLineBreak()" />
  1385. <textarea id=d4editorarea autocomplete="off" style="height:calc(100vh - 286px);width:100%;overflow:scroll;resize:none;white-space:pre"></textarea>
  1386. </div>
  1387. <div id=dialog7 style="">
  1388. <div class="dtab">
  1389. <button id="td7meshkvm" class="tablinks" onclick="changeDesktopSettingsTab(event, 'd7meshkvm')">Agent</button>
  1390. <button id="td7rdpkvm" class="tablinks" onclick="changeDesktopSettingsTab(event, 'd7rdpkvm')">RDP</button>
  1391. <button id="td7amtkvm" class="tablinks" onclick="changeDesktopSettingsTab(event, 'd7amtkvm')">Intel&reg; AMT</button>
  1392. </div>
  1393. <div id="d7meshkvm" class="tabcontent">
  1394. <!--<h4>Agent Remote Desktop</h4>-->
  1395. <div style="margin-top:8px">
  1396. <div>Quality</div>
  1397. <select id="d7bitmapquality" dir="rtl"></select>
  1398. </div>
  1399. <div>
  1400. <div>Scaling</div>
  1401. <select id="d7bitmapscaling" style="" dir="rtl">
  1402. <option selected=selected value=1024>100%</option>
  1403. <option value=896>87.5%</option>
  1404. <option value=768>75%</option>
  1405. <option value=640>62.5%</option>
  1406. <option value=512>50%</option>
  1407. <option value=384>37.5%</option>
  1408. <option value=256>25%</option>
  1409. <option value=128>12.5%</option>
  1410. </select>
  1411. </div>
  1412. <div>
  1413. <div>Frame rate</div>
  1414. <select id="d7framelimiter" dir="rtl">
  1415. <option selected=selected value=50>Fast</option>
  1416. <option value=100>Medium</option>
  1417. <option value=400>Slow</option>
  1418. <option value=1000>Very slow</option>
  1419. </select>
  1420. </div>
  1421. <div>
  1422. <div>Encoding</div>
  1423. <select id="d7encoding" dir="rtl">
  1424. <option value=1>JPEG</option>
  1425. <option value=2>PNG</option>
  1426. <option value=3>TIFF</option>
  1427. <option selected=selected value=4>WEBP</option>
  1428. </select>
  1429. </div>
  1430. <div id="d7desktopOtherSettings">
  1431. <div>Other Settings</div>
  1432. <div id="d7otherset2" style="display:block">
  1433. <label style="display:block"><input type="checkbox" id="d7deskSwapMouse" />Swap Mouse Buttons</label>
  1434. <label style="display:block"><input type="checkbox" id="d7deskrmw" />Reverse Mouse Wheel</label>
  1435. <label style="display:block"><input type="checkbox" id="d7deskRemoteKeyMap" />Use Remote Keyboard Map</label>
  1436. <label style="display:block" id="d7deskAutoClipboardLabel"><input type="checkbox" id="d7deskAutoClipboard" />Automatic Clipboard</label>
  1437. <label style="display:block" id="d7deskAutoLockLabel"><input type="checkbox" id="d7deskAutoLock" />Lock on Disconnect</label>
  1438. </div>
  1439. </div>
  1440. </div>
  1441. <div id="d7amtkvm" class="tabcontent">
  1442. <!--<h4>Intel&reg; AMT Hardware KVM</h4>-->
  1443. <div style="margin-top:8px">
  1444. <div>Image Encoding</div>
  1445. <select id="d7desktopmode">
  1446. <option value="1">RLE8, Fastest</option>
  1447. <option value="2">RLE16, Recommended</option>
  1448. <option value="3">RAW8, Slow</option>
  1449. <option value="4">RAW16, Very Slow</option>
  1450. </select>
  1451. </div>
  1452. <div>
  1453. <div>Other Settings</div>
  1454. <div id="d7otherset" style="display:block">
  1455. <label style="display:block"><input type="checkbox" id="d7showfocus" />Show Focus Tool</label>
  1456. <label style="display:block"><input type="checkbox" id="d7showcursor" />Show Local Mouse Cursor</label>
  1457. <label style="display:block"><input type="checkbox" id="d7localKeyMap" />Local Keyboard Map</label>
  1458. <label style="display:block"><input type="checkbox" id="d7kvmrmw" />Reverse Mouse Wheel</label>
  1459. </div>
  1460. </div>
  1461. </div>
  1462. <div id="d7rdpkvm" class="tabcontent">
  1463. <!--<h4>Remote Desktop Protocol</h4>-->
  1464. <div style="margin-top:8px">
  1465. <div>Display Size</div>
  1466. <select id="d7rdpsize">
  1467. <option notransval=1 value="canvas">Canvas Size</option>
  1468. <option notransval=1 value="browser">Browser Size</option>
  1469. <option notransval=1 value="screen">Screen Size</option>
  1470. <option notransval=1 value="640x480">640x480</option>
  1471. <option notransval=1 value="1024x768">1024x768</option>
  1472. <option notransval=1 value="1280x800">1280x800</option>
  1473. <option notransval=1 value="1440x900">1440x900</option>
  1474. <option notransval=1 value="1600x900">1600x900</option>
  1475. <option notransval=1 value="1680x1050">1680x1050</option>
  1476. <option notransval=1 value="1920x1080">1920x1080</option>
  1477. </select>
  1478. </div>
  1479. <div>
  1480. <div>Options</div>
  1481. <div id="d7rdpflags" style="display:block">
  1482. <label style="display:block"><input type="checkbox" id="d7rdp1" />Disable Wallpaper</label>
  1483. <label style="display:block"><input type="checkbox" id="d7rdp2" />Disable Full Window Drag</label>
  1484. <label style="display:block"><input type="checkbox" id="d7rdp3" />Disable Menu Animations</label>
  1485. <label style="display:block"><input type="checkbox" id="d7rdp4" />Disable Theming</label>
  1486. <label style="display:block"><input type="checkbox" id="d7rdp6" />Disable Cursor Shadow</label>
  1487. <label style="display:block"><input type="checkbox" id="d7rdp7" />Disable Cursor Settings</label>
  1488. <label style="display:block"><input type="checkbox" id="d7rdp8" />Enable Font Smoothing</label>
  1489. <label style="display:block"><input type="checkbox" id="d7rdp9" />Enable Desktop Composision</label>
  1490. <label style="display:block"><input type="checkbox" id="d7rdpclip" />Automatic Clipboard</label>
  1491. <label style="display:block"><input type="checkbox" id="d7rdpsmb" />Swap Mouse Buttons</label>
  1492. <label style="display:block"><input type="checkbox" id="d7rdprmw" />Reverse Mouse Wheel</label>
  1493. </div>
  1494. </div>
  1495. </div>
  1496. </div>
  1497. </div>
  1498. <div id="idx_dlgButtonBar">
  1499. <input id="idx_dlgCancelButton" type="button" value="Cancel" style="" onclick="dialogclose(0)" />
  1500. <input id="idx_dlgOkButton" type="button" value="OK" style="" onclick="dialogclose(1)" />
  1501. <div><input id="idx_dlgDeleteButton" type="button" value="Delete" style="display:none" onclick="dialogclose(2)" /></div>
  1502. <div id="idx_dlgMoreButtons">
  1503. <a href=# id=idx_dlgMoreButtons1 style=cursor:pointer;color:gray;text-decoration:none title="Toggle advanced options" onclick=MoreToggle(true)>&#x25BC;</a>
  1504. <a href=# id=idx_dlgMoreButtons2 style=cursor:pointer;color:gray;text-decoration:none title="Toggle advanced options" onclick=MoreToggle(false)>&#x25B2;</a>
  1505. </div>
  1506. </div>
  1507. </div>
  1508. <iframe name="fileUploadFrame" style="display:none"></iframe>
  1509. <form style="display:none" method=post action=uploadfile.ashx enctype=multipart/form-data target=fileUploadFrame><input id=p5fileDragName name="name" /><input id=p5fileDragAuthCookie name="auth" /><input id=p5fileDragSize name="size" /><input id=p5fileDragType name="type" /><input id=p5fileDragData name="data" /><input id=p5fileDragLink name="link" /><input type=submit id=p5loginSubmit2 style="display:none" /></form>
  1510. <form style="display:none" method=post action=uploadnodefile.ashx enctype=multipart/form-data target=fileUploadFrame><input id=p13fileDragName name="name" /><input id=p13fileDragSize name="size" /><input id=p13fileDragType name="type" /><input id=p13fileDragData name="data" /><input id=p13fileDragLink name="link" /><input type=submit id=p13loginSubmit2 style="display:none" /></form>
  1511. <audio id="chimes"><source src="sounds/chimes.mp3" type="audio/mp3" /></audio>
  1512. <iframe style=display:none name="fileDownloadFrame"></iframe>
  1513. </div>
  1514. <script type="text/javascript">
  1515. 'use strict';
  1516. var random = '{{{randomlength}}}' // Random length string for BREACH mitigation
  1517. // Process server-side web state
  1518. var webState = '{{{webstate}}}';
  1519. if (webState != '') { webState = JSON.parse(decodeURIComponent(webState)); }
  1520. if ((webState == null) || (typeof webState != 'object')) { webState = {}; }
  1521. for (var i in webState) { try { localStorage.setItem(i, webState[i]); } catch (ex) {} }
  1522. if (webState && !webState.loctag) { try { delete localStorage.removeItem('loctag'); } catch (ex) { } }
  1523. var args, urlargs;
  1524. var autoReconnect = true;
  1525. var powerStatetable = ['', "Powered", "Sleep", "Sleep", "Sleep", "Hibernating", "Power off", "Present"];
  1526. var StatusStrs = ["Disconnected", "Connecting...", "Setup...", "Connected", "Intel&reg; AMT Connected"];
  1527. 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"];
  1528. var agentsStrNoAgent = ['', '', '', '', "Windows", '', "Linux", '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', "Apple"];
  1529. var sort = 0;
  1530. var searchFocus = 0;
  1531. var mapSearchFocus = 0;
  1532. var userSearchFocus = 0;
  1533. var consoleFocus = 0;
  1534. var showRealNames = false;
  1535. var meshserver = null;
  1536. var meshes = {};
  1537. var loginTokens = {};
  1538. var meshcount = 0;
  1539. var nodes = null;
  1540. var usergroups = null;
  1541. var filetree = {};
  1542. var userinfo = null;
  1543. var serverinfo = null;
  1544. var events = [];
  1545. var users = null;
  1546. var wssessions = null;
  1547. var stars = {}; // Devices that have been "stared" by the user.
  1548. var nodeShortIdent = 0;
  1549. var desktop;
  1550. var desktopsettings = { encoding: 2, showfocus: false, showmouse: true, showcad: true, quality: 40, scaling: 1024, framerate: 50, localkeymap: false, swapmouse: false, remotekeymap: false, autoclipboard: false, autolock: false, agentencoding: 4 };
  1551. var multidesktopsettings = { quality: 20, scaling: 128, framerate: 1000, agentencoding: 4 };
  1552. var terminal;
  1553. var files;
  1554. var debugLevel = parseInt('{{{debuglevel}}}');
  1555. var features = parseInt('{{{features}}}');
  1556. var features2 = parseInt('{{{features2}}}');
  1557. var sessionTime = parseInt('{{{sessiontime}}}');
  1558. var webRelayPort = parseInt('{{{webRelayPort}}}');
  1559. var webRelayDns = '{{{webRelayDns}}}';
  1560. var hidePowerTimeline = '{{{hidePowerTimeline}}}';
  1561. var showNotesPanel = '{{{showNotesPanel}}}';
  1562. var sessionRefreshTimer = null;
  1563. var domain = '{{{domain}}}';
  1564. var domainUrl = '{{{domainurl}}}';
  1565. var authCookie = '{{{authCookie}}}';
  1566. var authRelayCookie = '{{{authRelayCookie}}}';
  1567. var logoutControls = JSON.parse(decodeURIComponent('{{{logoutControls}}}'));
  1568. var authCookieRenewTimer = null;
  1569. var multiDesktop = {};
  1570. var serverPublicNamePort = '{{{serverDnsName}}}:{{{serverPublicPort}}}';
  1571. var amtScanResults = null;
  1572. var debugmode = 0;
  1573. var windowsBrowser = detectWindowsBrowser();
  1574. var attemptWebRTC = ((features & 128) != 0);
  1575. var webrtcconfiguration = '{{{webrtcconfig}}}';
  1576. if (webrtcconfiguration == '') { webrtcconfiguration = null; } else { try { webrtcconfiguration = JSON.parse(decodeURIComponent(webrtcconfiguration)); } catch (ex) { console.log('Invalid WebRTC config: "' + webrtcconfiguration + '".'); webrtcconfiguration = null; } }
  1577. var passRequirements = '{{{passRequirements}}}';
  1578. if (passRequirements != '') { passRequirements = JSON.parse(decodeURIComponent(passRequirements)); }
  1579. var customui = '{{{customui}}}';
  1580. if (customui != '') { customui = JSON.parse(decodeURIComponent(customui)); } else { customui = null; }
  1581. var deskAspectRatio = 0;
  1582. try { deskAspectRatio = parseInt(getstore('deskAspectRatio', '0')); } catch (ex) { }
  1583. var uiMode = parseInt(getstore('uiMode', 1));
  1584. var webPageStackMenu = false;
  1585. var webPageFullScreen = true;
  1586. var nightMode = setNightMode();
  1587. var footerBar = (getstore('footerBar', '1') == '1');
  1588. var sessionActivity = Date.now();
  1589. var updateSessionTimer = null;
  1590. var pluginHandlerBuilder = {{{pluginHandler}}};
  1591. var pluginHandler = null;
  1592. if (pluginHandlerBuilder != null) { pluginHandler = new pluginHandlerBuilder(); }
  1593. var installedPluginList = null;
  1594. var goBackStack = [];
  1595. var CollapsedGroups = {};
  1596. var collapseGroups = '{{{collapseGroups}}}';
  1597. try { CollapsedGroups = JSON.parse(getstore('_collapse', '{}')); } catch(ex) {}
  1598. var deviceViewSettings = {};
  1599. try { deviceViewSettings = JSON.parse(getstore('_deviceViewSettings', '{}')); } catch(ex) {}
  1600. var userViewSettings = {};
  1601. try { userViewSettings = JSON.parse(getstore('_usersViewSettings', '{}')); } catch(ex) {}
  1602. var xterm = null;
  1603. var xtermfit = null;
  1604. var xtermResizeTimer = null;
  1605. var miscState = {};
  1606. var checkedNodeids = {};
  1607. var deskKeyboardShortcuts = [];
  1608. var deskKeyboardStrings = [];
  1609. var deskLastClipboardSent = null;
  1610. var requestedLastConnects = false;
  1611. var devicePagingState = null;
  1612. // Console Message Display Timers
  1613. var p11DeskConsoleMsgTimer = null;
  1614. var p12TermConsoleMsgTimer = null;
  1615. var p13FilesConsoleMsgTimer = null;
  1616. /*
  1617. // Check browser dark mode preference
  1618. var prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
  1619. var prefersLightScheme = window.matchMedia('(prefers-color-scheme: light)').matches;
  1620. console.log(prefersDarkScheme, prefersLightScheme);
  1621. var mql = window.matchMedia('(prefers-color-scheme: dark)');
  1622. mql.addEventListener('change', function() { console.log('Dark Change'); });
  1623. */
  1624. // Check if WebP is supported
  1625. var webpSupport = false;
  1626. check_webp_feature('lossy', function (f, x) {
  1627. webpSupport = x;
  1628. if (!x) {
  1629. d7encoding.options[1].disabled = true;
  1630. d7encoding.value = 1;
  1631. }
  1632. });
  1633. function startup() {
  1634. if ((features & 32) == 0) {
  1635. // Guard against other site's top frames (web bugs).
  1636. var loc = null;
  1637. try { loc = top.location.toString().toLowerCase(); } catch (e) { }
  1638. if (top != self && (loc == null || top.active == false)) { top.location = self.location; return; }
  1639. }
  1640. // Fetch URL arguments & do sanitation
  1641. urlargs = parseUriArgs();
  1642. if (urlargs.key != null) { urlargs.key = "" + urlargs.key; }
  1643. if (urlargs.key && (isAlphaNumeric(urlargs.key) == false)) { delete urlargs.key; }
  1644. if (urlargs.locale && (isAlphaNumeric(urlargs.locale) == false)) { delete urlargs.locale; }
  1645. delete urlargs.user;
  1646. delete urlargs.pass;
  1647. delete urlargs.viewmode;
  1648. delete urlargs.gotonode;
  1649. delete urlargs.gotodevicename;
  1650. delete urlargs.gotodevicername;
  1651. delete urlargs.gotodeviceip;
  1652. delete urlargs.gotomesh;
  1653. delete urlargs.gotouser;
  1654. delete urlargs.gotougrp;
  1655. // Fix links if a loginKey is used
  1656. if (urlargs.key) { Q('termsLinkFooter').href += '?key=' + urlargs.key; }
  1657. // Check if we are in debug mode
  1658. args = parseUriArgs();
  1659. if (args.key && (isAlphaNumeric(args.key) == false)) { delete args.key; }
  1660. if (args.locale && (isAlphaNumeric(args.locale) == false)) { delete args.locale; }
  1661. if (!args.locale) { var x = getstore('loctag', 0); if ((x != null) && (x != '*')) { args.locale = x; } }
  1662. debugmode = args.debug;
  1663. //attemptWebRTC = false; // For now, default WebRTC off unless we set it in the URL.
  1664. if (args.webrtc != null) { attemptWebRTC = (args.webrtc == 1); }
  1665. QV('p13AutoConnect', debugmode); // Files
  1666. QV('autoconnectbutton2', debugmode); // Terminal
  1667. QV('autoconnectbutton1', debugmode); // Desktop
  1668. //QV('DeskClip', debugmode); // Clipboard feature, not completed so show in in debug mode only.
  1669. if (nightMode) { QC('body').add('night'); QS('body')['background-color'] = '#000'; }
  1670. toggleFullScreen();
  1671. // Setup stared devices
  1672. try { stars = JSON.parse(getstore('stars', '{}')); } catch (ex) {}
  1673. // Setup page visuals
  1674. var hide = 0;
  1675. var globalHide = parseInt('{{{hide}}}');
  1676. if (globalHide || args.hide) {
  1677. if (args.hide) { hide = parseInt(args.hide); }
  1678. if (globalHide) { hide = (hide | globalHide); }
  1679. }
  1680. args.hide = hide;
  1681. QV('uiViewButton5', !(args.hide & 4)); // Hide the footer toggle button if footer is hidden anyway.
  1682. adjustPanels();
  1683. // Setup logout control
  1684. var logoutControl = '';
  1685. if (logoutControls) {
  1686. if (logoutControls.name != null) { logoutControl = ('<span onmouseup=go(2) CLASS="LogoffLinkColor" style="cursor:pointer">' + format("Welcome {0}.", logoutControls.name) + '</span>'); }
  1687. if (logoutControls.logoutUrl != null) { logoutControl += format(' <a href="' + logoutControls.logoutUrl + '" CLASS="LogoffLinkColor">' + "Logout" + '</a>'); }
  1688. }
  1689. if (args.hide & 1) { QH('logoutControlSpan2', logoutControl); } else { QH('logoutControlSpan', logoutControl); }
  1690. // Setup the context menu
  1691. document.onclick = function (e) { hideContextMenu(); }
  1692. document.onkeypress = ondockeypress;
  1693. document.onkeydown = ondockeydown;
  1694. document.onkeyup = ondockeyup;
  1695. //window.addEventListener('focus', ondocfocus, false);
  1696. window.addEventListener('blur', ondocblur, false);
  1697. window.onresize = function () {
  1698. hideContextMenu();
  1699. mainUpdate(512);
  1700. browserfullscreen = isBrowserFullscreen();
  1701. QV('DeskESC', browserfullscreen);
  1702. if ((xtermfit != null) && (xxcurrentView == 12)) { xtermfit.fit(); }
  1703. }
  1704. setTimeout(function() {
  1705. if (urlargs.filter) { Q('SearchInput').value = urlargs.filter; Q('KvmSearchInput').value = urlargs.filter; }
  1706. mainUpdate(512);
  1707. }, 200);
  1708. // open notes markdown links in new tab
  1709. if (DOMPurify) {
  1710. DOMPurify.addHook('afterSanitizeAttributes', function (node) {
  1711. if ('target' in node) {
  1712. node.setAttribute('target', '_blank');
  1713. node.setAttribute('rel', 'noopener noreferrer');
  1714. }
  1715. if (!node.hasAttribute('target') && (node.hasAttribute('xlink:href') || node.hasAttribute('href'))) {
  1716. node.setAttribute('xlink:show', 'new');
  1717. }
  1718. });
  1719. }
  1720. // Show the modern ui switcher
  1721. QV('textnewui', ((features2 & 0x40000000) == 0) ? false : true);
  1722. // Connect to the mesh server
  1723. meshserver = MeshServerCreateControl(domainUrl);
  1724. meshserver.onStateChanged = onStateChanged;
  1725. meshserver.onMessage = onMessage;
  1726. meshserver.trace = args.trace;
  1727. meshserver.Start();
  1728. // Setup page controls
  1729. Q('sortselect').selectedIndex = sort = getstore('sort', 0);
  1730. Q('sizeselect').selectedIndex = getstore('viewsize', 1);
  1731. Q('KvmSearchInput').value = Q('SearchInput').value = getstore('_search', '');
  1732. showRealNames = (getstore('showRealNames', 0) == 1);
  1733. Q('RealNameCheckBox').checked = showRealNames;
  1734. Q('DevFilterSelect').value = getstore('devFilterSelect', 0);
  1735. Q('viewselect').value = getstore('deviceView', 1);
  1736. Q('DeskControl').checked = (getstore('DeskControl', 1) == 1);
  1737. QV('accountChangeEmailAddressSpan', (features & 0x200000) == 0);
  1738. // Display the page devices
  1739. mainUpdate(3)
  1740. for (var j = 1; j < 5; j++) { Q('devViewButton' + j).classList.remove('viewSelectorSel'); }
  1741. Q('devViewButton' + Q('viewselect').value).classList.add('viewSelectorSel');
  1742. // Setup upload drag & drop for server files
  1743. Q('p5filetable').addEventListener('drop', p5fileDragDrop, false);
  1744. Q('p5filetable').addEventListener('dragover', p5fileDragOver, false);
  1745. Q('p5filetable').addEventListener('dragleave', p5fileDragLeave, false);
  1746. //Q('p5fileCatchAllInput').addEventListener('drop', p5fileDragDrop, false);
  1747. //Q('p5fileCatchAllInput').addEventListener('dragover', p5fileDragOver, false);
  1748. //Q('p5fileCatchAllInput').addEventListener('dragleave', p5fileDragLeave, false);
  1749. // Setup drag & drop for device desktop
  1750. Q('deskarea0').addEventListener('drop', p11fileDragDrop, false);
  1751. Q('deskarea0').addEventListener('dragover', p11fileDragOver, false);
  1752. Q('deskarea0').addEventListener('dragleave', p11fileDragLeave, false);
  1753. // Setup upload drag & drop for device files
  1754. Q('p13filetable').addEventListener('drop', p13fileDragDrop, false);
  1755. Q('p13filetable').addEventListener('dragover', p13fileDragOver, false);
  1756. Q('p13filetable').addEventListener('dragleave', p13fileDragLeave, false);
  1757. // Timeline update interval
  1758. setInterval(updateDeviceTimeline, 120000); // Check every 2 minutes
  1759. // Load desktop settings
  1760. var t = null;
  1761. try { t = localStorage.getItem('desktopsettings'); } catch (ex) {}
  1762. if (t != null) { desktopsettings = JSON.parse(t); }
  1763. t = null;
  1764. try { t = localStorage.getItem('multidesktopsettings'); } catch (ex) {}
  1765. if (t != null) { multidesktopsettings = JSON.parse(t); }
  1766. applyDesktopSettings();
  1767. // Terminal special keys
  1768. var x = '';
  1769. for (var c = 1; c < 27; c++) x += '<option value=\'' + c + '\'>' + "Ctrl" + '-' + String.fromCharCode(64 + c) + ' (' + c + ')</option>';
  1770. QH('specialkeylist', x);
  1771. // Setup server stats panels
  1772. setupGeneralServerStats();
  1773. setupServerTimelineStats();
  1774. // Setup the user interface in the right mode
  1775. userInterfaceSelectMenu();
  1776. // Setup Mesh summary panel
  1777. setupMeshSummaryStats();
  1778. // If SSPI or LDAP authentication not used, allow batch account creation.
  1779. QV('p4UserBatchCreate', (features & 0x00080000) == 0);
  1780. // Set the file editor
  1781. d4EditWrapVal = Number(getstore('editorWrap', 0));
  1782. d4EditSizeVal = Number(getstore('editorSize', 0));
  1783. d4EditEncodingVal = Number(getstore('editorEncoding', 0));
  1784. d4EditLineBreakVal = Number(getstore('editorLineBreak', 0));
  1785. d4ToggleWrap(true);
  1786. d4ToggleSize(true);
  1787. d4ToggleEncoding(true);
  1788. d4ToggleLineBreak(true);
  1789. if (pluginHandler != null) pluginHandler.callHook('onWebUIStartupEnd');
  1790. // Deleted non-english style and fix all topbar titles
  1791. if ((['en','ko','ja','zh-chs','zh-cht'].indexOf('{{{lang}}}') == -1) && ('{{{lang}}}'.toLowerCase() != '')) { QC('body').add('nonenglish'); }
  1792. var elements = document.getElementsByClassName('topbar_td');
  1793. for (var i in elements) { if (elements[i].innerHTML) { elements[i].innerHTML = elements[i].innerHTML.split(' ').join('&nbsp;'); } }
  1794. // Custom UI
  1795. if (customui != null) {
  1796. if (customui.devicesbarbuttons) {
  1797. var x = '';
  1798. for (var i in customui.devicesbarbuttons) {
  1799. var disabled = ((customui.devicesbarbuttons[i].selection == 'one') || (customui.devicesbarbuttons[i].selection == 'many'))?'disabled':'';
  1800. x += '<input id="cui:' + i + '" type="button" value="' + customui.devicesbarbuttons[i].name + '" ' + disabled + ' onclick=customUIAction(event,"devicesbarbuttons") />';
  1801. }
  1802. QH('devsCustomUIBar', x);
  1803. }
  1804. if (customui.desktopbuttons) {
  1805. var x = '';
  1806. for (var i in customui.desktopbuttons) { x += '<input id="cui:' + i + '" type="button" value="' + customui.desktopbuttons[i].name + '" style="float:left" onclick=customUIAction(event,"desktopbuttons") />'; }
  1807. QH('desktopCustomUiButtons', x);
  1808. }
  1809. if (customui.terminalbuttons) {
  1810. var x = '';
  1811. for (var i in customui.terminalbuttons) { x += '<input id="cui:' + i + '" type="button" value="' + customui.terminalbuttons[i].name + '" style="float:left" onclick=customUIAction(event,"terminalbuttons") />'; }
  1812. QH('terminalCustomUiButtons', x);
  1813. }
  1814. if (customui.filesbuttons) {
  1815. var x = '';
  1816. for (var i in customui.filesbuttons) { x += '<input id="cui:' + i + '" type="button" value="' + customui.filesbuttons[i].name + '" style="float:left" onclick=customUIAction(event,"filesbuttons") />'; }
  1817. QH('filesCustomUiButtons', x);
  1818. }
  1819. }
  1820. // Session Refresh Timer
  1821. if (sessionTime >= 10) { sessionRefreshTimer = setTimeout(refreshCookieSession, Math.round((sessionTime * 60000) * 0.8)); }
  1822. // Set the user's desktop shortcut keys
  1823. deskKeyboardShortcuts = [];
  1824. var deskKeyboardShortcutsStr = getstore('deskKeyShortcuts', '0x0A002E,0x100000,0x100028,0x100026,0x10004C,0x10004D,0x11004D,0x100052,0x020073,0x080057,0x020009,0x100025,0x100027').split(',');
  1825. for (var i in deskKeyboardShortcutsStr) { if (deskKeyboardShortcutsStr[i] != "") { deskKeyboardShortcuts.push(parseInt(deskKeyboardShortcutsStr[i])); } }
  1826. updateDeskShortcutKeys();
  1827. // Set the user's desktop strings
  1828. try { deskKeyboardStrings = JSON.parse(getstore('deskStrings', '[]')); } catch (ex) {}
  1829. updateDesktopStrings();
  1830. // Override the collapse button text
  1831. updateCollapseAllButton();
  1832. // Make the dialog box movable
  1833. dialogBoxDrag();
  1834. // Fix links
  1835. if (urlargs.key) { Q('p6backuplink').href += '?key=' + urlargs.key; }
  1836. // Hide night mode button if needed
  1837. QV('uiViewButton4', (features2 & 0x00300000) == 0);
  1838. // Fix HTML words that in english have two or more meanings
  1839. Q('DeskType').value = multiTranslate("[KeyboardTyping]|Type");
  1840. // Show the "Scroll to top" button
  1841. QV('ScrollToTopButton', (features2 & 0x08000000) != 0);
  1842. }
  1843. function refreshCookieSession() {
  1844. var xdr = null;
  1845. try { xdr = new XDomainRequest(); } catch (e) { }
  1846. if (!xdr) xdr = new XMLHttpRequest();
  1847. xdr.open('GET', window.location.origin + domainUrl + 'refresh.ashx');
  1848. xdr.timeout = 15000;
  1849. xdr.onload = function () { sessionRefreshTimer = setTimeout(refreshCookieSession, Math.round((sessionTime * 60000) * 0.8)); };
  1850. xdr.onerror = xdr.ontimeout = function () { sessionRefreshTimer = null; };
  1851. xdr.send();
  1852. }
  1853. // Generic handling of custom actions
  1854. function customUIAction(e, section) {
  1855. var id = e.srcElement.id;
  1856. if (id.startsWith('cui:') == false) return;
  1857. var info = customui[section][id.substring(4)];
  1858. if (info == null) return;
  1859. if (section == 'devicesbarbuttons') {
  1860. var elements = document.getElementsByClassName('DeviceCheckbox'), selectedDevices = [];
  1861. for (var i = 0; i < elements.length; i++) { if (elements[i].checked === true) { selectedDevices.push(elements[i].defaultValue.substring(6)); } }
  1862. if (typeof info.action == 'string') {
  1863. if (info.action == 'event') { meshserver.send({ action: 'uicustomevent', section: section, element: id.substring(4), selectedDevices: selectedDevices, logmsg: info.logmsg }); }
  1864. if (info.action.startsWith('dialog:')) { showCustomUiDialog(info.action.substring(7), { section: section, element: id.substring(4), selectedDevices: selectedDevices }); }
  1865. if (info.deselectdevices) { for (var i = 0; i < elements.length; i++) { elements[i].checked = false; } checkedNodeids = {}; p1updateInfo(); }
  1866. }
  1867. }
  1868. if ((section == 'devicebuttons') || (section == 'desktopbuttons') || (section == 'terminalbuttons') || (section == 'filesbuttons')) {
  1869. var selectedDevices = [ currentNode._id ];
  1870. if (typeof info.action == 'string') {
  1871. if (info.action == 'event') { meshserver.send({ action: 'uicustomevent', section: section, element: id.substring(4), selectedDevices: selectedDevices, logmsg: info.logmsg }); }
  1872. if (info.action.startsWith('dialog:')) { showCustomUiDialog(info.action.substring(7), { section: section, element: id.substring(4), selectedDevices: selectedDevices }); }
  1873. }
  1874. }
  1875. }
  1876. // Display a generic custom UI dialog
  1877. function showCustomUiDialog(name, tag) {
  1878. if ((customui == null) || (customui.dialogs == null) || (typeof customui.dialogs[name] != 'object')) return;
  1879. var x = '', dialog = customui.dialogs[name], buttons = 3;
  1880. if (typeof dialog.text == 'string') { x += '<div style=margin-bottom:8px>' + dialog.text + '</div>'; }
  1881. if (typeof dialog.buttons == 'number') { buttons = dialog.buttons; }
  1882. if (typeof dialog.elements == 'object') {
  1883. for (var i in dialog.elements) {
  1884. var elem = dialog.elements[i];
  1885. if (elem.type == 'text') { x += addHtmlValue(elem.name, '<input id=cui:' + i + ' style=width:230px autocomplete=off />'); }
  1886. if (elem.type == 'textarea') {
  1887. if (elem.name == null) {
  1888. x += '<textarea id=cui:' + i + ' style=width:356px;resize:none;height:100px autocomplete=off></textarea>';
  1889. } else {
  1890. x += addHtmlValue(elem.name, '<textarea id=cui:' + i + ' style=width:230px;resize:none;height:100px autocomplete=off></textarea>');
  1891. }
  1892. }
  1893. if (elem.type == 'droplist') {
  1894. var y = '';
  1895. for (var j in elem.options) { y += '<option value="' + j + '">' + EscapeHtml(elem.options[j]) + '</option>'; }
  1896. x += addHtmlValue(elem.name, '<select id="cui:' + i + '" style=width:230px autocomplete=off />' + y + '</select>');
  1897. }
  1898. }
  1899. }
  1900. setDialogMode(2, dialog.title, buttons, showCustomUiDialogEx, x, { action: 'uicustomevent', section: 'dialogs', element: name, src: tag, values: {}, logmsg: dialog.logmsg });
  1901. }
  1902. // Handle a generic custom UI dialog event
  1903. function showCustomUiDialogEx(b, t) {
  1904. if (b != 1) return;
  1905. var dialog = customui.dialogs[t.element];
  1906. if (typeof dialog.elements == 'object') {
  1907. for (var i in dialog.elements) {
  1908. var elem = dialog.elements[i];
  1909. if ((elem.type == 'droplist') || (elem.type == 'textarea') || (elem.type == 'text')) { t.values[i] = Q('cui:' + i).value; }
  1910. }
  1911. }
  1912. meshserver.send(t);
  1913. if (dialog.deselectdevices) {
  1914. var elements = document.getElementsByClassName('DeviceCheckbox')
  1915. for (var i = 0; i < elements.length; i++) { elements[i].checked = false; } checkedNodeids = {}; p1updateInfo();
  1916. }
  1917. }
  1918. function adjustPanels() {
  1919. var hide = args.hide;
  1920. if (footerBar == false) { hide |= 4; }
  1921. QV('masthead', !(hide & 1));
  1922. QV('topbar', !(hide & 2));
  1923. QV('footer', !(hide & 4));
  1924. QV('p1title', !(hide & 8));
  1925. QV('p2title', !(hide & 8));
  1926. QV('p3title', !(hide & 8));
  1927. QV('p4title', !(hide & 8));
  1928. QV('p5title', !(hide & 8));
  1929. QV('p6title', !(hide & 8));
  1930. QV('p10title', !(hide & 8));
  1931. QV('p11title', !(hide & 8));
  1932. QV('p12title', !(hide & 8));
  1933. QV('p13title', !(hide & 8));
  1934. QV('p14title', !(hide & 8));
  1935. QV('p15title', !(hide & 8));
  1936. QV('p16title', !(hide & 8));
  1937. QV('p17title', !(hide & 8));
  1938. QV('p20title', !(hide & 8));
  1939. QV('p21title', !(hide & 8));
  1940. QV('p30title', !(hide & 8));
  1941. QV('p31title', !(hide & 8));
  1942. QV('p40title', !(hide & 8));
  1943. QV('p41title', !(hide & 8));
  1944. QV('p50title', !(hide & 8));
  1945. QV('p51title', !(hide & 8));
  1946. //if (hide & 16) { QV('page_leftbar', false); QS('page_content').left = '0px'; }
  1947. if (!footerBar) { QC('body').add('nofooter'); } else { QC('body').remove('nofooter'); }
  1948. if (args.hide != 0) {
  1949. // Fix the main grid to zero-height elements we want to hide.
  1950. if (uiMode == 2) {
  1951. QS('container')['grid-template-rows'] = ((hide & 1) ? '0' : '66') + 'px fit-content(48px) auto ' + ((hide & 4) ? '0' : '45') + 'px';
  1952. QS('container')['-ms-grid-rows'] = ((hide & 1) ? '0' : '66') + 'px fit-content(48px) auto ' + ((hide & 4) ? '0' : '45') + 'px';
  1953. } else {
  1954. QS('container')['grid-template-rows'] = ((hide & 1) ? '0' : '66') + 'px ' + ((hide & 2) ? '0' : '24') + 'px auto ' + ((hide & 4) ? '0' : '45') + 'px';
  1955. QS('container')['-ms-grid-rows'] = ((hide & 1) ? '0' : '66') + 'px ' + ((hide & 2) ? '0' : '24') + 'px auto ' + ((hide & 4) ? '0' : '45') + 'px';
  1956. }
  1957. }
  1958. // Adjust height of remote desktop, files and Intel AMT
  1959. // 1 = Top bar, 2 = Tool Bar, 4 = Bottom Bar, 8 = Tab Title
  1960. var xh = (((hide & 1) ? 0 : 66) + ((hide & 2) ? 0 : 24) + ((hide & 4) ? 0 : 45) + ((hide & 8) ? 0 : 60)); // 0 to 195
  1961. var xh2 = (uiMode > 1)?24:0;
  1962. QS('p2info')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
  1963. QS('p2info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
  1964. QS('p3users')['max-height'] = 'calc(100vh - ' + (50 + xh) + 'px)'; // 124
  1965. QS('p50groups')['max-height'] = 'calc(100vh - ' + (50 + xh) + 'px)'; // 124
  1966. QS('p3events')['height'] = 'calc(100vh - ' + (50 + xh) + 'px)'; // 124
  1967. QS('p5filetable')['height'] = 'calc(100vh - ' + (99 + xh) + 'px)'; // 160
  1968. QS('p13filetable')['height'] = 'calc(100vh - ' + (127 + xh + xh2) + 'px)'; // 124
  1969. QS('serverMainStats')['height'] = 'calc(100vh - ' + (50 + xh + xh2) + 'px)'; // 110
  1970. QS('serverMainStats')['max-height'] = 'calc(100vh - ' + (50 + xh + xh2) + 'px)'; // 110
  1971. QS('xdevices')['max-height'] = 'calc(100vh - ' + (46 + xh) + 'px)'; // 124
  1972. QS('xdevicesmap')['max-height'] = 'calc(100vh - ' + (46 + xh) + 'px)'; // 124
  1973. QS('p15agentConsole')['height'] = 'calc(100vh - ' + (84 + xh + xh2) + 'px)';
  1974. QS('p15agentConsole')['max-height'] = 'calc(100vh - ' + (84 + xh + xh2) + 'px)';
  1975. QS('p15agentConsoleText')['height'] = 'calc(100vh - ' + (81 + xh + xh2) + 'px)';
  1976. QS('p15agentConsoleText')['max-height'] = 'calc(100vh - ' + (81 + xh + xh2) + 'px)';
  1977. var xtermActive = !((args.xterm === 0) || ((terminal != null) && (xterm == null)));
  1978. if (fullscreen) {
  1979. QS('deskarea3x')['height'] = null;
  1980. QS('deskarea3x')['max-height'] = null;
  1981. QS('p14iframe')['height'] = null;
  1982. QS('p14iframe')['max-height'] = null;
  1983. if (xtermActive) {
  1984. QS('termarea3x')['height'] = 'calc(100vh - 55px)';
  1985. QS('termarea3xdiv')['height'] = 'calc(100vh - 55px)';
  1986. QS('termarea3x')['max-width'] = 'calc(1px)';
  1987. }
  1988. } else {
  1989. QS('deskarea3x')['height'] = 'calc(100vh - ' + (74 + xh + xh2) + 'px)';
  1990. QS('deskarea3x')['max-height'] = 'calc(100vh - ' + (74 + xh + xh2) + 'px)';
  1991. QS('p14iframe')['height'] = 'calc(100vh - ' + (23 + xh + xh2) + 'px)';
  1992. QS('p14iframe')['max-height'] = 'calc(100vh - ' + (23 + xh + xh2) + 'px)';
  1993. if (xtermActive) {
  1994. QS('termarea3x')['height'] = 'calc(100vh - ' + (74 + xh + xh2) + 'px)';
  1995. QS('termarea3xdiv')['height'] = 'calc(100vh - ' + (74 + xh + xh2) + 'px)';
  1996. QS('termarea3x')['max-width'] = 'calc(1px)';
  1997. }
  1998. }
  1999. QS('p43iframe')['height'] = 'calc(100vh - ' + (84 + xh) + 'px)';
  2000. QS('p43iframe')['max-height'] = 'calc(100vh - ' + (84 + xh) + 'px)';
  2001. QS('p6info')['height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
  2002. QS('p6info')['max-height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
  2003. QS('p10info')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
  2004. QS('p10info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
  2005. QS('p17info')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
  2006. QS('p17info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
  2007. QS('p20main')['height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
  2008. QS('p20main')['max-height'] = 'calc(100vh - ' + (-50 + xh + xh2) + 'px)';
  2009. QS('p21main')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
  2010. QS('p21main')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
  2011. QS('p30info')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
  2012. QS('p30info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
  2013. QS('p51info')['height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
  2014. QS('p51info')['max-height'] = 'calc(100vh - ' + (20 + xh + xh2) + 'px)';
  2015. QS('p16events')['height'] = 'calc(100vh - ' + (50 + xh + xh2) + 'px)';
  2016. QS('p16events')['max-height'] = 'calc(100vh - ' + (50 + xh + xh2) + 'px)';
  2017. QS('p31events')['height'] = 'calc(100vh - ' + (50 + xh + xh2) + 'px)';
  2018. QS('p31events')['max-height'] = 'calc(100vh - ' + (50 + xh + xh2) + 'px)';
  2019. QS('p41events')['height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
  2020. QS('p41events')['max-height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
  2021. QS('p52recordings')['height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
  2022. QS('p52recordings')['max-height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
  2023. QS('p60report')['height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
  2024. QS('p60report')['max-height'] = 'calc(100vh - ' + (48 + xh + xh2) + 'px)';
  2025. // We are looking at a single device, remove all the back buttons
  2026. if ((args.hide & 32) || ('{{currentNode}}'.toLowerCase() != '')) {
  2027. QV('p10BackButton', false);
  2028. QV('p11BackButton', false);
  2029. QV('p12BackButton', false);
  2030. QV('p13BackButton', false);
  2031. QV('p14BackButton', false);
  2032. QV('p15BackButton', false);
  2033. QV('p16BackButton', false);
  2034. QV('p17BackButton', false);
  2035. }
  2036. p1updateInfo();
  2037. }
  2038. // Toggle the web page to full screen
  2039. function toggleAspectRatio(toggle) {
  2040. if (toggle === 1) { deskAspectRatio = ((deskAspectRatio + 1) % 3); putstore('deskAspectRatio', deskAspectRatio); }
  2041. deskAdjust();
  2042. }
  2043. // If FullScreen, toggle menu to be horizontal or vertical
  2044. function toggleStackMenu(toggle) {
  2045. if (webPageFullScreen == true) {
  2046. if (toggle === 1) {
  2047. webPageStackMenu = !webPageStackMenu;
  2048. putstore('webPageStackMenu', webPageStackMenu);
  2049. }
  2050. if (webPageStackMenu == false) {
  2051. QC('body').remove('menu_stack');
  2052. } else {
  2053. QC('body').add('menu_stack');
  2054. if (xxcurrentView >= 10) QC('column_l').remove('room4submenu');
  2055. }
  2056. deskAdjust();
  2057. }
  2058. }
  2059. // Toggle user interface menu
  2060. function showUserInterfaceSelectMenu() {
  2061. if (xxdialogMode) return;
  2062. Q('uiViewButton1').classList.remove('uiSelectorSel');
  2063. Q('uiViewButton2').classList.remove('uiSelectorSel');
  2064. Q('uiViewButton3').classList.remove('uiSelectorSel');
  2065. Q('uiViewButton4').classList.remove('uiSelectorSel');
  2066. Q('uiViewButton5').classList.remove('uiSelectorSel');
  2067. try { Q('uiViewButton' + uiMode).classList.add('uiSelectorSel'); } catch (ex) { }
  2068. QV('uiMenu', (QS('uiMenu').display == 'none'));
  2069. //Q('uiViewButton1').focus();
  2070. if (nightMode) { Q('uiViewButton4').classList.add('uiSelectorSel'); }
  2071. if (footerBar) { Q('uiViewButton5').classList.add('uiSelectorSel'); }
  2072. }
  2073. function userInterfaceSelectMenu(s) {
  2074. if (s) { uiMode = s; putstore('uiMode', uiMode); }
  2075. webPageFullScreen = (uiMode < 3);
  2076. webPageStackMenu = (uiMode > 1);
  2077. toggleFullScreen(0);
  2078. toggleStackMenu(0);
  2079. if (webPageStackMenu && (xxcurrentView >= 10)) { QC('column_l').add('room4submenu'); } else { QC('column_l').remove('room4submenu'); }
  2080. adjustPanels();
  2081. }
  2082. function toggleNightMode() {
  2083. if (xxdialogMode) return;
  2084. var cNightMode = getstore('nightMode', '0');
  2085. var x = '<input type=radio id=night0 name=nightmoderadio value=0 ' + ((cNightMode == 0)?'checked':'') + '><label for=night0>' + "Browser default" + '</label><br>';
  2086. x += '<input type=radio id=night2 name=nightmoderadio value=2 ' + ((cNightMode == 2)?'checked':'') + '><label for=night2>' + "Light mode" + '</label><br>';
  2087. x += '<input type=radio id=night1 name=nightmoderadio value=1 ' + ((cNightMode == 1)?'checked':'') + '><label for=night1>' + "Dark mode" + '</label><br>';
  2088. setDialogMode(2, "Night Mode", 3, toggleNightModeEx, x);
  2089. QV('uiMenu', false);
  2090. }
  2091. function toggleNightModeEx() {
  2092. // Save new night mode
  2093. var nNightMode = '0';
  2094. if (Q('night1').checked) { nNightMode = '1'; }
  2095. if (Q('night2').checked) { nNightMode = '2'; }
  2096. putstore('nightMode', nNightMode);
  2097. setNightMode();
  2098. }
  2099. if(window.matchMedia) { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', setNightMode); }
  2100. function setNightMode() {
  2101. // Set night mode
  2102. var nNightMode = getstore('nightMode', '0')
  2103. nightMode = false;
  2104. if ((features2 & 0x00100000) != 0) { nNightMode = '1'; }
  2105. if ((features2 & 0x00200000) != 0) { nNightMode = '2'; }
  2106. if (nNightMode == '1') { nightMode = true; }
  2107. else if ((nNightMode == '0') && (window.matchMedia)) { nightMode = window.matchMedia('(prefers-color-scheme: dark)').matches }
  2108. if (nightMode) { QC('body').add('night'); QS('body')['background-color'] = '#000'; } else { QC('body').remove('night'); QS('body')['background-color'] = '#d3d9d6'; }
  2109. return nightMode;
  2110. }
  2111. function toggleFooterBarMode() {
  2112. footerBar = !footerBar;
  2113. putstore('footerBar', footerBar?'1':'0');
  2114. QS('container')['grid-template-rows'] = null;
  2115. QS('container')['-ms-grid-rows'] = null;
  2116. adjustPanels();
  2117. }
  2118. // Toggle the web page to full screen
  2119. function toggleFullScreen(toggle) {
  2120. if (toggle === 1) { webPageFullScreen = !webPageFullScreen; putstore('webPageFullScreen', webPageFullScreen); }
  2121. if (webPageFullScreen == false) {
  2122. QC('body').remove('menu_stack');
  2123. QC('body').remove('fullscreen');
  2124. QC('body').remove('arg_hide');
  2125. if (xxcurrentView >= 10) QC('column_l').add('room4submenu');
  2126. QV('UserDummyMenuSpan', false);
  2127. //QV('page_leftbar', false);
  2128. } else {
  2129. QC('body').add('fullscreen');
  2130. if (args.hide & 16) QC('body').add('arg_hide');
  2131. QV('page_leftbar', !(args.hide & 16));
  2132. QV('MainMenuSpan', !(args.hide & 16));
  2133. if (xxcurrentView >= 10) QC('column_l').remove('room4submenu');
  2134. QV('UserDummyMenuSpan', (xxcurrentView < 10) && webPageFullScreen);
  2135. }
  2136. mainUpdate(512);
  2137. QV('body', true);
  2138. }
  2139. function saveUserInterfaceMode() {
  2140. var nUiViewMode = 2;
  2141. if (Q('ui1').checked) { nUiViewMode = 3; }
  2142. if (getstore('uiViewMode', 2) != nUiViewMode) {
  2143. putstore('uiViewMode', nUiViewMode);
  2144. reload();
  2145. }
  2146. }
  2147. function toggleBootstrapUIMode() {
  2148. if (xxdialogMode) return;
  2149. var uiViewMode = getstore('uiViewMode', 2);
  2150. var x = '<input type=radio id=ui0 name=uiradio value=2 ' + ((uiViewMode == 2)?'checked':'') + '><label for=ui0>' + "Classic" + '</label><br>';
  2151. x += '<input type=radio id=ui1 name=uiradio value=3 ' + ((uiViewMode == 3)?'checked':'') + '><label for=ui1>' + "Modern" + '</label><br>';
  2152. setDialogMode(2, "User Interface", 3, saveUserInterfaceMode, x);
  2153. QV('uiMenu', false);
  2154. }
  2155. function getNodeFromId(id) { if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == id) return nodes[i]; } } return null; }
  2156. function reload() {
  2157. var x = window.location.href;
  2158. if (x.endsWith('/#')) { x = x.substring(0, x.length - 2); }
  2159. if (x.endsWith('#')) { x = x.substring(0, x.length - 1); }
  2160. window.location.href = x;
  2161. }
  2162. function onStateChanged(server, state, prevState, errorCode) {
  2163. if (state == 0) {
  2164. // Control web socket disconnected
  2165. setDialogMode(0); // Close any dialog boxes if present
  2166. go(0); // Go to disconnection panel
  2167. // Clean up
  2168. powerTimeline = null;
  2169. powerTimelineReq = null;
  2170. powerTimelineNode = null;
  2171. powerTimelineUpdate = null;
  2172. deviceShares = null;
  2173. deleteAllNotifications(); // Close and clear notifications if present
  2174. hideContextMenu(); // Hide the context menu if present
  2175. QV('verifyEmailId2', false);
  2176. QV('logoutControl', false);
  2177. if (errorCode == 'noauth') { QH('p0span', "Unable to perform authentication"); return; }
  2178. if (errorCode == 'invalidorigin') { QH('p0span', "Invalid origin in HTTP request"); return; }
  2179. if (prevState == 2) { if (autoReconnect) { setTimeout(serverPoll, 5000); } } else { QH('p0span', "Unable to connect web socket"); }
  2180. if (authCookieRenewTimer != null) { clearInterval(authCookieRenewTimer); authCookieRenewTimer = null; }
  2181. } else if (state == 2) {
  2182. // Fetch list of meshes, nodes, files
  2183. meshserver.send({ action: 'usergroups' });
  2184. meshserver.send({ action: 'meshes' });
  2185. meshserver.send({ action: 'nodes', id: '{{currentNode}}', skip: (devicePagingState == null) ? 0 : devicePagingState.skip });
  2186. meshserver.send({ action: 'loginTokens' });
  2187. if (pluginHandler != null) { meshserver.send({ action: 'plugins' }); }
  2188. if ('{{currentNode}}'.toLowerCase() == '') { meshserver.send({ action: 'files' }); }
  2189. if ('{{viewmode}}'.toLowerCase() == '') { go(1); }
  2190. authCookieRenewTimer = setInterval(function () { meshserver.send({ action: 'authcookie' }); }, 1800000); // Request a cookie refresh every 30 minutes.
  2191. if (xxcurrentView == 40) { refreshServerTimelineStats(); }
  2192. }
  2193. }
  2194. // Poll the server, if it responds, refresh the page.
  2195. function serverPoll() {
  2196. var xdr = null;
  2197. try { xdr = new XDomainRequest(); } catch (e) { }
  2198. if (!xdr) xdr = new XMLHttpRequest();
  2199. xdr.open('HEAD', window.location.href);
  2200. xdr.timeout = 15000;
  2201. // Make sure there isn't a reverse proxy in front that may just be returning 5xx codes
  2202. // Status code 4xx should still be allowed, since a page could potentially be removed, etc
  2203. xdr.onload = function () { if (xdr.status < 500) reload(); else setTimeout(serverPoll, 10000); };
  2204. xdr.onerror = xdr.ontimeout = function () { setTimeout(serverPoll, 10000); };
  2205. xdr.send();
  2206. }
  2207. // Return true if this browser is Windows based
  2208. function detectWindowsBrowser() {
  2209. var userAgent = window.navigator.userAgent.toUpperCase();
  2210. return (userAgent.indexOf('WINDOWS') >= 0) || (userAgent.indexOf('WIN32') >= 0) || (userAgent.indexOf('WIN64') >= 0);
  2211. }
  2212. function updateSiteAdmin() {
  2213. var serverFeatures = parseInt('{{{serverfeatures}}}');
  2214. var siteRights = userinfo.siteadmin;
  2215. var accountSettingsLocked = ((siteRights != 0xFFFFFFFF) && ((siteRights & 1024) != 0)) || ((features2 & 0x100) != 0); // Not admin and have account features locked, or using a loginToken
  2216. // Update account actions
  2217. 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.
  2218. QV('p2AccountActions', !accountSettingsLocked)
  2219. QV('managePhoneNumber1', (features & 0x02000000) && (features & 0x04000000) && (serverinfo.lock2factor != true));
  2220. QV('managePhoneNumber2', (features & 0x02000000) && !(features & 0x04000000) && (serverinfo.lock2factor != true));
  2221. QV('manageMessaging1', (features2 & 0x02000000) && (features2 & 0x04000000) && (serverinfo.lock2factor != true));
  2222. QV('manageMessaging2', (features2 & 0x02000000) && !(features2 & 0x04000000) && (serverinfo.lock2factor != true));
  2223. QV('manageEmail2FA', (features & 0x00800000) && (serverinfo.lock2factor != true));
  2224. 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
  2225. //QV('p2AccountImage', ((features & 4) == 0) && (serverinfo.domainauth == false)); // If account actions are not visible, also remove the image on that panel
  2226. QV('accountCreateLoginTokenSpan', features2 & 0x00000080);
  2227. QV('p2AccountImageFrame', !accountSettingsLocked)
  2228. QV('p2ServerActions', (siteRights & 21) && ((serverFeatures & 143) != 0));
  2229. QV('LeftMenuMyServer', (siteRights & 21) && ((serverFeatures & 64) != 0)); // 16 + 4 + 1
  2230. QV('MainMenuMyServer', siteRights & 21);
  2231. QV('p2ServerActionsBackup', (siteRights & 1) && ((serverFeatures & 1) != 0));
  2232. QV('p2ServerActionsRestore', (siteRights & 4) && ((serverFeatures & 2) != 0));
  2233. QV('p2ServerActionsVersion', (siteRights & 16) && ((serverFeatures & 4) != 0));
  2234. QV('p2ServerActionsErrors', (siteRights & 16) && ((serverFeatures & 8) != 0));
  2235. QV('p2ServerActionsConfig', (siteRights & 16) && ((serverFeatures & 128) != 0));
  2236. QV('MainMenuMyFiles', siteRights & 8);
  2237. QV('LeftMenuMyFiles', siteRights & 8);
  2238. if (((siteRights & 8) == 0) && (xxcurrentView == 5)) { setDialogMode(0); go(1); }
  2239. if (currentNode != null) { gotoDevice(currentNode._id, xxcurrentView, true); }
  2240. // Update user image
  2241. if ((userinfo.flags != null) && (userinfo.flags & 1)) {
  2242. if (userinfo.accountImageRnd == null) { userinfo.accountImageRnd = Math.floor(Math.random() * 9999999999); }
  2243. Q('p2AccountImage').src = 'userimage.ashx?rnd=' + userinfo.accountImageRnd;
  2244. } else {
  2245. Q('p2AccountImage').src = 'images/user-256.png';
  2246. }
  2247. // Update user management state
  2248. if ((userinfo.siteadmin & 2) != 0) {
  2249. // We are user administrator
  2250. if (users == null) { meshserver.send({ action: 'users' }); }
  2251. if (wssessions == null) { meshserver.send({ action: 'wssessioncount' }); }
  2252. mainUpdate(8192 + 16384);
  2253. } else {
  2254. // We are not user administrator
  2255. users = null;
  2256. wssessions = null;
  2257. mainUpdate(16384);
  2258. if (xxcurrentView == 4 || ((xxcurrentView >= 30) && (xxcurrentView < 40))) { setDialogMode(0); go(1); currentUser = null; }
  2259. }
  2260. meshserver.send({ action: 'events', limit: parseInt(p3limitdropdown.value) });
  2261. QV('ServerConsole', (userinfo.siteadmin === 0xFFFFFFFF) && ((serverFeatures & 16) != 0));
  2262. QV('ServerTrace', (userinfo.siteadmin === 0xFFFFFFFF) && ((serverFeatures & 32) != 0));
  2263. if ((xxcurrentView == 115) && (userinfo.siteadmin != 0xFFFFFFFF)) { go(6); }
  2264. if ((xxcurrentView == 6) && ((userinfo.siteadmin & 21) == 0)) { go(1); }
  2265. // If we are site administrator, register to get server statistics
  2266. if ((siteRights & 21) != 0) { meshserver.send({ action: 'serverstats', interval: 10000 }); }
  2267. }
  2268. // To boost the speed of the web page when even floods occur, this method perform a delayed update on the web page.
  2269. var updateNaggleTimer = null;
  2270. var updateNaggleFlags = 0;
  2271. function mainUpdate(flags) {
  2272. updateNaggleFlags |= flags;
  2273. if (updateNaggleTimer == null) {
  2274. updateNaggleTimer = setTimeout(function () {
  2275. if (updateNaggleFlags & 512) { center(); }
  2276. if (updateNaggleFlags & 1) { onSearchInputChanged(); }
  2277. if (updateNaggleFlags & 2) { onSortSelectChange(false); }
  2278. if (updateNaggleFlags & 128) { updateMeshes(); }
  2279. if (updateNaggleFlags & 4) { updateDevices(); updateDeviceDetails(); if (xxcurrentView == 21) { p21updateMesh(); } }
  2280. if (updateNaggleFlags & 8) { drawNotifications(); }
  2281. if ((updateNaggleFlags & 16) && (features & 0x00008000)) { updateMapMarkers(); }
  2282. if (updateNaggleFlags & 32) { eventsUpdate(); }
  2283. if ((updateNaggleFlags & 64) && (features & 0x00008000)) { refreshMap(false, true); }
  2284. if (updateNaggleFlags & 256) { drawDeviceTimeline(); }
  2285. if (updateNaggleFlags & 1024) { deviceEventsUpdate(); }
  2286. if (updateNaggleFlags & 2048) { userEventsUpdate(); }
  2287. if (updateNaggleFlags & 4096) { p20updateMesh(); }
  2288. if (updateNaggleFlags & 8192) { updateUserGroups(); }
  2289. if (updateNaggleFlags & 16384) { updateUsers(); }
  2290. if (updateNaggleFlags & 32768) { updateRecordings(); }
  2291. if (updateNaggleFlags & 65536) { updateLoginTokens(); }
  2292. updateNaggleTimer = null;
  2293. updateNaggleFlags = 0;
  2294. gotoStartViewPage();
  2295. }, 150);
  2296. }
  2297. }
  2298. // Return the number of 2nd factor for this account
  2299. function count2factoraAuths() {
  2300. if (userinfo == null) return -1;
  2301. var authFactorCount = 0;
  2302. if (userinfo.otpsecret == 1) { authFactorCount++; } // Authenticator time factor
  2303. if (userinfo.otpduo == 1) { authFactorCount++; } // Duo factor
  2304. if (userinfo.otpdev == 1) { authFactorCount++; } // Push authentication factor
  2305. if (userinfo.otphkeys > 0) { authFactorCount += userinfo.otphkeys; } // FIDO hardware factor
  2306. if ((features & 0x00800000) && (userinfo.otpekey == 1)) { authFactorCount++; } // EMail factor
  2307. if ((features & 0x02000000) && (features & 0x04000000) && (userinfo.phone != null)) { authFactorCount++; } // SMS factor
  2308. if ((features2 & 0x02000000) && (features2 & 0x04000000) && (userinfo.msghandle != null)) { authFactorCount++; } // Messaging factor
  2309. if ((authFactorCount > 0) && (userinfo.otpkeys > 0) && ((features2 & 0x40000) == 0)) { authFactorCount++; } // Backup keys
  2310. return authFactorCount;
  2311. }
  2312. var backupCodesWarningDone = false;
  2313. function updateSelf() {
  2314. var authFactorCount = count2factoraAuths(); // Get the number of 2nd factors
  2315. var accountSettingsLocked = ((userinfo.siteadmin != 0xFFFFFFFF) && ((userinfo.siteadmin & 1024) != 0));
  2316. QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true));
  2317. QV('verifyEmailId2', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true) && (accountSettingsLocked == false));
  2318. QV('manageOtp', (serverinfo.lock2factor != true) && (authFactorCount > 0) && ((features2 & 0x40000) == 0));
  2319. QV('authPhoneNumberCheck', (userinfo.phone != null));
  2320. QV('authMessagingCheck', (userinfo.msghandle != null));
  2321. QV('authEmailSetupCheck', (userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
  2322. QV('authDuoSetupCheck', (userinfo.otpduo == 1) && ((features2 & 0x20000000) != 0));
  2323. QV('authAppSetupCheck', userinfo.otpsecret == 1);
  2324. QV('manageAuthApp', (serverinfo.lock2factor != true) && ((userinfo.otpsecret == 1) || ((features2 & 0x00020000) == 0)));
  2325. QV('manageDuoApp', (serverinfo.lock2factor != true) && ((features2 & 0x20000000) != 0));
  2326. QV('authKeySetupCheck', userinfo.otphkeys > 0);
  2327. QV('authPushAuthDevCheck', (userinfo.otpdev > 0) && ((features2 & 0x40) != 0));
  2328. QV('authCodesSetupCheck', userinfo.otpkeys > 0);
  2329. QV('managePushAuthDev', (serverinfo.lock2factor != true) && (features2 & 0x40) && (authFactorCount > 0));
  2330. QV('manageHardwareOtp', (serverinfo.lock2factor != true));
  2331. mainUpdate(4 + 128 + 4096);
  2332. // Check if none or at least 2 factors are enabled.
  2333. if ((backupCodesWarningDone == false) && (authFactorCount == 1) && ((features2 & 0x80000) == 0)) {
  2334. addNotification({ text: "Please add two-factor backup codes. If the current factor is lost, there is no way to recover this account.", title: "Two factor authentication", tag: 'backupcodes' });
  2335. backupCodesWarningDone = true;
  2336. }
  2337. // If we can't create new groups, hide all links that can do that.
  2338. var newGroupsAllowed = ((userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.siteadmin & 64) == 0));
  2339. QV('p2createMeshLink1', newGroupsAllowed);
  2340. QV('p2createMeshLink2', newGroupsAllowed);
  2341. QV('getStarted1', newGroupsAllowed);
  2342. QV('getStarted2', !newGroupsAllowed);
  2343. if (typeof userinfo.passchange == 'number') {
  2344. if (userinfo.passchange == -1) { QH('p2nextPasswordUpdateTime', " - Reset on next login."); }
  2345. else if ((passRequirements != null) && (typeof passRequirements.reset == 'number')) {
  2346. var seconds = (userinfo.passchange) + (passRequirements.reset * 86400) - Math.floor(Date.now() / 1000);
  2347. if (seconds < 0) { QH('p2nextPasswordUpdateTime', " - Reset on next login."); }
  2348. else if (seconds < 3600) { var secs = Math.floor(seconds / 60); QH('p2nextPasswordUpdateTime',format((secs == 1)?" - Reset in 1 minute.":" - Reset in {0} minutes.", secs)); }
  2349. else if (seconds < 86400) { var hours = Math.floor(seconds / 3600); QH('p2nextPasswordUpdateTime', format((hours == 1) ? " - Reset in 1 hour." : " - Reset in {0} hours.", hours)); }
  2350. else { var days = Math.floor(seconds / 86400); QH('p2nextPasswordUpdateTime', format((hours == 1) ? " - Reset in 1 day." : " - Reset in {0} days.", days)); }
  2351. }
  2352. }
  2353. // Adjust "My Users" tabs
  2354. QV('MainMenuMyUsers', ((users != null) && ((features & 4) == 0)) || (((userinfo.siteadmin & 512) != 0) && ((features & 0x08000000) != 0)));
  2355. QV('LeftMenuMyUsers', ((users != null) && ((features & 4) == 0)) || (((userinfo.siteadmin & 512) != 0) && ((features & 0x08000000) != 0)));
  2356. QV('UsersGeneral', ((users != null) && ((features & 4) == 0)));
  2357. QV('UsersGroups', ((users != null) && ((features & 4) == 0)));
  2358. QV('UsersRecordings', ((userinfo.siteadmin & 512) != 0) && ((features & 0x08000000) != 0));
  2359. }
  2360. function setSessionActivity() { sessionActivity = Date.now(); QH('idleTimeoutNotify', ''); }
  2361. function checkIdleSessionTimeout() {
  2362. var delta = (Date.now() - sessionActivity);
  2363. if (delta > serverinfo.timeout) {
  2364. if (desktop != null) { // Disconnect remote desktop
  2365. desktop.Stop();
  2366. webRtcDesktopReset();
  2367. desktopNode = desktop = null;
  2368. if (pluginHandler != null) { pluginHandler.callHook('onDesktopDisconnect'); }
  2369. }
  2370. if (terminal != null) { // Disconnect terminal
  2371. terminal.Stop();
  2372. terminal = null;
  2373. }
  2374. if (files != null) { // Disconnect files
  2375. files.Stop();
  2376. files = null;
  2377. }
  2378. if (serverinfo.logoutonidlesessiontimeout) {
  2379. if (urlargs.key) { window.location.href = 'logout?key=' + urlargs.key; }
  2380. else { window.location.href = 'logout'; }
  2381. }
  2382. } else {
  2383. var ds = Math.round((serverinfo.timeout - delta) / 1000);
  2384. var sessionInProgress = desktop != null || terminal != null || files != null;
  2385. var show = serverinfo.logoutonidlesessiontimeout || sessionInProgress;
  2386. var isLogout = serverinfo.logoutonidlesessiontimeout;
  2387. var theText = ''; // Initialize theText
  2388. if (ds <= 60) {
  2389. theText = isLogout
  2390. ? (ds == 1 ? "1 second until logout" : "{0} seconds until logout")
  2391. : (ds == 1 ? "1 second until disconnect" : "{0} seconds until disconnect");
  2392. } else {
  2393. ds = Math.round(ds / 60);
  2394. if (ds <= 5) {
  2395. theText = isLogout
  2396. ? (ds == 1 ? "1 minute until logout" : "{0} minutes until logout")
  2397. : (ds == 1 ? "1 minute until disconnect" : "{0} minutes until disconnect");
  2398. }
  2399. }
  2400. QH('idleTimeoutNotify', show && theText ? '<br />' + format(theText, ds) : '');
  2401. }
  2402. }
  2403. function onMessage(server, message) {
  2404. switch (message.action) {
  2405. case 'trace': {
  2406. serverTrace.unshift(message);
  2407. displayServerTrace();
  2408. break;
  2409. }
  2410. case 'intersession' : {
  2411. if (message.subaction == 'removeNotify') { notificationDelete(message.id); }
  2412. break;
  2413. }
  2414. case 'traceinfo': {
  2415. if (typeof message.traceSources == 'object') {
  2416. if ((message.traceSources != null) && (message.traceSources.length > 0)) {
  2417. serverTraceSources = message.traceSources;
  2418. QH('p41traceStatus', EscapeHtml(message.traceSources.join(', ')));
  2419. } else {
  2420. serverTraceSources = [];
  2421. QH('p41traceStatus', "None");
  2422. }
  2423. }
  2424. break;
  2425. }
  2426. case 'serverstats': {
  2427. updateGeneralServerStats(message);
  2428. break;
  2429. }
  2430. case 'serverwarnings': {
  2431. if ((message.warnings != null) && (message.warnings.length > 0)) {
  2432. var ServerWarnings = {
  2433. 1: "",
  2434. 2: "Missing WebDAV parameters.",
  2435. 3: "Unrecognized configuration option \"{0}\".",
  2436. 4: "WebSocket compression is disabled, this feature is broken in NodeJS v11.11 to v12.15 and v13.2",
  2437. 5: "Unable to load Intel AMT TLS root certificate for default domain.",
  2438. 6: "Unable to load Intel AMT TLS root certificate for domain {0}.",
  2439. 7: "CIRA local FQDN's ignored when server in LAN-only or WAN-only mode.",
  2440. 8: "Can't have more than 4 CIRA local FQDN's. Ignoring value.",
  2441. 9: "Agent hash checking is being skipped, this is unsafe.",
  2442. 10: "Missing Let's Encrypt email address.",
  2443. 11: "Invalid Let's Encrypt host names.",
  2444. 12: "Invalid Let's Encrypt names, can't contain a *.",
  2445. 13: "Unable to setup Let's Encrypt module.",
  2446. 14: "Invalid Let's Encrypt names, unable to resolve: {0}",
  2447. 15: "Invalid Let's Encrypt email address, unable to resolve: {0}",
  2448. 16: "Unable to load CloudFlare trusted proxy IPv6 address list.",
  2449. 17: "SendGrid server has limited use in LAN mode.",
  2450. 18: "SMTP server has limited use in LAN mode.",
  2451. 19: "SMS gateway has limited use in LAN mode.",
  2452. 20: "Invalid \"LoginCookieEncryptionKey\" in config.json.",
  2453. 21: "Backup path can't be set within meshcentral-data folder, backup settings ignored.",
  2454. 22: "Failed to sign agent {0}: {1}",
  2455. 23: "Unable to load agent icon file: {0}.",
  2456. 24: "Unable to load agent logo file: {0}.",
  2457. 25: "This NodeJS version does not support OpenID.",
  2458. 26: "This NodeJS version does not support Discord.js.",
  2459. 27: "Firebase now requires a service account JSON file, Firebase disabled."
  2460. };
  2461. var x = '';
  2462. for (var i in message.warnings) {
  2463. var y = message.warnings[i];
  2464. if (typeof y == 'string') {
  2465. x += '<div style=color:red;padding-bottom:6px><b>' + "WARNING: " + y + '</b></div>';
  2466. } else {
  2467. var z = ServerWarnings[y.id];
  2468. if (z == null) { z = y.msg; } else { if (y.args != null) { z = format(z, ...y.args); } }
  2469. x += '<div style=color:red;padding-bottom:6px><b>' + "WARNING: " + z + '</b></div>';
  2470. }
  2471. }
  2472. QH('serverWarnings', x);
  2473. QV('serverWarningsDiv', true);
  2474. }
  2475. break;
  2476. }
  2477. case 'servertimelinestats': {
  2478. setServerTimelineStats(message.events);
  2479. break;
  2480. }
  2481. case 'authcookie': {
  2482. // Got an authentication cookie refresh
  2483. authCookie = message.cookie;
  2484. authRelayCookie = message.rcookie;
  2485. break;
  2486. }
  2487. case 'serverinfo': {
  2488. serverinfo = message.serverinfo;
  2489. if (serverinfo.timeout) { setInterval(checkIdleSessionTimeout, 10000); checkIdleSessionTimeout(); }
  2490. if (debugmode == 1) { console.log('Server time: ', printDateTime(new Date(serverinfo.serverTime))); }
  2491. setupServiceWorker();
  2492. if (serverinfo.certExpire != null) {
  2493. var days = Math.floor((serverinfo.certExpire - Date.now()) / 86400000);
  2494. if ((days >= 0) && (days < 20)) {
  2495. QH('serverCertWarnings', '<div style=color:red;padding-bottom:6px><b>' + "WARNING: " + format("Certificate expires in {0} day(s)", days) + '</b></div>');
  2496. QV('serverWarningsDiv', true);
  2497. addNotification({ text: format("Certificate expires in {0} day(s)", days) });
  2498. }
  2499. }
  2500. if (serverinfo.preConfiguredRemoteInput) {
  2501. // Setup the pre-configured keyboard remote input strings
  2502. var x = '';
  2503. for (var i in serverinfo.preConfiguredRemoteInput) { x += '<div class="cmtext" onclick="cmdeskpreconfigtypeaction(' + i + ',event)">' + EscapeHtml(serverinfo.preConfiguredRemoteInput[i].name) + '</div>'; }
  2504. if (x != '') { x += '<hr />'; }
  2505. QH('deskPreConfigShortcutContextMenu1', x);
  2506. }
  2507. if (serverinfo.preConfiguredScripts) {
  2508. // Setup the pre-configured scripts
  2509. var x = '', y = '';
  2510. for (var i in serverinfo.preConfiguredScripts) {
  2511. var s = serverinfo.preConfiguredScripts[i];
  2512. var z = '<div class="cmtext" onclick="cmdeskpreconfigscriptaction(' + i + ',event)">' + EscapeHtml(serverinfo.preConfiguredScripts[i].name) + '</div>';
  2513. if (s.type != 3) { x += z; }
  2514. if (s.type >= 3) { y += z; }
  2515. }
  2516. QH('deskPreConfigScriptContextMenu1', x); // Windows devices
  2517. QH('deskPreConfigScriptContextMenu2', y); // Other devices
  2518. }
  2519. break;
  2520. }
  2521. case 'userinfo': {
  2522. userinfo = message.userinfo;
  2523. updateSiteAdmin();
  2524. updateSelf();
  2525. break;
  2526. }
  2527. case 'users': {
  2528. users = {};
  2529. for (var m in message.users) { users[message.users[m]._id] = message.users[m]; }
  2530. if (currentUser != null) { currentUser = users[currentUser._id]; }
  2531. mainUpdate(16384);
  2532. updateSelf();
  2533. break;
  2534. }
  2535. case 'wssessioncount': {
  2536. wssessions = message.wssessions;
  2537. mainUpdate(16384);
  2538. break;
  2539. }
  2540. case 'meshes': {
  2541. meshes = {};
  2542. for (var m in message.meshes) { meshes[message.meshes[m]._id] = message.meshes[m]; }
  2543. if (currentMesh != null) { currentMesh = meshes[currentMesh._id]; }
  2544. mainUpdate(4 + 128);
  2545. break;
  2546. }
  2547. case 'usergroups': {
  2548. var groupCount = 0;
  2549. if (Array.isArray(message.ugroups)) {
  2550. usergroups = {};
  2551. for (var i in message.ugroups) { groupCount++; usergroups[message.ugroups[i]._id] = message.ugroups[i]; }
  2552. if (groupCount == 0) { usergroups = null; }
  2553. } else {
  2554. usergroups = message.ugroups;
  2555. for (var i in message.ugroups) { groupCount++; message.ugroups[i]._id = i; }
  2556. if (groupCount == 0) { usergroups = null; }
  2557. }
  2558. mainUpdate(8192);
  2559. break;
  2560. }
  2561. case 'files': {
  2562. filetree = setupBackPointers(message.filetree);
  2563. updateFiles();
  2564. d3updatefiles();
  2565. break;
  2566. }
  2567. case 'nodes': {
  2568. nodes = [];
  2569. for (var m in message.nodes) {
  2570. for (var n in message.nodes[m]) {
  2571. if (message.nodes[m][n]._id == null) { console.log('Invalid node (' + n + '): ' + JSON.stringify(message.nodes)); continue; }
  2572. if (message.nodes[m][n].name == null) { message.nodes[m][n].name = '???'; }
  2573. message.nodes[m][n].namel = message.nodes[m][n].name.toLowerCase();
  2574. 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; }
  2575. message.nodes[m][n].meshnamel = meshes[m]?meshes[m].name.toLowerCase():'*';
  2576. message.nodes[m][n].meshid = m;
  2577. message.nodes[m][n].state = (message.nodes[m][n].state)?(message.nodes[m][n].state):0;
  2578. if (!message.nodes[m][n].icon) message.nodes[m][n].icon = 1;
  2579. message.nodes[m][n].ident = ++nodeShortIdent;
  2580. nodes.push(message.nodes[m][n]);
  2581. }
  2582. }
  2583. // Remove any stars for nodes that don't exist
  2584. for (var i in stars) { if (getNodeFromId(i) == null) { delete stars[i]; } }
  2585. // If we are currently looking at a node this is now gone, change the view.
  2586. if ((currentNode != null) && (IsNodeViewable(currentNode) == false)) { currentNode = null; go(1); }
  2587. // Change the reference to the current node
  2588. if (currentNode != null) { currentNode = getNodeFromId(currentNode._id); if (currentNode != null) { gotoDevice(currentNode._id, xxcurrentView, true); } else { go(1); } }
  2589. // Update device paging
  2590. devicePagingState = (message.totalcount == null) ? null : { total: message.totalcount, skip: message.skip, limit: message.limit };
  2591. updateDevicePageState();
  2592. mainUpdate(1 | 2 | 4 | 64);
  2593. // If groups are to be collapsed by default, do it now
  2594. if (collapseGroups === 'true' && Object.keys(CollapsedGroups).length === 0 && typeof(getstore('_collapse')) === 'undefined') { cmexpandaction(2); }
  2595. break;
  2596. }
  2597. case 'powertimeline': {
  2598. if (message.nodeid != powerTimelineReq) break;
  2599. powerTimelineNode = message.nodeid;
  2600. powerTimeline = message.timeline;
  2601. powerTimelineUpdate = Date.now() + 300000; // Update every 5 minutes
  2602. for (var i in powerTimeline) { if (i % 2 == 1) { powerTimeline[i] = powerTimeline[i] * 1000; } } // Decompress time
  2603. if (currentNode._id == message.nodeid) { mainUpdate(256); }
  2604. break;
  2605. }
  2606. case 'deviceShares': {
  2607. if (message.nodeid != deviceSharesReq) break;
  2608. deviceSharesNode = message.nodeid;
  2609. deviceShares = message.deviceShares;
  2610. if ((currentNode != null) && (currentNode._id == message.nodeid)) { gotoDevice(currentNode._id, xxcurrentView, true); }
  2611. break;
  2612. }
  2613. case 'deviceMeshShares': {
  2614. if (message.meshid != deviceSharesReq) break;
  2615. deviceSharesNode = message.meshid;
  2616. deviceShares = message.deviceShares;
  2617. if ((currentMesh != null) && (currentMesh._id == message.meshid)) { p20updateMesh(); }
  2618. break;
  2619. }
  2620. case 'getsysinfo': {
  2621. if (message.nodeid != powerTimelineReq) break;
  2622. if (message.noinfo === true) {
  2623. updateDeviceDetails(getNodeFromId(message.nodeid));
  2624. } else {
  2625. message.hardware.time = message.time;
  2626. updateDeviceDetails(getNodeFromId(message.nodeid), message.hardware);
  2627. }
  2628. break;
  2629. }
  2630. case 'lastconnect': {
  2631. var node = getNodeFromId(message.nodeid);
  2632. if (node != null) {
  2633. node.lastconnect = message.time;
  2634. node.lastaddr = message.addr;
  2635. if ((currentNode._id == node._id) && (Q('MainComputerState').innerHTML == '') && (node.lastconnect != null)) {
  2636. QH('MainComputerState', '<span>' + "Last seen:" + '<br />' + printDateTime(new Date(node.lastconnect)) + '</span>');
  2637. }
  2638. }
  2639. break;
  2640. }
  2641. case 'lastconnects': {
  2642. var lcnodes = Object.keys(message.lastconnects);
  2643. for (var i in lcnodes) {
  2644. var lcnodeid = lcnodes[i];
  2645. var node = getNodeFromId(lcnodeid);
  2646. if (node != null) { node.lastconnect = message.lastconnects[lcnodeid] }
  2647. }
  2648. mainUpdate(4);
  2649. }
  2650. case 'msg': {
  2651. // Check if this is a message from a node
  2652. if (message.nodeid != null) {
  2653. var index = -1;
  2654. if (nodes != null) { for (var i in nodes) { if (nodes[i]._id == message.nodeid) { index = i; break; } } }
  2655. if (index != -1) {
  2656. // Node was found, dispatch the message
  2657. if ((message.type === 'cpuinfo') && (currentNode != null) && (currentNode._id == message.nodeid)) {
  2658. var now = (Date.now() / 1000), cpu = 0, memory = 0;
  2659. if (typeof message.cpu.total == 'number') { cpu = message.cpu.total; }
  2660. if (typeof message.memory.percentConsumed == 'number') { memory = message.memory.percentConsumed; }
  2661. deviceDetailsStatsData.push([now, cpu, memory]);
  2662. deviceDetailsStatsDraw(message);
  2663. } else if (message.type === 'console') { p15consoleReceive(nodes[index], message.value, message.source); } // This is a console message.
  2664. else if (message.type === 'notify') { // This is a notification message.
  2665. var n = getstore('notifications', 0);
  2666. if (((n & 8) == 0) && (message.amtMessage != null)) { break; } // Intel AMT desktop & terminal messages should be ignored.
  2667. var n = { text: message.value, title: message.title, icon: message.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
  2668. if (message.id != null) { n.id = message.id; }
  2669. if (message.nodeid != null) { n.nodeid = message.nodeid; }
  2670. if (message.tag != null) { n.tag = message.tag; }
  2671. if (message.url != null) { n.url = message.url; }
  2672. if (message.username != null) { n.username = message.username; }
  2673. if (typeof message.maxtime === 'number') { n.maxtime = message.maxtime; }
  2674. addNotification(n);
  2675. } else if (message.type === 'ps') {
  2676. showDeskToolsProcesses(message);
  2677. } else if (message.type === 'services') {
  2678. showDeskToolsServices(message);
  2679. } else if (message.type === 'service') {
  2680. showServiceDetailsDialog(message);
  2681. } else if ((message.type === 'getclip') && (currentNode != null) && (currentNode._id == message.nodeid)) {
  2682. if ((message.tag == 1) && (xxdialogTag === 'clipboard')) {
  2683. Q('d2clipText').value = message.data; // Put remote clipboard data into dialog box
  2684. } else if (message.tag === 2) {
  2685. if (navigator.clipboard != null) { navigator.clipboard.writeText(message.data).then(function() { }).catch(function(err) { console.log(err); }) } // Put remote clipboard data into our clipboard
  2686. }
  2687. } else if ((message.type === 'setclip') && (xxdialogTag === 'clipboard') && (currentNode != null) && (currentNode._id == message.nodeid)) {
  2688. // Display success/fail on the clipboard dialog box.
  2689. QH('dlgClipStatus', message.success ? '<span style=color:green>' + "Success" + '</span>' : '<span style=color:red>' + "Failed" + '</span>')
  2690. setTimeout(function () { try { QH('dlgClipStatus', ''); } catch (ex) { } }, 2000);
  2691. } else if ((message.type === 'userSessions') && (currentNode != null) && (currentNode._id === message.nodeid) && (desktop == null)) {
  2692. // Got list of user sessions
  2693. var userSessions = [];
  2694. if (message.data != null) { for (var i in message.data) { if ((message.data[i].State == 'Active') || (message.data[i].State == 'Connected') || (message.data[i].StationName == 'Console') || (debugmode == 3)) { userSessions.push(message.data[i]); } } }
  2695. if (userSessions.length === 0) { connectDesktop(null, 1, null, message.tag); } // No active sessions, do a normal connection.
  2696. else if (userSessions.length === 1) { connectDesktop(null, 1, userSessions[0].SessionId, message.tag); } // One active session, connect to it
  2697. else {
  2698. var x = '';
  2699. var sortBy = "{{{userSessionsSort}}}";
  2700. if (sortBy != '') {
  2701. userSessions.sort(function(a, b) {
  2702. if (!a[sortBy]) return -1; // a comes before b
  2703. if (!b[sortBy]) return 1; // b comes before a
  2704. if (a[sortBy] < b[sortBy]) return -1;
  2705. if (a[sortBy] > b[sortBy]) return 1;
  2706. return 0;
  2707. });
  2708. }
  2709. for (var i in userSessions) {
  2710. 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 ? (', ' + userSessions[i].StationName) : '');
  2711. if (userSessions[i].Username) { if (userSessions[i].Domain) { x += ' - ' + userSessions[i].Domain + '/' + userSessions[i].Username; } else { x += ' - ' + userSessions[i].Username; } }
  2712. x += '</div>';
  2713. }
  2714. QH('p11DeskSessionSelector', x);
  2715. QV('p11DeskSessionSelector', true);
  2716. }
  2717. } else if (message.type === 'psinfo') {
  2718. if (xxdialogTag === ('ps|' + message.nodeid + '|' + message.pid)) {
  2719. var x = '<div style=max-height:200px;overflow-y:auto>';
  2720. //x += addHtmlValue4("Process ID", message.pid);
  2721. if ((typeof message.value == 'object') && (Object.keys(message.value).length > 0)) {
  2722. // lets fix agent psinfo not being camelcase
  2723. message.value = jsonToCamel(message.value);
  2724. if(!message.value.cmd && message.value.path){ message.value.cmd = message.value.path }
  2725. if(!message.value.processUser && message.value.userName){ message.value.processUser = message.value.userName.split("\\")[1]; }
  2726. if(!message.value.processDomain && message.value.userName){ message.value.processDomain = message.value.userName.split("\\")[0]; }
  2727. if (message.value.processName) { x += '<div style=padding:4px><div style=padding-bottom:4px>' + "Process Name" + '</div><div style=word-wrap:break-word;padding-left:6px><b>' + message.value.processName + '</b></div></div>'; }
  2728. if (message.value.machineName) { x += addHtmlValue5("Machine Name", message.value.machineName); }
  2729. if (message.value.cmd) { x += '<div style=padding:4px><div style=padding-bottom:4px>' + "Command Line" + '</div><div style=word-wrap:break-word;padding-left:6px><b>' + message.value.cmd + '</b></div></div>'; }
  2730. if (message.value.mainWindowTitle) { x += '<div style=padding:4px><div style=padding-bottom:4px>' + "Window Title" + '</div><div style=word-wrap:break-word;padding-left:6px><b>' + message.value.mainWindowTitle + '</b></div></div>'; }
  2731. if (message.value.processUser) { x += addHtmlValue5("User", message.value.processUser); }
  2732. if (message.value.processDomain) { x += addHtmlValue5("Domain", message.value.processDomain); }
  2733. if (message.value.startTime) { x += addHtmlValue5("Start Time", message.value.startTime); }
  2734. x += '<hr />';
  2735. if (message.value.PriorityBoostEnabled) { x += addHtmlValue5("Priority Boost", message.value.PriorityBoostEnabled?"Enabled":"Disabled"); }
  2736. if (message.value.sessionId) { x += addHtmlValue5("Session Id", message.value.sessionId); }
  2737. if (message.value.handleCount) { x += addHtmlValue5("Handle Count", message.value.handleCount); }
  2738. if (message.value.privilegedProcessorTime) { x += addHtmlValue5("Privileged Processor Time", format("{0} seconds", message.value.privilegedProcessorTime)); }
  2739. if (message.value.totalProcessorTime) { x += addHtmlValue5("Total Processor Time", format("{0} seconds", message.value.totalProcessorTime)); }
  2740. if (message.value.userProcessorTime) { x += addHtmlValue5("User Processor Time", format("{0} seconds", message.value.userProcessorTime)); }
  2741. if (message.value.nonpagedSystemMemorySize) { x += addHtmlValue5("Non Paged Memory", getNiceSize2(message.value.nonpagedSystemMemorySize)); }
  2742. if (message.value.pagedMemorySize) { x += addHtmlValue5("Paged Memory", getNiceSize2(message.value.pagedMemorySize)); }
  2743. if (message.value.privateMemorySize) { x += addHtmlValue5("Private Memory", getNiceSize2(message.value.privateMemorySize)); }
  2744. if (message.value.virtualMemorySize) { x += addHtmlValue5("Virtual Memory", getNiceSize2(message.value.virtualMemorySize)); }
  2745. if (message.value.workingSet) { x += addHtmlValue5("Working Set", getNiceSize2(message.value.workingSet)); }
  2746. if (message.value.peakWorkingSet) { x += addHtmlValue5("Peak Working Set", getNiceSize2(message.value.peakWorkingSet)); }
  2747. if (message.value.peakPagedMemorySize) { x += addHtmlValue5("Peak Paged Memory", getNiceSize2(message.value.peakPagedMemorySize)); }
  2748. if (message.value.peakVirtualMemorySize) { x += addHtmlValue5("Peak Virtual Memory", getNiceSize2(message.value.peakVirtualMemorySize)); }
  2749. } else {
  2750. x += "No information provided";
  2751. }
  2752. x += '</div>';
  2753. QH('id_dialogOptions', x);
  2754. }
  2755. } else if (message.type === 'deskBackground') {
  2756. if(message.data != "") {
  2757. Q('DeskBackgroundButtonImage').src = 'images/icon-background.png';
  2758. }else{
  2759. Q('DeskBackgroundButtonImage').src = 'images/icon-background-red.png';
  2760. }
  2761. }
  2762. }
  2763. } else {
  2764. if (message.type === 'notify') { // This is a notification message.
  2765. var n = { text: message.value, title: message.title, icon: message.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
  2766. if (message.id != null) { n.id = message.id; }
  2767. if (message.tag != null) { n.tag = message.tag; }
  2768. if (message.url != null) { n.url = message.url; }
  2769. if (message.username != null) { n.username = message.username; }
  2770. if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
  2771. addNotification(n);
  2772. }
  2773. }
  2774. break;
  2775. }
  2776. case 'getnetworkinfo': {
  2777. if (currentNode._id != message.nodeid) return;
  2778. updateDeviceDetails(getNodeFromId(message.nodeid), null, message);
  2779. if ((xxdialogMode == 2) && (xxdialogTag == 'if' + message.nodeid)) {
  2780. var x = '<div class=dialogText>';
  2781. if (currentNode.firstconnect) { x += addHtmlValue2("First agent connection", printDateTime(new Date(currentNode.firstconnect))); }
  2782. if (currentNode.lastconnect) { x += addHtmlValue2("Last agent connection", printDateTime(new Date(currentNode.lastconnect))); }
  2783. if (currentNode.lastaddr) {
  2784. var splitip = currentNode.lastaddr.split(':');
  2785. if (splitip.length > 2) {
  2786. // IPv6
  2787. x += addHtmlValue2("Last agent address", currentNode.lastaddr + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(currentNode.lastaddr) + '") width=10 height=10>');
  2788. } else {
  2789. // IPv4
  2790. if (isPrivateIP(currentNode.lastaddr)) {
  2791. x += addHtmlValue2("Last agent address", splitip[0] + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(splitip[0]) + '") width=10 height=10>');
  2792. } else {
  2793. x += addHtmlValue2("Last agent address", '<a href="https://iplocation.com/?ip=' + splitip[0] + '" rel="noreferrer noopener" target="MeshIPLoopup">' + splitip[0] + '</a> <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(splitip[0]) + '") width=10 height=10>');
  2794. }
  2795. }
  2796. }
  2797. if (message.updateTime != null) { x += addHtmlValue2("Last interfaces update", printDateTime(new Date(message.updateTime))); }
  2798. if (message.netif != null) {
  2799. // Old style
  2800. for (var i in message.netif) {
  2801. var net = message.netif[i];
  2802. x += '<hr />'
  2803. if (net.name) { x += addHtmlValue2("Name", '<b>' + EscapeHtml(net.name) + '</b>'); }
  2804. if (net.desc) { x += addHtmlValue2("Description", EscapeHtml(net.desc).replace('(R)', '&reg;').replace('(r)', '&reg;')); }
  2805. if (net.dnssuffix) { x += addHtmlValue2("DNS suffix", EscapeHtml(net.dnssuffix) + ' <img src="images/link4.png" title="' + "Copy name to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.dnssuffix) + '") width=10 height=10>'); }
  2806. if (net.mac) { x += addHtmlValue2("MAC address", '<a href="https://maclookup.app/search/result?mac=' + net.mac.substring(0, 6) + '" rel="noreferrer noopener" target="MeshMACLoopup">' + EscapeHtml(net.mac.toLowerCase()) + '</a> <img src="images/link4.png" title="' + "Copy MAC address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.mac.toLowerCase()) + '") width=10 height=10>'); }
  2807. if (net.v4addr) { x += addHtmlValue2("IPv4 address", EscapeHtml(net.v4addr) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.v4addr) + '") width=10 height=10>'); }
  2808. if (net.v4mask) { x += addHtmlValue2("IPv4 mask", EscapeHtml(net.v4mask) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.v4mask) + '") width=10 height=10>'); }
  2809. if (net.v4gateway) { x += addHtmlValue2("IPv4 gateway", EscapeHtml(net.v4gateway) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.v4gateway) + '") width=10 height=10>'); }
  2810. if (net.gatewaymac) { x += addHtmlValue2("Gateway MAC", '<a href="https://maclookup.app/search/result?mac=' + net.gatewaymac.substring(0, 6) + '" rel="noreferrer noopener" target="MeshMACLoopup">' + EscapeHtml(net.gatewaymac.toLowerCase()) + '</a> <img src="images/link4.png" title="' + "Copy MAC address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net.gatewaymac.toLowerCase()) + '") width=10 height=10>'); }
  2811. }
  2812. } else if (message.netif2 != null) {
  2813. // New style
  2814. for (var i in message.netif2) {
  2815. var net = message.netif2[i];
  2816. if ((Array.isArray(net) == false) || (net.length < 1) || (net[0] == null) || ((typeof net[0].mac == 'string') && (net[0].mac.startsWith('00:00:00:00')))) continue;
  2817. x += '<hr />'
  2818. x += addHtmlValue2("Name", '<b>' + EscapeHtml(i) + '</b>');
  2819. if (typeof net[0].mac == 'string') { x += addHtmlValue2("MAC address", '<a href="https://maclookup.app/search/result?mac=' + net[0].mac.split(':').join('').substring(0, 6) + '" rel="noreferrer noopener" target="MeshMACLoopup">' + EscapeHtml(net[0].mac.toLowerCase()) + '</a> <img src="images/link4.png" title="' + "Copy MAC address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(net[0].mac.toLowerCase()) + '") width=10 height=10>'); }
  2820. if (net[0].fqdn) { x += addHtmlValue2("FQDN", net[0].fqdn); }
  2821. for (var j = 0; j < net.length; j++) {
  2822. var netif = net[j];
  2823. if (netif.family == 'IPv6') {
  2824. if (netif.address) { x += addHtmlValue2("IPv6 address", EscapeHtml(netif.address) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(netif.address) + '") width=10 height=10>'); }
  2825. if (netif.netmask) { x += addHtmlValue2("IPv6 mask", EscapeHtml(netif.netmask) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(netif.netmask) + '") width=10 height=10>'); }
  2826. if (netif.gateway) { x += addHtmlValue2("IPv6 gateway", EscapeHtml(netif.gateway) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(netif.gateway) + '") width=10 height=10>'); }
  2827. } else if (netif.family == 'IPv4') {
  2828. if (netif.address) { x += addHtmlValue2("IPv4 address", EscapeHtml(netif.address) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(netif.address) + '") width=10 height=10>'); }
  2829. if (netif.netmask) { x += addHtmlValue2("IPv4 mask", EscapeHtml(netif.netmask) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(netif.netmask) + '") width=10 height=10>'); }
  2830. if (netif.gateway) { x += addHtmlValue2("IPv4 gateway", EscapeHtml(netif.gateway) + ' <img src="images/link4.png" title="' + "Copy address to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(netif.gateway) + '") width=10 height=10>'); }
  2831. }
  2832. }
  2833. }
  2834. } else {
  2835. x += '<hr />'
  2836. x += "No network interface information available for this device.";
  2837. }
  2838. x += '</div>';
  2839. QH('d2netinfo', x);
  2840. }
  2841. break;
  2842. }
  2843. case 'serverversion': {
  2844. if ((xxdialogMode == 2) && (xxdialogTag == 'MeshCentralServerUpdate')) {
  2845. var x = '<div class=dialogText>';
  2846. if (!message.tags.current) { message.tags.current = "Unknown"; }
  2847. if (!message.tags.stable) { message.tags.stable = "Unknown"; }
  2848. if (!message.tags.latest) { message.tags.latest = "Unknown"; }
  2849. x += addHtmlValue2("Current Version", '<b>' + EscapeHtml(message.tags.current) + '</b>');
  2850. x += '<hr />';
  2851. x += addHtmlValue2('<label><input id=d2updateCheck1 type=checkbox onclick=server_showVersionDlgUpdate()' + (((message.tags.stable == "Unknown") || (message.tags.current == message.tags.stable) || ((features & 2048) == 0))?' disabled':'') + ' /> ' + "Stable Version" + '</label>', '<b>' + EscapeHtml(message.tags.stable) + '</b>');
  2852. x += addHtmlValue2('<label><input id=d2updateCheck2 type=checkbox onclick=server_showVersionDlgUpdate()' + (((message.tags.latest == "Unknown") || (message.tags.current == message.tags.latest) || ((features & 2048) == 0))?' disabled':'') + ' /> ' + "Latest Version" + '</label>', '<b>' + EscapeHtml(message.tags.latest) + '</b>');
  2853. x += '</div>';
  2854. if (((message.tags.current == message.tags.latest) && (message.tags.current == message.tags.stable)) || ((features & 2048) == 0)) {
  2855. setDialogMode(2, "MeshCentral Version", 1, null, x);
  2856. } else {
  2857. x += '<hr />' + "Check and click OK to start server self-update.";
  2858. setDialogMode(2, "MeshCentral Version", 3, server_showVersionDlgEx, x, message.tags);
  2859. server_showVersionDlgUpdate();
  2860. }
  2861. }
  2862. break;
  2863. }
  2864. case 'servererrors': {
  2865. if ((xxdialogMode == 2) && (xxdialogTag == 'MeshCentralServerErrors')) {
  2866. if (message.data == null) {
  2867. setDialogMode(2, "Server Errors", 1, null, "Server has no error log.");
  2868. } else {
  2869. var x = '<div class="dialogText dialogTextLog"><pre id=d2ServerErrorsLogPre>' + EscapeHtml(message.data) + '</pre></div>';
  2870. x += '<br /><div style=float:right><img src=images/link4.png height=10 width=10 title="' + "Download error log" + '" style=cursor:pointer onclick=d2CopyServerErrorsToClip()></div>';
  2871. x += '<div><label><input id=d2clearErrorsCheck type=checkbox onclick=server_showErrorsDlgUpdate() /> ' + "Check and click OK to clear error log." + '</label></div>';
  2872. setDialogMode(2, "MeshCentral Server Errors", 3, server_showErrorsDlgEx, x);
  2873. server_showErrorsDlgUpdate();
  2874. }
  2875. }
  2876. break;
  2877. }
  2878. case 'serverconfig': {
  2879. if ((xxdialogMode == 2) && (xxdialogTag == 'MeshCentralServerConfig')) {
  2880. if (message.data == null) {
  2881. setDialogMode(2, "Server Configuration", 1, null, "Server has no config file.");
  2882. } else {
  2883. setDialogMode(4, "Server Configuration", 2);
  2884. QV('d4EncodingButton', false);
  2885. QV('d4LineBreakButton', false);
  2886. QS('dialog').width = 'auto';
  2887. QS('dialog').bottom = '80px';
  2888. QS('dialog').top = QS('dialog').left = QS('dialog').right = '100px';
  2889. Q('d4editorarea').value = message.data;
  2890. Q('d4editorarea').setAttribute('readonly','readonly');
  2891. }
  2892. }
  2893. break;
  2894. }
  2895. case 'serverconsole': {
  2896. p15consoleReceive('serverconsole', message.value);
  2897. break;
  2898. }
  2899. case 'events': {
  2900. if ((message.nodeid != null) && (currentNode != null) && (message.nodeid == currentNode._id)) {
  2901. currentDeviceEvents = message.events;
  2902. mainUpdate(1024);
  2903. } else if ((message.userid != null) && (currentUser != null) && (message.userid == currentUser._id)) {
  2904. currentUserEvents = message.events;
  2905. mainUpdate(2048);
  2906. } else {
  2907. events = message.events;
  2908. mainUpdate(32);
  2909. }
  2910. break;
  2911. }
  2912. case 'recordings': {
  2913. p52recordings = message.events;
  2914. if (message.error != null) { p52recordings = message.error; }
  2915. updateRecordings();
  2916. break;
  2917. }
  2918. case 'getcookie': {
  2919. if (message.tag == 'MCRouter') {
  2920. var servername = serverinfo.name;
  2921. 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.
  2922. var domainUrlNoSlash = domainUrl.substring(0, domainUrl.length - 1);
  2923. var portStr = (serverinfo.port == 443) ? '' : (':' + serverinfo.port);
  2924. var url = 'mcrouter://' + servername + portStr + domainUrl + 'control.ashx?c=' + authCookie + '&t=' + serverinfo.tlshash + '&l={{{lang}}}' + (urlargs.key?('&key=' + urlargs.key):'');
  2925. if (message.nodeid != null) { url += ('&nodeid=' + message.nodeid); }
  2926. if (message.tcpport != null) { url += ('&protocol=1&remoteport=' + message.tcpport); }
  2927. if (message.localRelay) { url += '&local=1'; }
  2928. if (message.localport) { url += '&localport=' + message.localport; }
  2929. if (message.name != null) { url += ('&name=' + encodeURIComponentEx(message.name)); }
  2930. if (message.ip != null) { url += ('&remoteip=' + message.ip); }
  2931. url += ('&appid=' + message.protocol + '&autoexit=1'); // Protocol: 0 = Custom, 1 = HTTP, 2 = HTTPS, 3 = RDP, 4 = PuTTY, 5 = WinSCP, 6 = MCRDesktop, 7 = MCRFiles
  2932. //console.log(url);
  2933. downloadFile(url, '');
  2934. } else if (message.tag == 'novnc') {
  2935. 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}}}';
  2936. var node = getNodeFromId(message.nodeid);
  2937. if (node != null) { vncurl += '&name=' + encodeURIComponentEx(node.name); }
  2938. safeNewWindow(vncurl, 'mcnovnc/' + message.nodeid);
  2939. } else if (message.tag == 'mstsc') {
  2940. var rdpurl = window.location.origin + domainUrl + 'mstsc.html?ws=' + message.cookie + (urlargs.key?('&key=' + urlargs.key):'');
  2941. var node = getNodeFromId(message.nodeid);
  2942. if (node != null) { rdpurl += '&name=' + encodeURIComponentEx(node.name); }
  2943. if (message.localRelay) { rdpurl += '&local=1'; }
  2944. safeNewWindow(rdpurl, 'mcmstsc/' + message.nodeid);
  2945. } else if (message.tag == 'ssh') {
  2946. var sshurl = window.location.origin + domainUrl + 'ssh.html?ws=' + message.cookie + (urlargs.key?('&key=' + urlargs.key):'');
  2947. var node = getNodeFromId(message.nodeid);
  2948. if (node != null) { sshurl += '&name=' + encodeURIComponentEx(node.name); }
  2949. if (message.localRelay) { sshurl += '&local=1'; }
  2950. safeNewWindow(sshurl, 'mcssh/' + message.nodeid);
  2951. }
  2952. break;
  2953. }
  2954. case 'getNotes': {
  2955. var n = Q('d2devNotes');
  2956. if (n && (message.id == decodeURIComponent(n.attributes['noteid'].value))) {
  2957. if (message.notes) { QH('d2devNotes', decodeURIComponent(message.notes)); } else { QH('d2devNotes', ''); }
  2958. var ro = (n.attributes['ro'].value == 'true');
  2959. if (ro == false) { // If we have permissions, set read/write on this note.
  2960. n.removeAttribute('readonly');
  2961. QE('idx_dlgOkButton', true);
  2962. QV('idx_dlgOkButton', true);
  2963. focusTextBox('d2devNotes');
  2964. }
  2965. }else{
  2966. Q('notesPanelArea').innerHTML = (message.notes && marked && DOMPurify) ? DOMPurify.sanitize(marked.parse(decodeURIComponent(message.notes), { breaks: true }), { USE_PROFILES: { html: true } }) : '';
  2967. if ((showNotesPanel === 'true') && message.notes) { QV('notesPanel',true); }else{ QV('notesPanel', false); }
  2968. }
  2969. break;
  2970. }
  2971. case 'otpauth-request': {
  2972. if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-request')) {
  2973. if (message.err != null) {
  2974. var otpauthErrors = [ '', "2FA is locked", "Backup codes are locked", "Login token in use", "OTP 2FA not allowed", "Account is locked", "Unable to load OTPLIB" ];
  2975. if ((message.err > 0) && (message.err < otpauthErrors.length)) { QH('d2optinfo', otpauthErrors[message.err]); } else { QH('d2optinfo', format("Error #{0}", message.err)); }
  2976. } else {
  2977. var secret = message.secret;
  2978. if (secret.length == 52) { secret = secret.split(/(.............)/).filter(Boolean).join(' '); }
  2979. else if (secret.length == 32) { secret = secret.split(/(....)/).filter(Boolean).join(' '); secret = secret.substring(0, 20) + '<br/>' + secret.substring(20) }
  2980. QH('d2optinfo', '<table style=width:380px><tr><td style=vertical-align:top>' + 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 and scan the barcode, use <a href=\"{0}\" rel=\"noreferrer noopener\" target=_blank>this link</a> or enter the secret. Then, enter the current 6 digit token below to activate 2-Step login.", message.url) + '<br /><br />' + 'Secret <img src=images/link4.png height=10 width=10 title="' + "Copy Secret to clipboard" + '" style=cursor:pointer onclick=d2CopySecretToClip()>' + '<br /><tt id=d2optsecret secret="' + message.secret + '" style=font-size:12px>' + secret + '</tt><br /><br /></td><td style=width:1px;vertical-align:top><a href="' + message.url + '" rel="noreferrer noopener" target=_blank><div id="qrcode"></div></a></td><tr><td colspan=2 style="text-align:center;border-top:1px solid black"><br />' + "Enter the token here for 2-step login:" + ' <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></td></table>');
  2981. new QRCode(Q('qrcode'), { text: message.url, width: 128, height: 128, colorDark: '#000000', colorLight: '#EEE', correctLevel: QRCode.CorrectLevel.H });
  2982. QV('idx_dlgOkButton', true);
  2983. QE('idx_dlgOkButton', false);
  2984. Q('d2otpauthinput').focus();
  2985. }
  2986. }
  2987. break;
  2988. }
  2989. case 'otpauth-setup': {
  2990. if (xxdialogMode) return;
  2991. 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."));
  2992. break;
  2993. }
  2994. case 'otpauth-clear': {
  2995. if (xxdialogMode) return;
  2996. 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."));
  2997. break;
  2998. }
  2999. case 'otpauth-getpasswords': {
  3000. if (xxdialogMode) return;
  3001. var x = "One time tokens can be used as secondary authentication. Generate a set, print them and keep them in a safe place.";
  3002. 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 class=selecttext style=width:100%;text-align:center>';
  3003. if (message.passwords) {
  3004. var j = 0, clipb = '';
  3005. for (var i in message.passwords) {
  3006. if (++j % 2) { x += '<tr>'; }
  3007. var p = '' + message.passwords[i].p;
  3008. while (p.length < 8) { p = '0' + p; }
  3009. if (message.passwords[i].u === true) {
  3010. x += '<td>' + p.substring(0, 4) + '&nbsp;' + p.substring(4);
  3011. if (clipb != '') { clipb += ' '; }
  3012. clipb += p;
  3013. } else {
  3014. x += '<td><strike style=color:#BBB>' + p.substring(0, 4) + '&nbsp;' + p.substring(4); + '</strike>';
  3015. }
  3016. }
  3017. } else {
  3018. x += '<tr><td>' + "No Active Tokens";
  3019. }
  3020. x += '</table></div></div><br />';
  3021. x += '<div><input type=button value=' + "Close" + ' onclick=setDialogMode(0) style=float:right></input>';
  3022. x += '<input type=button value="' + "Generate New Tokens" + '" onclick="account_manageOtp(1);"></input>';
  3023. if (message.passwords != null) {
  3024. x += '<input type=button value="' + "Clear Tokens" + '" onclick="account_manageOtp(2);"></input>';
  3025. x += '&nbsp;<img src=images/link4.png height=10 width=10 title="' + "Copy valid codes to clipboard" + '" style=cursor:pointer onclick=copyTextToClip2("' + encodeURIComponentEx(clipb) + '")>';
  3026. }
  3027. x += '</div><br />';
  3028. setDialogMode(2, "Manage Backup Codes", 8, null, x, 'otpauth-manage');
  3029. break;
  3030. }
  3031. case 'otp-hkey-get': {
  3032. if (xxdialogMode && (xxdialogTag != 'otpauth-hardware-manage')) return;
  3033. var start = '<div style="border-radius:6px;border:2px solid #CCC;background-color:#BBB;width:100%;box-sizing:border-box;margin-bottom:6px"><div style="margin:3px;font-family:Arial, Helvetica, sans-serif;font-size:16px;font-weight:bold"><table style=width:100%;text-align:left>';
  3034. var end = '</table></div></div>';
  3035. var x = '<a href="https://www.yubico.com/" rel="noreferrer noopener" target="_blank">' + "Hardware keys" + '</a> ' + "are used as secondary login authentication.";
  3036. x += '<div style="max-height:150px;overflow-y:auto;overflow-x:hidden;margin-top:6px;margin-bottom:6px">';
  3037. if (message.keys && message.keys.length > 0) {
  3038. for (var i in message.keys) {
  3039. var key = message.keys[i], type = (key.type == 2)?'OTP':'WebAuthn';
  3040. x += start + '<tr style=margin:5px><td style=width:30px><img width=24 height=18 src="images/hardware-key-' + type + '-24.png" style=margin-top:4px><td style=width:250px>' + key.name + '<td><input type=button value="' + "Remove" + '" onclick=account_removehkey(' + key.i + ')></input>' + end;
  3041. }
  3042. } else {
  3043. x += start + '<tr style=text-align:center><td>' + "No Keys Configured" + end;
  3044. }
  3045. x += '</div>';
  3046. x += '<div><input type=button value="' + "Close" + '" onclick=setDialogMode(0) style=float:right></input>';
  3047. var hkeycount = (typeof userinfo.otphkeys == 'number') ? userinfo.otphkeys : 0;
  3048. if ((typeof serverinfo.maxfidokeys != 'number') || (serverinfo.maxfidokeys > hkeycount)) { // Check if we we reached maximum hardware keys
  3049. if ((features & 0x00020000) != 0) { x += '<input id=d2addkey3 type=button value="' + "Add Key" + '" onclick="account_addhkey(3);"></input>'; }
  3050. if ((features & 0x00004000) != 0) { x += '<input id=d2addkey2 type=button value="' + "Add YubiKey&reg; OTP" + '" onclick="account_addhkey(2);"></input>'; }
  3051. } else {
  3052. x += "Maximum keys reached.";
  3053. }
  3054. x += '</div><br />';
  3055. setDialogMode(2, "Manage Security Keys", 8, null, x, 'otpauth-hardware-manage');
  3056. if (u2fSupported() == false) { QE('d2addkey1', false); }
  3057. break;
  3058. }
  3059. case 'otp-hkey-yubikey-add': {
  3060. if (message.result) {
  3061. meshserver.send({ action: 'otp-hkey-get' }); // Success, ask for the full list of keys.
  3062. } else {
  3063. setDialogMode(2, "Add Security Key", 1, null, '<br />' + "Error, Unable to add key." + '<br /><br />');
  3064. }
  3065. break;
  3066. }
  3067. case 'otp-hkey-setup-response': {
  3068. if (xxdialogMode && (xxdialogTag != 'otpauth-hardware-manage')) return;
  3069. if (message.result == true) {
  3070. meshserver.send({ action: 'otp-hkey-get' }); // Success, ask for the full list of keys.
  3071. } else {
  3072. setDialogMode(2, "Add Security Key", 1, null, '<br />' + "ERROR: Unable to add key." + '<br /><br />', 'otpauth-hardware-manage');
  3073. }
  3074. break;
  3075. }
  3076. case 'webauthn-startregister': {
  3077. if (xxdialogMode && (xxdialogTag != 'otpauth-hardware-manage')) return;
  3078. var x = "Press the key button now." + '<br /><br /><div style=width:100%;text-align:center><img width=120 height=117 src="images/hardware-keypress-120.png" /></div><input id=dp1keyname style=display:none value=' + message.name + ' />';
  3079. setDialogMode(2, "Add Security Key", 2, null, x);
  3080. var publicKey = message.request;
  3081. message.request.challenge = Uint8Array.from(atob(message.request.challenge), function (c) { return c.charCodeAt(0) })
  3082. message.request.user.id = Uint8Array.from(atob(message.request.user.id), function (c) { return c.charCodeAt(0) })
  3083. navigator.credentials.create({ publicKey: publicKey }).then(function(newCredentialInfo) {
  3084. // Public key credential
  3085. meshserver.send({ action: 'webauthn-endregister', response: { rawId: btoa(String.fromCharCode.apply(null, new Uint8Array(newCredentialInfo.rawId))), response: { attestationObject: btoa(String.fromCharCode.apply(null, new Uint8Array(newCredentialInfo.response.attestationObject))), clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(newCredentialInfo.response.clientDataJSON))) }, type: newCredentialInfo.type } });
  3086. setDialogMode(0);
  3087. }, function(error) {
  3088. // Error
  3089. console.log("ERROR: " + error);
  3090. setDialogMode(2, "Add Security Key", 1, null, "ERROR: " + error);
  3091. });
  3092. break;
  3093. }
  3094. case 'verifyPhone': {
  3095. if (xxdialogMode && (xxdialogTag != 'verifyPhone')) return;
  3096. var x = '<table><tr><td><img src="images/phone80.png" style=padding:8px>';
  3097. x += '<td>' + "Check your phone and enter the verification code.";
  3098. 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>';
  3099. setDialogMode(2, "Phone Notifications", 3, account_managePhoneConfirm, x, message.cookie);
  3100. Q('d2phoneCodeInput').focus();
  3101. account_managePhoneCodeValidate();
  3102. break;
  3103. }
  3104. case 'verifyMessaging': {
  3105. if (xxdialogMode && (xxdialogTag != 'verifyMessaging')) return;
  3106. var x = '<table><tr><td><img src="images/messaging40.png" style=padding:8px>';
  3107. x += '<td>' + "Check your messaging application and enter the verification code.";
  3108. 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>';
  3109. setDialogMode(2, "Messaging Notifications", 3, account_manageMessagingConfirm, x, message.cookie);
  3110. Q('d2phoneCodeInput').focus();
  3111. account_managePhoneCodeValidate();
  3112. break;
  3113. }
  3114. case 'fileoperation': {
  3115. // View the file in the dialog box
  3116. var p5editSaveBack = function(b, tag) {
  3117. var data;
  3118. var value = Q('d4editorarea').value;
  3119. value = d4EditLineBreakVal === 0 ? value.replace(/\r?\n|\r/g, '\r\n') : // Windows
  3120. d4EditLineBreakVal === 2 ? value.replace(/\r\n|\n/g, '\r') : // Mac
  3121. value.replace(/\r\n|\r/g, '\n'); // Linux
  3122. if (d4EditEncodingVal == 1) {
  3123. data = encode_utf8(value); // UTF8 encoding
  3124. } else {
  3125. data = value; // RAW encoding
  3126. }
  3127. meshserver.send({ action: 'fileoperation', fileop: 'set', path: tag.path, file: tag.file, data: btoa(data) });
  3128. }
  3129. setDialogMode(4, EscapeHtml(message.file), 3, p5editSaveBack, null, message);
  3130. QV('d4EncodingButton', true);
  3131. QV('d4LineBreakButton', true);
  3132. QS('dialog').width = 'auto';
  3133. QS('dialog').bottom = '80px';
  3134. QS('dialog').top = QS('dialog').left = QS('dialog').right = '100px';
  3135. if (d4EditEncodingVal == 1) {
  3136. Q('d4editorarea').value = decode_utf8(atob(message.data)); // UTF8 Encoding
  3137. } else {
  3138. Q('d4editorarea').value = atob(message.data); // RAW Encoding
  3139. }
  3140. break;
  3141. }
  3142. case 'event': {
  3143. if (!message.event.nolog) {
  3144. if (currentNode && (message.event.nodeid == currentNode._id) && (currentDeviceEvents != null)) {
  3145. // If this event has a nodeid and we are looking at this node, update the log in real time.
  3146. if ((message.event.action == p16filterevents.value) || (p16filterevents.value == "")) {
  3147. if(currentDeviceEvents != null) {
  3148. currentDeviceEvents.unshift(message.event);
  3149. var eventLimit = parseInt(p16limitdropdown.value);
  3150. while (currentDeviceEvents.length > eventLimit) { currentDeviceEvents.pop(); } // Remove element(s) at the end
  3151. }
  3152. mainUpdate(1024);
  3153. }
  3154. }
  3155. if (currentUser && (message.event.userid == currentUser._id)) {
  3156. // If this event has a userid and we are looking at this user, update the log in real time.
  3157. if ((message.event.action == p31filterevents.value) || (p31filterevents.value == "")) {
  3158. if(currentUserEvents != null) {
  3159. currentUserEvents.unshift(message.event);
  3160. var eventLimit = parseInt(p31limitdropdown.value);
  3161. while (currentUserEvents.length > eventLimit) { currentUserEvents.pop(); } // Remove element(s) at the end
  3162. }
  3163. mainUpdate(2048);
  3164. }
  3165. }
  3166. // Add this event to the main events log.
  3167. if ((message.event.action == p3filterevents.value) || (p3filterevents.value == "")) {
  3168. if(events != null) {
  3169. events.unshift(message.event);
  3170. var eventLimit = parseInt(p3limitdropdown.value);
  3171. while (events.length > eventLimit) { events.pop(); } // Remove element(s) at the end
  3172. }
  3173. mainUpdate(32);
  3174. }
  3175. }
  3176. if (message.event.noact) break; // Take no action on this event
  3177. switch (message.event.action) {
  3178. case 'serverinfochange': {
  3179. if (message.event.lock2factor != null) { serverinfo.lock2factor = message.event.lock2factor; updateSelf(); updateSiteAdmin(); }
  3180. break;
  3181. }
  3182. case 'deviceShareUpdate': {
  3183. if (message.event.nodeid != deviceSharesReq) break;
  3184. deviceSharesNode = message.event.nodeid;
  3185. deviceShares = message.event.deviceShares;
  3186. if (currentNode._id == deviceSharesNode) { gotoDevice(currentNode._id, xxcurrentView, true); }
  3187. break;
  3188. }
  3189. case 'agentlog': {
  3190. if (message.event.msgid == 98) {
  3191. // This is a agent help request, popup a notification.
  3192. if (GetNodeRights(message.event.nodeid) > 0) {
  3193. var n = getNodeFromId(message.event.nodeid);
  3194. addNotification({ text: format("Help requested from {0}: {1}", message.event.msgArgs[0], message.event.msgArgs[1]), title: n.name, icon: n.icon, nodeid: message.event.nodeid });
  3195. }
  3196. }
  3197. break;
  3198. }
  3199. case 'recording': {
  3200. if ((p52recordings != null) && (typeof p52recordings == 'object')) { p52recordings.unshift(message.event); message.event.present = 1; updateRecordings(); }
  3201. break;
  3202. }
  3203. case 'userWebState': {
  3204. // New user web state, update the web page as needed
  3205. try {
  3206. if (localStorage != null) {
  3207. // TODO: The problem with this "old" values is that if changed from a different tab, the old and new values will be the same.
  3208. // So comparing against these values is not a good idea.
  3209. var oldShowRealNames = localStorage.getItem('showRealNames');
  3210. var oldUiMode = localStorage.getItem('uiMode');
  3211. var oldSort = localStorage.getItem('sort');
  3212. var oldLoctag = localStorage.getItem('loctag');
  3213. var oldNightMode = localStorage.getItem('nightMode');
  3214. var oldFooterBar = localStorage.getItem('footerBar');
  3215. var webstate = JSON.parse(message.event.state);
  3216. for (var i in webstate) { localStorage.setItem(i, webstate[i]); }
  3217. // Update the web page
  3218. //if ((webstate.deskAspectRatio != null) && (webstate.deskAspectRatio != deskAspectRatio)) { deskAspectRatio = webstate.deskAspectRatio; deskAdjust(); }
  3219. if ((webstate.showRealNames != null) && (webstate.showRealNames != oldShowRealNames)) { showRealNames = Q('RealNameCheckBox').checked = (webstate.showRealNames == '1'); mainUpdate(6); }
  3220. if ((webstate.uiMode != null) && (webstate.uiMode != oldUiMode)) { userInterfaceSelectMenu(parseInt(webstate.uiMode)); }
  3221. if ((webstate.sort != null) && (webstate.sort != oldSort)) { document.getElementById('sortselect').selectedIndex = sort = parseInt(webstate.sort); mainUpdate(6); }
  3222. if ((webstate.loctag != null) && (webstate.loctag != oldLoctag)) { if (webstate.loctag != null) { args.locale = webstate.loctag; } else { delete args.locale; } mainUpdate(0xFFFFFFFF); }
  3223. if ((webstate.nightMode != null) && (webstate.nightMode != oldNightMode)) { putstore('nightMode', webstate.nightMode); setNightMode(); }
  3224. if ((webstate.footerBar != null) && (webstate.footerBar != oldFooterBar)) { footerBar = (webstate.footerBar == '1'); QS('container')['grid-template-rows'] = null; QS('container')['-ms-grid-rows'] = null; adjustPanels(); }
  3225. if ((webstate.stars != null) && (webstate.stars != JSON.stringify(stars))) {
  3226. stars = JSON.parse(webstate.stars);
  3227. if (Q('DevFilterSelect').value == 3) { mainUpdate(5); } else { mainUpdate(4); }
  3228. if (currentNode) { refreshDevice(currentNode._id); }
  3229. }
  3230. if (webstate.deskKeyShortcuts != null) {
  3231. // Set the user's desktop shortcut keys
  3232. deskKeyboardShortcuts = [];
  3233. var deskKeyboardShortcutsStr = webstate.deskKeyShortcuts.split(',');
  3234. for (var i in deskKeyboardShortcutsStr) { if (deskKeyboardShortcutsStr[i] != "") { deskKeyboardShortcuts.push(parseInt(deskKeyboardShortcutsStr[i])); } }
  3235. updateDeskShortcutKeys();
  3236. }
  3237. if (webstate.deskStrings != null) {
  3238. // Set the user's desktop strings
  3239. var s = null;
  3240. try { s = JSON.parse(webstate.deskStrings); } catch (ex) {}
  3241. if (Array.isArray(s)) { deskKeyboardStrings = s; updateDesktopStrings(); }
  3242. }
  3243. }
  3244. } catch (ex) {}
  3245. break;
  3246. }
  3247. case 'servertimelinestats': { addServerTimelineStats(message.event.data); break; }
  3248. case 'accountcreate':
  3249. case 'accountchange': {
  3250. // An account was created or changed
  3251. if ((typeof message.event.account != 'object') || (message.event.account == null)) { console.log(message.event); return; }
  3252. if (userinfo._id == message.event.account._id) {
  3253. var newsiteadmin = message.event.account.siteadmin?message.event.account.siteadmin:0;
  3254. var oldsiteadmin = userinfo.siteadmin?userinfo.siteadmin:0;
  3255. var newRemoveRights = message.event.account.removeRights?message.event.account.removeRights:0;
  3256. var oldRemoveRights = userinfo.removeRights?userinfo.removeRights:0;
  3257. if ((message.event.account.quota != userinfo.quota) || (((userinfo.siteadmin & 8) == 0) && ((message.event.account.siteadmin & 8) != 0))) { meshserver.send({ action: 'files' }); }
  3258. var oldgroups = userinfo.groups;
  3259. userinfo = message.event.account;
  3260. if ((oldsiteadmin != newsiteadmin) || (oldRemoveRights != newRemoveRights) || (message.event.accountImageChange == 1)) { // If the site admin permission or user image has changed...
  3261. if (message.event.accountImageChange == 1) { userinfo.accountImageRnd = Math.floor(Math.random() * 9999999999); }
  3262. updateSiteAdmin();
  3263. }
  3264. updateSelf();
  3265. if ((userinfo.siteadmin & 2) != 0) {
  3266. // Compare our groups
  3267. var og = oldgroups ? oldgroups : [];
  3268. var ng = userinfo.groups ? userinfo.groups : [];
  3269. if (og.join(',') != ng.join(',')) {
  3270. // Our groups have changed, re-ask for a list of users.
  3271. users = wssessions = null;
  3272. meshserver.send({ action: 'users' });
  3273. meshserver.send({ action: 'wssessioncount' });
  3274. }
  3275. }
  3276. // If our list of nodes may have changes, request the new list now.
  3277. if (message.event.nodeListChange == userinfo._id) { meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip }); }
  3278. }
  3279. if (currentNode) { refreshDevice(currentNode._id); }
  3280. if (users == null) break;
  3281. // Check if the account is part of our user group
  3282. if ((userinfo.groups == null) || (userinfo.groups.length == 0) || (findOne(message.event.account.groups, userinfo.groups) == true)) {
  3283. users[message.event.account._id] = message.event.account; // Part of our groups, update this user.
  3284. } else {
  3285. delete users[message.event.account._id]; // No longer part of our groups, remove this user.
  3286. }
  3287. mainUpdate(4 | 16384);
  3288. break;
  3289. }
  3290. case 'accountremove': {
  3291. // An account was removed
  3292. if (users == null) break;
  3293. delete users[message.event.userid];
  3294. mainUpdate(16384);
  3295. break;
  3296. }
  3297. case 'createusergroup':
  3298. case 'usergroupchange': {
  3299. // User group changed
  3300. if (usergroups == null) { usergroups = {}; }
  3301. var ugroup = usergroups[message.event.ugrpid];
  3302. if (ugroup == null) {
  3303. // This is a new user group for us
  3304. usergroups[message.event.ugrpid] = { _id: message.event.ugrpid, name: message.event.name, desc: message.event.desc, domain: message.event.domain, links: message.event.links };
  3305. } else {
  3306. // This is an existing user group
  3307. ugroup.name = message.event.name;
  3308. if (message.event.desc) { ugroup.desc = message.event.desc; } else { delete ugroup.desc; }
  3309. if (message.event.links) { ugroup.links = message.event.links; } else { delete ugroup.links; }
  3310. if (message.event.flags) { ugroup.flags = message.event.flags; } else { delete ugroup.flags; }
  3311. if (typeof message.event.consent == 'number') { ugroup.consent = message.event.consent; }
  3312. }
  3313. mainUpdate(4096 + 8192 + 16384);
  3314. // Group update, refresh all our device groups and nodes. TODO: Optimize this to only do this when needed.
  3315. meshserver.send({ action: 'meshes' });
  3316. meshserver.send({ action: 'nodes', skip: (devicePagingState == null) ? 0 : devicePagingState.skip });
  3317. break;
  3318. }
  3319. case 'deleteusergroup': {
  3320. // User group removed
  3321. if ((usergroups != null) && (usergroups[message.event.ugrpid] != null)) {
  3322. delete usergroups[message.event.ugrpid];
  3323. var c = 0;
  3324. for (var i in usergroups) { c++; }
  3325. if (c == 0) { usergroups = null; } // If user groups is empty, set it to null.
  3326. mainUpdate(8192 + 16384);
  3327. }
  3328. break;
  3329. }
  3330. case 'createmesh': {
  3331. // A new mesh was created
  3332. if ((meshes[message.event.meshid] == null) && ((serverinfo.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.
  3333. meshes[message.event.meshid] = message.event.mesh;
  3334. mainUpdate(4 + 128 + 8192 + 16384);
  3335. meshserver.send({ action: 'files' });
  3336. }
  3337. break;
  3338. }
  3339. case 'meshchange': {
  3340. // Update mesh information
  3341. if (meshes[message.event.meshid] == null) {
  3342. // Check if we have any access to this device group
  3343. var add = false;
  3344. if (message.event.links[userinfo._id] != null) { add = true; }
  3345. if (userinfo.links[message.event.meshid] != null) { add = true; }
  3346. for (var i in userinfo.links) { if ((i.startsWith('ugrp/')) && (message.event.links[i] != null)) { add = true; } }
  3347. // This is a new mesh for us
  3348. if (add) {
  3349. meshes[message.event.meshid] = { _id: message.event.meshid, name: message.event.name, mtype: message.event.mtype, desc: message.event.desc, links: message.event.links, amt: message.event.amt, invite: message.event.invite, expireDevs: message.event.expireDevs, relayid: message.event.relayid };
  3350. 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).
  3351. }
  3352. } else {
  3353. // This is an existing device group
  3354. if (message.event.name != null) {
  3355. meshes[message.event.meshid].name = message.event.name;
  3356. for (var i in nodes) { if (nodes[i].meshid == message.event.meshid) { nodes[i].meshnamel = message.event.name.toLowerCase(); } }
  3357. }
  3358. if (message.event.desc != null) { meshes[message.event.meshid].desc = message.event.desc; }
  3359. if (message.event.flags != null) { meshes[message.event.meshid].flags = message.event.flags; }
  3360. if (message.event.consent != null) { meshes[message.event.meshid].consent = message.event.consent; }
  3361. if (message.event.links) { meshes[message.event.meshid].links = message.event.links; }
  3362. if (message.event.amt) { meshes[message.event.meshid].amt = message.event.amt; }
  3363. if (message.event.invite != null) { meshes[message.event.meshid].invite = message.event.invite; } else { delete meshes[message.event.meshid].invite; }
  3364. if (message.event.expireDevs != null) { if (message.event.expireDevs > 0) { meshes[message.event.meshid].expireDevs = message.event.expireDevs; } else { delete meshes[message.event.meshid].expireDevs; } }
  3365. if (message.event.relayid != null) { meshes[message.event.meshid].relayid = message.event.relayid; }
  3366. // Check if we lost rights to this mesh in this change.
  3367. if (IsMeshViewable(message.event.meshid) == false) {
  3368. if ((xxcurrentView == 20) && (currentMesh == meshes[message.event.meshid])) go(2);
  3369. delete meshes[message.event.meshid];
  3370. // Delete all nodes in that mesh, except ones with direct links
  3371. var newnodes = [];
  3372. 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]); } }
  3373. nodes = newnodes;
  3374. // If we are looking at a node that is no longer visible, move back to "My Devices"
  3375. if ((xxcurrentView >= 10) && (xxcurrentView < 20) && currentNode && !IsNodeViewable(currentNode)) { setDialogMode(0); go(1); }
  3376. }
  3377. }
  3378. mainUpdate(4 + 128 + 8192 + 16384);
  3379. if (currentNode && !IsNodeViewable(currentNode)) { currentNode = null; if ((xxcurrentView >= 10) && (xxcurrentView < 20)) { go(1); } }
  3380. //meshserver.send({ action: 'files' }); // TODO: Why do we need to do this??
  3381. // If we are looking at a mesh that is now deleted, move back to "My Account"
  3382. if (xxcurrentView == 20 && currentMesh._id == message.event.meshid) { mainUpdate(4096); }
  3383. break;
  3384. }
  3385. case 'deletemesh': {
  3386. // Delete the mesh
  3387. if (meshes[message.event.meshid]) {
  3388. delete meshes[message.event.meshid];
  3389. mainUpdate(128);
  3390. meshserver.send({ action: 'files' });
  3391. }
  3392. // Delete all nodes in that mesh
  3393. var newnodes = [];
  3394. if (nodes != null) { for (var i in nodes) { if (nodes[i].meshid != message.event.meshid) { newnodes.push(nodes[i]); } } }
  3395. nodes = newnodes;
  3396. mainUpdate(4 + 8192 + 16384);
  3397. // If we are looking at a mesh that is now deleted, move back to "My Account"
  3398. if (xxcurrentView >= 20 && xxcurrentView < 30 && currentMesh._id == message.event.meshid) { setDialogMode(0); go(2); }
  3399. // If we are looking at a node in the deleted mesh, move back to "My Devices"
  3400. if (xxcurrentView >= 10 && xxcurrentView < 20 && currentNode && !IsNodeViewable(currentNode)) { setDialogMode(0); go(1); }
  3401. break;
  3402. }
  3403. case 'addnode': {
  3404. var node = message.event.node;
  3405. 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.
  3406. if (getNodeFromId(node._id) != null) break; // This node is already known.
  3407. node.namel = node.name.toLowerCase();
  3408. if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
  3409. node.meshnamel = meshes[node.meshid]?meshes[node.meshid].name.toLowerCase():'*';
  3410. node.state = 0;
  3411. if (!node.icon) node.icon = 1;
  3412. node.ident = ++nodeShortIdent;
  3413. if (nodes == null) { }
  3414. nodes.push(node);
  3415. // Web page update
  3416. mainUpdate(1 | 2 | 4 | 16);
  3417. // If we are looking at the device group for this device, update that.
  3418. if ((xxcurrentView == 20) && (currentMesh != null) && (currentMesh._id == node.meshid)) { mainUpdate(4096); }
  3419. break;
  3420. }
  3421. case 'removenode': {
  3422. var index = -1;
  3423. for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
  3424. if (index != -1) {
  3425. var node = nodes[index];
  3426. if (currentNode == node) {
  3427. if (xxcurrentView >= 10 && xxcurrentView < 20) { setDialogMode(0); go(1); }
  3428. currentNode = null;
  3429. // TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
  3430. }
  3431. nodes.splice(index, 1);
  3432. // Web page update
  3433. mainUpdate(4 | 16);
  3434. // If we are looking at the device group for this device, update that.
  3435. if ((xxcurrentView == 20) && (currentMesh != null) && (currentMesh._id == node.meshid)) { mainUpdate(4096); }
  3436. }
  3437. break;
  3438. }
  3439. case 'changenode': {
  3440. var index = -1;
  3441. for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
  3442. if (index != -1) {
  3443. var node = nodes[index];
  3444. // Change the node
  3445. node.name = message.event.node.name;
  3446. node.rname = message.event.node.rname;
  3447. node.lusers = message.event.node.lusers;
  3448. node.upnusers = message.event.node.upnusers;
  3449. node.users = message.event.node.users;
  3450. node.host = message.event.node.host;
  3451. node.desc = message.event.node.desc;
  3452. node.ip = message.event.node.ip;
  3453. node.osdesc = message.event.node.osdesc;
  3454. node.publicip = message.event.node.publicip;
  3455. node.iploc = message.event.node.iploc;
  3456. node.wifiloc = message.event.node.wifiloc;
  3457. node.gpsloc = message.event.node.gpsloc;
  3458. node.tags = message.event.node.tags;
  3459. node.ssh = message.event.node.ssh;
  3460. node.rdp = message.event.node.rdp;
  3461. node.userloc = message.event.node.userloc;
  3462. node.rdpport = message.event.node.rdpport;
  3463. node.rfbport = message.event.node.rfbport;
  3464. node.sshport = message.event.node.sshport;
  3465. node.httpport = message.event.node.httpport;
  3466. node.httpsport = message.event.node.httpsport;
  3467. node.consent = message.event.node.consent;
  3468. node.pmt = message.event.node.pmt;
  3469. if (message.event.node.links != null) { node.links = message.event.node.links; } else { delete node.links; }
  3470. if (message.event.node.agent != null) {
  3471. if (node.agent == null) node.agent = {};
  3472. if (message.event.node.agent.ver != null) { node.agent.ver = message.event.node.agent.ver; }
  3473. if (message.event.node.agent.id != null) { node.agent.id = message.event.node.agent.id; }
  3474. if (message.event.node.agent.caps != null) { node.agent.caps = message.event.node.agent.caps; }
  3475. if (message.event.node.agent.root != null) { node.agent.root = message.event.node.agent.root; }
  3476. if (message.event.node.agent.core != null) { node.agent.core = message.event.node.agent.core; } else { if (node.agent.core) { delete node.agent.core; } }
  3477. node.agent.tag = message.event.node.agent.tag;
  3478. }
  3479. if (message.event.node.intelamt != null) {
  3480. if (node.intelamt == null) node.intelamt = {};
  3481. if (message.event.node.intelamt.state != null) { node.intelamt.state = message.event.node.intelamt.state; }
  3482. if (message.event.node.intelamt.host != null) { node.intelamt.user = message.event.node.intelamt.host; }
  3483. if (message.event.node.intelamt.user != null) { node.intelamt.user = message.event.node.intelamt.user; } else { delete node.intelamt.user; }
  3484. if (message.event.node.intelamt.tls != null) { node.intelamt.tls = message.event.node.intelamt.tls; }
  3485. if (message.event.node.intelamt.ver != null) { node.intelamt.ver = message.event.node.intelamt.ver; }
  3486. if (message.event.node.intelamt.tag != null) { node.intelamt.tag = message.event.node.intelamt.tag; }
  3487. if (message.event.node.intelamt.uuid != null) { node.intelamt.uuid = message.event.node.intelamt.uuid; }
  3488. if (message.event.node.intelamt.realm != null) { node.intelamt.realm = message.event.node.intelamt.realm; }
  3489. if (message.event.node.intelamt.flags != null) { node.intelamt.flags = message.event.node.intelamt.flags; }
  3490. if (message.event.node.intelamt.warn != null) { node.intelamt.warn = message.event.node.intelamt.warn; } else { delete node.intelamt.warn; }
  3491. }
  3492. if (message.event.node.av != null) { node.av = message.event.node.av; }
  3493. if (message.event.node.wsc != null) { node.wsc = message.event.node.wsc; }
  3494. if (message.event.node.volumes != null) { node.volumes = message.event.node.volumes; }
  3495. node.namel = node.name.toLowerCase();
  3496. if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
  3497. if (message.event.node.icon) { node.icon = message.event.node.icon; }
  3498. if (message.event.node.lastbootuptime != null) { node.lastbootuptime = message.event.node.lastbootuptime; }
  3499. // Check if this device has changed device group
  3500. if (message.event.node.meshid != node.meshid) {
  3501. if ((meshes[message.event.node.meshid] == null) && ((userinfo.links == null) || (userinfo.links[node._id] == null))) {
  3502. // We don't see the new mesh, remove this device
  3503. // TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
  3504. if ((currentNode != null) && (currentNode._id == node._id)) {
  3505. if ((xxcurrentView >= 10) && (xxcurrentView < 20) && !IsNodeViewable(currentNode)) { currentNode = null; setDialogMode(0); go(1); }
  3506. }
  3507. var index = -1;
  3508. for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
  3509. nodes.splice(index, 1);
  3510. mainUpdate(4 | 16);
  3511. } else {
  3512. // We see the new mesh, move this device
  3513. node.meshid = message.event.node.meshid;
  3514. node.meshnamel = meshes[message.event.node.meshid]?meshes[message.event.node.meshid].name.toLowerCase():'*';
  3515. mainUpdate(1 | 2 | 4);
  3516. }
  3517. }
  3518. // Web page update
  3519. updateDeviceViewDevice(node);
  3520. mainUpdate(2 | 8 | 16);
  3521. refreshDevice(node._id);
  3522. // If we are looking at the device group for this device, update that.
  3523. if ((xxcurrentView == 20) && (currentMesh != null) && (currentMesh._id == node.meshid)) { mainUpdate(4096); }
  3524. if ((currentNode == node) && (xxdialogMode != null) && (xxdialogTag == '@xxmap')) { p10showNodeLocationDialog(); }
  3525. }
  3526. break;
  3527. }
  3528. case 'nodemeshchange': {
  3529. var index = -1;
  3530. for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
  3531. if (index != -1) {
  3532. var node = nodes[index];
  3533. if ((meshes[message.event.newMeshId] == null) && ((userinfo.links == null) || (userinfo.links[node._id] == null))) {
  3534. // We don't see the new mesh, remove this device
  3535. // TODO: Correctly disconnect from this node (Desktop/Terminal/Files...)
  3536. if ((currentNode != null) && (currentNode._id == node._id)) {
  3537. if ((xxcurrentView >= 10) && (xxcurrentView < 20) && !IsNodeViewable(currentNode)) { currentNode = null; setDialogMode(0); go(1); }
  3538. }
  3539. nodes.splice(index, 1);
  3540. mainUpdate(4 | 16);
  3541. } else {
  3542. // We see the new mesh, move this device
  3543. node.meshid = message.event.newMeshId;
  3544. node.meshnamel = meshes[message.event.newMeshId]?meshes[message.event.newMeshId].name.toLowerCase():'*';
  3545. mainUpdate(1 | 2 | 4);
  3546. }
  3547. refreshDevice(message.event.nodeid);
  3548. } else {
  3549. // This is a new device, add it.
  3550. var node = message.event.node;
  3551. 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.
  3552. node.namel = node.name.toLowerCase();
  3553. if (node.rname) { node.rnamel = node.rname.toLowerCase(); } else { node.rnamel = node.namel; }
  3554. node.meshnamel = meshes[node.meshid]?meshes[node.meshid].name.toLowerCase():'*';
  3555. node.state = 0;
  3556. if (!node.icon) node.icon = 1;
  3557. node.ident = ++nodeShortIdent;
  3558. if (nodes == null) { }
  3559. nodes.push(node);
  3560. // Web page update
  3561. mainUpdate(1 | 2 | 4 | 16);
  3562. }
  3563. break;
  3564. }
  3565. case 'nodeconnect': {
  3566. // Indicated a node has changed connectivity state
  3567. var index = -1;
  3568. for (var i in nodes) { if (nodes[i]._id == message.event.nodeid) { index = i; break; } }
  3569. if (index != -1) {
  3570. var node = nodes[index];
  3571. // Event the connection change if needed
  3572. var n = getstore('notifications', 0); // Account notification settings
  3573. // Per-group notification settings
  3574. if (message.event.meshid && userinfo.links && userinfo.links[message.event.meshid] && userinfo.links[message.event.meshid].notify) {
  3575. n |= userinfo.links[message.event.meshid].notify;
  3576. }
  3577. // Per-user notification settings
  3578. if (userinfo.notify != null) {
  3579. if (userinfo.notify[message.event.meshid] != null) { n |= userinfo.notify[message.event.meshid]; }
  3580. if (userinfo.notify[message.event.nodeid] != null) { n |= userinfo.notify[message.event.nodeid]; }
  3581. }
  3582. // Show the notification
  3583. if (n & 2) {
  3584. var agentPrivilages = "Agent connected";
  3585. if ((node.agent != null) && (node.agent.root === false)) { agentPrivilages = "Agent connected with limited privilages"; }
  3586. var abc = { title: node.name, icon: node.icon, nodeid: node._id, id: message.event.id };
  3587. if (((node.conn & 1) == 0) && ((message.event.conn & 1) != 0)) { abc.text = agentPrivilages; addNotification(abc); }
  3588. if (((node.conn & 2) == 0) && ((message.event.conn & 2) != 0)) { abc.text = "Intel AMT CIRA connected"; addNotification(abc); }
  3589. if (((node.conn & 4) == 0) && ((message.event.conn & 4) != 0)) { abc.text = "Intel AMT connected"; addNotification(abc); }
  3590. if (((node.conn & 16) == 0) && ((message.event.conn & 16) != 0)) { abc.text = "MQTT connected"; addNotification(abc); }
  3591. }
  3592. if (n & 4) {
  3593. var abc = { title: node.name, icon: node.icon, nodeid: node._id, id: message.event.id };
  3594. if (((node.conn & 1) != 0) && ((message.event.conn & 1) == 0)) { abc.text = "Agent disconnected"; addNotification(abc); }
  3595. if (((node.conn & 2) != 0) && ((message.event.conn & 2) == 0)) { abc.text = "Intel AMT CIRA disconnected"; addNotification(abc); }
  3596. if (((node.conn & 4) != 0) && ((message.event.conn & 4) == 0)) { abc.text = "Intel AMT disconnected"; addNotification(abc); }
  3597. if (((node.conn & 16) != 0) && ((message.event.conn & 16) == 0)) { abc.text = "MQTT disconnected"; addNotification(abc); }
  3598. }
  3599. // Change the node connection state
  3600. node.conn = message.event.conn;
  3601. node.pwr = message.event.pwr;
  3602. node.lastconnect = Date.now();
  3603. // Clear sesssion and battery information if needed
  3604. if ((node.conn & 1) == 0) { delete node.sessions; }
  3605. // Web page update
  3606. var filter = Q('DevFilterSelect').value;
  3607. if ((filter == 1) || (filter == 5)) {
  3608. // We are looking at online or offline devices. This may change that state.
  3609. mainUpdate(1 | 4 | 16);
  3610. } else {
  3611. // We are looking at a different filter, just update that specific device.
  3612. updateDeviceViewDevice(node);
  3613. mainUpdate(1 | 16);
  3614. }
  3615. refreshDevice(node._id);
  3616. // If we are looking at the device group for this device, update that.
  3617. if ((xxcurrentView == 20) && (currentMesh != null) && (currentMesh._id == node.meshid)) { mainUpdate(4096); }
  3618. }
  3619. break;
  3620. }
  3621. case 'wssessioncount': {
  3622. // Update the active web socket session count for a user
  3623. if (wssessions != null) {
  3624. if (message.event.count == 0 && wssessions[message.event.userid]) {
  3625. delete wssessions[message.event.userid];
  3626. } else {
  3627. wssessions[message.event.userid] = message.event.count;
  3628. }
  3629. mainUpdate(16384);
  3630. }
  3631. break;
  3632. }
  3633. case 'login': {
  3634. // Update the last login time
  3635. if (users != null && users[message.event.userid]) {
  3636. users[message.event.userid].login = Math.floor(new Date(message.event.time).getTime() / 1000);
  3637. }
  3638. break;
  3639. }
  3640. case 'scanamtdevice': {
  3641. // Populate the Intel AMT scan dialog box with the result of the RMCP scan
  3642. if ((xxdialogMode == null) || (!Q('dp1range')) || (xxdialogTag != ('AMTSCAN:' + message.event.range))) return;
  3643. var x = '';
  3644. if (message.event.results == null) {
  3645. // The scan could not occur because of an error. Likely the user range was invalid.
  3646. x = '<div style=width:100%;text-align:center;margin-top:12px>' + "Unable to scan this address range." + '</div><div style=width:100%;text-align:center;margin-top:12px;color:gray;line-height:1.5>' + "Sample IP range values" + '<br />192.168.0.100<br />192.168.1.0/24<br />192.167.0.1-192.168.0.100</div>';
  3647. } else {
  3648. // Go thru all the results and populate the dialog box
  3649. amtScanResults = message.event.results;
  3650. for (var i in message.event.results) {
  3651. var r = message.event.results[i], shortname = r.hostname;
  3652. if (r.hosttype == 'host') { shortname = capitalizeFirstLetter(shortname.split('.')[0]); }
  3653. if (shortname.length > 20) { shortname = shortname.substring(0, 20) + '...'; }
  3654. var str = '<b title="' + EscapeHtml(r.hostname) + '">' + EscapeHtml(shortname) + '</b> - v' + r.ver;
  3655. if (r.state == 2) { if (r.tls == 1) { str += " with TLS."; } else { str += " without TLS."; } } else { str += ' not activated.'; }
  3656. x += '<div style=width:100%;margin-bottom:2px;background-color:lightgray><div style=padding:4px><div style=display:inline-block;margin-right:5px><input class=DevScanCheckbox name=dp1checkbox tag="' + EscapeHtml(i) + '" type=checkbox onclick=addAmtScanToMeshCheckbox() /></div><div class=j1 style=display:inline-block></div><div style=display:inline-block;margin-left:5px;overflow-x:auto;white-space:nowrap>' + str + '</div></div></div>';
  3657. }
  3658. // If no results where found, display a nice message
  3659. if (x == '') { x = '<div style=width:100%;text-align:center;margin-top:12px>' + "Scan returned no results." + '</div><div style=width:100%;text-align:center;margin-top:12px;color:gray;line-height:1.5>' + "Sample IP range values" + '<br />192.168.0.100<br />192.168.1.0/24<br />192.167.0.1-192.168.0.100</div>'; }
  3660. }
  3661. // Set the html in the dialog box and re-enable the scan button
  3662. QH('dp1results', x);
  3663. QE('dp1range', true);
  3664. QE('dp1rangebutton', true);
  3665. QE('d2scanSelectButton', true);
  3666. break;
  3667. }
  3668. case 'notify': {
  3669. var n = { text: message.event.value, title: message.event.title, icon: message.event.icon, titleid: message.titleid, msgid: message.msgid, args: message.args };
  3670. if (message.id != null) { n.id = message.id; }
  3671. if (message.event.tag != null) { n.tag = message.event.tag; }
  3672. if (typeof message.maxtime == 'number') { n.maxtime = message.maxtime; }
  3673. addNotification(n);
  3674. break;
  3675. }
  3676. case 'traceinfo': {
  3677. if (typeof message.event.traceSources == 'object') {
  3678. if ((message.event.traceSources != null) && (message.event.traceSources.length > 0)) {
  3679. serverTraceSources = message.event.traceSources;
  3680. QH('p41traceStatus', EscapeHtml(message.event.traceSources.join(', ')));
  3681. } else {
  3682. serverTraceSources = [];
  3683. QH('p41traceStatus', "None");
  3684. }
  3685. }
  3686. break;
  3687. }
  3688. case 'sysinfohash': {
  3689. // If the sysinfo document has changed and we are looking at it, request an update.
  3690. if ((currentNode != null) && (message.event.nodeid == powerTimelineReq)) {
  3691. meshserver.send({ action: 'getsysinfo', nodeid: message.event.nodeid });
  3692. }
  3693. break;
  3694. }
  3695. case 'ifchange': {
  3696. // Network interface changed for a device, if we are currently viewing this device, ask for an update.
  3697. if ((currentNode != null) && (currentNode._id == message.event.nodeid)) { meshserver.send({ action: 'getnetworkinfo', nodeid: currentNode._id }); }
  3698. break;
  3699. }
  3700. case 'devicesessions': {
  3701. // List of sessions for a given device
  3702. var node = getNodeFromId(message.event.nodeid);
  3703. if (node == null) break; // Unknown node
  3704. node.sessions = message.event.sessions;
  3705. if (node.sessions != null) {
  3706. for (var i in node.sessions) { if (Object.keys(node.sessions[i]).length == 0) { delete node.sessions[i]; } }
  3707. if (Object.keys(node.sessions).length == 0) { delete node.sessions; }
  3708. }
  3709. updateDeviceViewDevice(node);
  3710. if ((currentNode != null) && (currentNode._id == message.event.nodeid)) { gotoDevice(currentNode._id, xxcurrentView, true); }
  3711. // If we are looking at the sessions dialog box for this device now, update it
  3712. if (xxdialogTag == ('SESSIONS-' + message.event.nodeid)) { showDeviceSessions(message.event.nodeid, true); }
  3713. if (xxdialogTag == ('MESSAGES-' + message.event.nodeid)) { showDeviceMessages(message.event.nodeid, true); }
  3714. if (xxdialogTag == ('HELPREQ-' + message.event.nodeid)) { showDeviceHelpRequests(message.event.nodeid, true); }
  3715. // If we are filtering on sessions or help, update the visible devices
  3716. if ((Q('DevFilterSelect').value == 2) || (Q('DevFilterSelect').value == 6)) { mainUpdate(1); }
  3717. break;
  3718. }
  3719. case 'loginTokenChanged': { // Login tokens have changed
  3720. if (message.event.userid != userinfo._id) return;
  3721. loginTokens = message.event.loginTokens;
  3722. mainUpdate(65536);
  3723. break;
  3724. }
  3725. case 'loginTokenAdded': { // A login token was added
  3726. if (message.event.userid != userinfo._id) return;
  3727. if (loginTokens == null) { loginTokens = []; }
  3728. loginTokens.push(message.event.newToken);
  3729. mainUpdate(65536);
  3730. break;
  3731. }
  3732. case 'stopped': { // Server is stopping.
  3733. // Disconnect
  3734. //console.log(message.msg);
  3735. break;
  3736. }
  3737. case 'updatePluginList': {
  3738. installedPluginList = message.event.list;
  3739. updatePluginList();
  3740. break;
  3741. }
  3742. case 'pluginStateChange': {
  3743. if (pluginHandler == null) break;
  3744. pluginHandler.refreshPluginHandler();
  3745. break;
  3746. }
  3747. case 'plugin': {
  3748. if (pluginHandler == null) break;
  3749. try { pluginHandler[message.event.plugin][message.event.pluginaction](message); } catch (e) { console.log("PluginHandler could not event message: ", e); }
  3750. break;
  3751. }
  3752. default:
  3753. //console.log('Unknown message.event.action', message.event.action);
  3754. break;
  3755. }
  3756. break;
  3757. }
  3758. case 'createInviteLink': { // Agent installation invitation link
  3759. if (xxdialogTag != message.meshid) break;
  3760. var servername = serverinfo.name;
  3761. 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.
  3762. var domainUrlNoSlash = domainUrl.substring(0, domainUrl.length - 1);
  3763. var url;
  3764. if (serverinfo.https == true) {
  3765. var portStr = (serverinfo.port == 443) ? '' : (':' + serverinfo.port);
  3766. url = 'https://' + servername + portStr + domainUrl + 'agentinvite?c=' + message.cookie + (urlargs.key?('&key=' + urlargs.key):'');
  3767. } else {
  3768. var portStr = (serverinfo.port == 80) ? '' : (':' + serverinfo.port);
  3769. url = 'http://' + servername + portStr + domainUrl + 'agentinvite?c=' + message.cookie + (urlargs.key?('&key=' + urlargs.key):'');
  3770. }
  3771. Q('agentInvitationLink').href = url;
  3772. var t = format((message.expire == 1)?"1 hour":"{0} hours", message.expire);
  3773. if (message.expire == 24) { t = "1 day"; }
  3774. if (message.expire == 168) { t = "1 week"; }
  3775. if (message.expire == 5040) { t = "1 month"; }
  3776. if (message.expire == 0) { t = "Unlimited"; }
  3777. QH('agentInvitationLink', format("Invitation Link ({0})", t));
  3778. QV('agentInvitationLinkDiv', true);
  3779. break;
  3780. }
  3781. case 'createDeviceShareLink': { // Guest sharing link
  3782. if (xxdialogTag) break;
  3783. var node = getNodeFromId(message.nodeid), x = '';
  3784. if (node == null) break;
  3785. x += addHtmlValue("Device", node.name);
  3786. x += addHtmlValue("Guest Name", message.guestname);
  3787. x += addHtmlValue("User Input", message.viewOnly ? "Not allowed, view only" : "Allowed");
  3788. if (message.start && message.expire) {
  3789. if (message.recurring) {
  3790. x += addHtmlValue("Start Time", printDateTime(new Date(message.start)));
  3791. if (message.expire < 2) { x += addHtmlValue("Duration", format("{0} minute", message.expire) ); }
  3792. else { x += addHtmlValue("Duration", format("{0} minutes", message.expire) ); }
  3793. } else {
  3794. x += addHtmlValue("Start Time", printDateTime(new Date(message.start)));
  3795. x += addHtmlValue("Expire Time", printDateTime(new Date(message.expire)));
  3796. }
  3797. }
  3798. if (message.recurring == 1) { x += addHtmlValue("Recurring", "Daily"); }
  3799. if (message.recurring == 2) { x += addHtmlValue("Recurring", "Weekly"); }
  3800. var y = [];
  3801. if (message.consent & 0x0007) { y.push("Notify"); }
  3802. if (message.consent & 0x0038) { y.push("Prompt"); }
  3803. if (message.consent & 0x0040) { y.push("Privacy bar"); }
  3804. if (y.length == 0) { y.push("None"); }
  3805. x += addHtmlValue("User Consent", y.join(', '));
  3806. var type = ''; if (message.p <= 7) { type = ['', "Remote Terminal Link", "Remote Desktop Link", "Remote Desktop + Terminal Link", "Remote Files Link", "Remote Terminal + Files Link", "Remote Desktop + Files Link", "Remote Desktop + Terminal + Files Link"][message.p]; } else if (message.p == 8) { type = format("HTTP/{0} link", message.port); } else if (message.p == 16) { type = format("HTTPS/{0}", message.port); }
  3807. x += '<div id=agentInvitationLinkDiv style="text-align:center;font-size:large;margin:16px"><a href="' + message.url + '" id=agentInvitationLink rel="noreferrer noopener" target="_blank" style=cursor:pointer>' + type + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy link to clipboard" + '" style=cursor:pointer onclick=d2CopyInviteToClip()></div></div>';
  3808. setDialogMode(2, "Share Device", 1, null, x);
  3809. break;
  3810. }
  3811. case 'getmqttlogin': {
  3812. if ((currentNode == null) || (currentNode._id != message.nodeid) || (xxdialogMode != null)) return;
  3813. var x = "These settings can be used to connect MQTT for this device." + '<br /><br />';
  3814. delete message.action;
  3815. delete message.nodeid;
  3816. x += '<textarea readonly=readonly style=width:100%;resize:none;height:100px;overflow:auto;font-size:12px readonly>' + JSON.stringify(message) + '</textarea>';
  3817. /*
  3818. x += addHtmlValue('Username', '<input style=width:230px readonly value="' + message.user + '" />');
  3819. x += addHtmlValue('Password', '<input style=width:230px readonly value="' + message.pass + '" />');
  3820. x += addHtmlValue('WS URL', '<input style=width:230px readonly value="' + message.wsUrl + '" />');
  3821. if (message.mpsUrl && message.mpsCertHash) {
  3822. x += addHtmlValue('MPS URL', '<input style=width:230px readonly value="' + message.mpsUrl + '" />');
  3823. x += addHtmlValue('MPS Cert Hash', '<input style=width:230px readonly value="' + message.mpsCertHash + '" />');
  3824. }
  3825. */
  3826. setDialogMode(2, "MQTT Credentials", 1, null, x);
  3827. break;
  3828. }
  3829. case 'stopped': { // Server is stopping.
  3830. // Disconnect
  3831. autoReconnect = false;
  3832. QH('p0span', message.msg);
  3833. break;
  3834. }
  3835. case 'updatePluginList': {
  3836. installedPluginList = message.list;
  3837. updatePluginList();
  3838. break;
  3839. }
  3840. case 'pluginVersionsAvailable': {
  3841. if (pluginHandler == null) break;
  3842. updatePluginList(message.list);
  3843. break;
  3844. }
  3845. case 'downgradePluginVersions': {
  3846. var vSelect = '<select id="lastPluginVersion">';
  3847. message.info.versionList.forEach(function(v) { vSelect += '<option value="' + v.zipball_url + '">' + v.name + '</option>'; });
  3848. vSelect += '</select>';
  3849. setDialogMode(2, "Plugin Action", 3, pluginActionEx, format('Select the version to downgrade the plugin: {0}', message.info.name) + '<hr />' + vSelect + '<hr />' + "Please be aware that downgrading is not recommended. Please only do so in the event that a recent upgrade has broken something." + + '<input id="lastPluginAct" type="hidden" value="downgrade" /><input id="lastPluginId" type="hidden" value="' + message.info.id + '" />');
  3850. break;
  3851. }
  3852. case 'serverBackup': {
  3853. if (message.service == 'googleDrive') {
  3854. QV('p2ServerActionsGoogleBackup', message.state > 0);
  3855. QV('p2ServerActionsGoogleBackupCheck', message.state > 2);
  3856. miscState['googleDrive'] = { state: message.state }
  3857. if (message.state == 2) {
  3858. var x = '<img style=float:right src="images/googledrive-48.png" /><div>' + "Nagivate to the URL below, grant access and copy the token code back." + '</div><br />';
  3859. x += addHtmlValue("Activation", '<a href=\"' + message.url + '\" rel="noreferrer noopener" target="_blank">Browse to this URL</a>');
  3860. x += addHtmlValue("Code", '<input id=gdcode onkeyup=server_setupGoogleDriveBackupCheck3() style=width:240px></input>');
  3861. setDialogMode(2, "Google Drive Backup", 3, server_setupGoogleDriveBackupEx, x, 'gd2');
  3862. Q('gdcode').focus();
  3863. QE('idx_dlgOkButton', false);
  3864. }
  3865. }
  3866. break;
  3867. }
  3868. case 'pluginError': {
  3869. setDialogMode(2, "Plugin Error", 1, null, message.msg);
  3870. break;
  3871. }
  3872. case 'plugin': {
  3873. if ((pluginHandler == null) || (typeof message.plugin != 'string')) break;
  3874. try { pluginHandler[message.plugin][message.method](server, message); } catch (e) { console.log('Error loading plugin handler ('+ e + ')'); }
  3875. break;
  3876. }
  3877. case 'amtsetupbin': {
  3878. saveAs(new Blob([ Uint8Array.from(atob(message.file), function (c) { return c.charCodeAt(0) }) ], { type: 'application/octet-stream' }), 'setup.bin');
  3879. break;
  3880. }
  3881. case 'previousLogins':{
  3882. var tag = 'previousLogins';
  3883. if (message.userid != null) { tag += ':' + message.userid; }
  3884. if ((xxdialogMode == 2) && (xxdialogTag == tag)) {
  3885. var x = '', c = 'BBB', xx = '';
  3886. if (message.events.length == 0) {
  3887. x += 'No previous login.';
  3888. } else {
  3889. x += '<div style=max-height:260px;overflow-y:scroll;overflow-x:hidden><table>';
  3890. for (var i in message.events) {
  3891. var m = message.events[i].m;
  3892. if (m == 107) { m = "Valid login"; c = 'BBD1BB'; xx = ''; if (message.events[i].tn != null) { m = format("Token: {0}", message.events[i].tn); c = '88D188' } }
  3893. else if (m == 108) { m = "Invalid 2FA"; c ='DD9DC3'; xx = 'x'; }
  3894. else if (m == 109) { m = "Locked account"; c ='E1BBBB'; xx = 'x'; }
  3895. else if (m == 110) { m = "Invalid password"; c = 'E1BBBB'; xx = 'x'; }
  3896. x += '<tr><td><img src=images/user-32' + xx + '.png height=32 width=32 style=float:left srcset="images/user-64' + xx + '.png 2x"><td><div style=width:300px;background-color:#' + c + ';border-radius:6px;margin-bottom:4px;padding:4px><div>' + printDateTime(new Date(message.events[i].t)) + ', <b>' + EscapeHtml(m) + '</b></div><div style=font-size:x-small>' + EscapeHtml(message.events[i].a.join(', ')) + '</div></div></tr>';
  3897. }
  3898. x += '</table></div>';
  3899. }
  3900. setDialogMode(2, "Previous Logins", 1, null, x);
  3901. }
  3902. break;
  3903. }
  3904. case 'getDeviceDetails': {
  3905. saveAs(new Blob([message.data], { type: 'application/octet-stream' }), "devicelist" + '.' + message.type);
  3906. break;
  3907. }
  3908. case 'createLoginToken': { // A new login token was created
  3909. if (xxdialogMode) return;
  3910. var x = "Take note of this username and password, the password cannot be shown again." + '<br /><br />';
  3911. x += addHtmlValue("Name", EscapeHtml(message.name))
  3912. if (message.expire != 0) { x += addHtmlValue("Expire", EscapeHtml(printDateTime(new Date(message.expire)))); }
  3913. x += addHtmlValue("Username", '<img src="images/link4.png" title="' + "Copy link to clipboard" + '" style="margin:9px;cursor:pointer;float:right" onclick=copyTextToClip2("' + encodeURIComponentEx(message.tokenUser) + '") width=10 height=10><div class=selecttext style=width:230px;padding:5px;background-color:orange;border-radius:5px;font-size:15px>' + EscapeHtml(message.tokenUser) + '</div>');
  3914. x += addHtmlValue("Password", '<img src="images/link4.png" title="' + "Copy link to clipboard" + '" style="margin:9px;cursor:pointer;float:right" onclick=copyTextToClip2("' + encodeURIComponentEx(message.tokenPass) + '") width=10 height=10><div class=selecttext style=width:230px;padding:5px;background-color:orange;border-radius:5px;font-size:15px>' + EscapeHtml(message.tokenPass) + '</div>');
  3915. setDialogMode(2, "Create Login Token", 1, null, x);
  3916. break;
  3917. }
  3918. case 'loginTokens': { // Reveiced the list of login tokens
  3919. loginTokens = message.loginTokens;
  3920. mainUpdate(65536);
  3921. break;
  3922. }
  3923. case 'report': {
  3924. renderReport(message.data);
  3925. break;
  3926. }
  3927. default:
  3928. //console.log('Unknown message.action', message.action);
  3929. break;
  3930. }
  3931. }
  3932. // Go to the correct starting view page
  3933. function gotoStartViewPage() {
  3934. var xviewmode = parseInt('{{viewmode}}');
  3935. if (xxcurrentView != -1) return;
  3936. if ('{{currentNode}}'.toLowerCase() != '') { // The .toLowerCase here is the minifier will not optimize this out.
  3937. if (getNodeFromId('{{currentNode}}') == null) return; // This node is not loaded yet
  3938. gotoDevice('{{currentNode}}', xviewmode);
  3939. } else if (args.gotonode != null) {
  3940. 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
  3941. if (getNodeFromId('node/' + domain + '/' + args.gotonode) == null) return; // This node is not loaded yet
  3942. gotoDevice('node/' + domain + '/' + args.gotonode, xviewmode);
  3943. goBackStack.push(1);
  3944. } else if (args.gotodevicename != null) {
  3945. var foundNode = null;
  3946. if (nodes != null) { for (var i in nodes) { if (nodes[i].name == args.gotodevicename) { foundNode = nodes[i]._id; } } }
  3947. if (foundNode) { gotoDevice(foundNode, xviewmode); goBackStack.push(1); }
  3948. } else if (args.gotodevicername != null) {
  3949. var foundNode = null;
  3950. if (nodes != null) { for (var i in nodes) { if (nodes[i].rname == args.gotodevicername) { foundNode = nodes[i]._id; } } }
  3951. if (foundNode) { gotoDevice(foundNode, xviewmode); goBackStack.push(1); }
  3952. } else if (args.gotodeviceip != null) {
  3953. var foundNode = null;
  3954. if (nodes != null) { for (var i in nodes) { if (nodes[i].ip == args.gotodeviceip) { foundNode = nodes[i]._id; } } }
  3955. if (foundNode) { gotoDevice(foundNode, xviewmode); goBackStack.push(1); }
  3956. } else if (args.gotomesh != null) {
  3957. if (meshes['mesh/' + domain + '/' + args.gotomesh] == null) return; // This device group is not loaded yet
  3958. gotoMesh('mesh/' + domain + '/' + args.gotomesh);
  3959. go(xviewmode);
  3960. goBackStack.push(2);
  3961. } else if (args.gotouser != null) {
  3962. var xuserid = args.gotouser;
  3963. if (args.gotouser.indexOf('/') < 0) { xuserid = 'user/' + domain + '/' + args.gotouser; }
  3964. if ((users == null) || (users[xuserid] == null)) return; // This user is not loaded yet
  3965. gotoUser(encodeURIComponentEx(xuserid));
  3966. go(xviewmode);
  3967. goBackStack.push(4);
  3968. } else if (args.gotougrp != null) {
  3969. var xusergrpid = args.gotougrp;
  3970. if (args.gotougrp.indexOf('/') < 0) { xusergrpid = 'ugrp/' + domain + '/' + args.gotougrp; }
  3971. if ((usergroups == null) || usergroups[xusergrpid] == null) return; // This user group is not loaded yet
  3972. gotoUserGroup(xusergrpid);
  3973. go(xviewmode);
  3974. goBackStack.push(50);
  3975. } else if (!isNaN(xviewmode)) {
  3976. go(xviewmode);
  3977. } else {
  3978. setDialogMode(0);
  3979. go(1);
  3980. }
  3981. delete args.gotonode;
  3982. delete args.gotomesh;
  3983. delete args.gotouser;
  3984. delete args.gotougrp;
  3985. }
  3986. //
  3987. // MY DEVICES
  3988. //
  3989. function onRealNameCheckBox() {
  3990. showRealNames = Q('RealNameCheckBox').checked;
  3991. putstore('showRealNames', showRealNames ? 1 : 0);
  3992. mainUpdate(7);
  3993. }
  3994. function onOnlineCheckBox(e) {
  3995. putstore('devFilterSelect', Q('DevFilterSelect').value);
  3996. onDeviceSearchChanged(e);
  3997. }
  3998. function updateDevicePageState() {
  3999. if ((devicePagingState == null) || (devicePagingState.total <= devicePagingState.limit)) {
  4000. QV('devViewPageState', false);
  4001. QV('devViewPageButton1', false);
  4002. QV('devViewPageButton2', false);
  4003. QV('devViewPageButton3', false);
  4004. QV('devViewPageButton4', false);
  4005. } else {
  4006. var currentPage = Math.floor((devicePagingState.skip + devicePagingState.limit) / devicePagingState.limit);
  4007. var maxPage = Math.ceil(devicePagingState.total / devicePagingState.limit);
  4008. QV('devViewPageState', true);
  4009. QV('devViewPageButton1', true);
  4010. QV('devViewPageButton2', true);
  4011. QV('devViewPageButton3', true);
  4012. QV('devViewPageButton4', true);
  4013. QH('devViewPageState', currentPage + '/' + maxPage);
  4014. }
  4015. }
  4016. function onDeviceViewPageChange(i) {
  4017. if (devicePagingState == null) return;
  4018. var currentPage = (Math.floor((devicePagingState.skip + devicePagingState.limit) / devicePagingState.limit));
  4019. var maxPage = Math.ceil(devicePagingState.total / devicePagingState.limit);
  4020. switch (i) {
  4021. case 1: { if (currentPage > 1) meshserver.send({ action: 'nodes', skip: 0 }); break; } // Goto first page
  4022. case 2: { if (currentPage > 1) meshserver.send({ action: 'nodes', skip: (currentPage - 2) * devicePagingState.limit }); break; } // Goto previous page
  4023. case 3: { if (currentPage < maxPage) meshserver.send({ action: 'nodes', skip: currentPage * devicePagingState.limit }); break; } // Goto next page
  4024. case 4: { if (currentPage < maxPage) meshserver.send({ action: 'nodes', skip: (maxPage - 1) * devicePagingState.limit }); break; } // Goto last page
  4025. }
  4026. }
  4027. function onDeviceViewChange(i) {
  4028. if (i != null) { Q('viewselect').value = i; }
  4029. for (var j = 1; j < 6; j++) { Q('devViewButton' + j).classList.remove('viewSelectorSel'); }
  4030. Q('devViewButton' + Q('viewselect').value).classList.add('viewSelectorSel');
  4031. putstore('deviceView', Q('viewselect').value);
  4032. putstore('viewsize', Q('sizeselect').value);
  4033. mainUpdate(4);
  4034. setTimeout(function () { mainUpdate(512); }, 200);
  4035. }
  4036. function onDeviceViewSettings() {
  4037. if (xxdialogMode) return;
  4038. // Use defaults if needed
  4039. if (deviceViewSettings == null) { deviceViewSettings = {}; }
  4040. if (!Array.isArray(deviceViewSettings.devsCols)) { deviceViewSettings.devsCols = ['user','ip','conn']; }
  4041. // Display the dialog box
  4042. var x = '';
  4043. x += '<label><input id=d2c8 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('agtype') >= 0)?' checked':'') + '>' + "Agent Type" + '</label><br />';
  4044. x += '<label><input id=d2c9 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('agver') >= 0)?' checked':'') + '>' + "Agent Version" + '</label><br />';
  4045. x += '<label><input id=d2c6 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('desc') >= 0)?' checked':'') + '>' + "Device Description" + '</label><br />';
  4046. x += '<label><input id=d2c5 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('os') >= 0)?' checked':'') + '>' + "Operating System" + '</label><br />';
  4047. x += '<label><input id=d2c10 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('tags') >= 0)?' checked':'') + '>' + "Tags" + '</label><br />';
  4048. x += '<label><input id=d2c11 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('windowsav') >= 0)?' checked':'') + '>' + "Windows AV" + '</label><br />';
  4049. x += '<label><input id=d2c12 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('windowsupdate') >= 0)?' checked':'') + '>' + "Windows Update" + '</label><br />';
  4050. x += '<label><input id=d2c13 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('windowsfirewall') >= 0)?' checked':'') + '>' + "Windows Firewall" + '</label><br />';
  4051. x += '<label><input id=d2c14 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('lastbootuptime') >= 0)?' checked':'') + '>' + "Last Boot Up Time" + '</label><br />';
  4052. x += '<label><input id=d2c1 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('links') >= 0)?' checked':'') + '>' + "MeshCentral Router Links" + '</label><br />';
  4053. x += '<label><input id=d2c2 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('user') >= 0)?' checked':'') + '>' + "Logged in users" + '</label><br />';
  4054. x += '<label><input id=d2c3 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('ip') >= 0)?' checked':'') + '>' + "Agent IP address" + '</label><br />';
  4055. x += '<label><input id=d2c4 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('conn') >= 0)?' checked':'') + '>' + "Server Connectivity" + '</label><br />';
  4056. x += '<label><input id=d2c15 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('amthost') >= 0)?' checked':'') + '>' + "Intel&reg; AMT hostname" + '</label><br />';
  4057. x += '<label><input id=d2c17 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('amtstate') >= 0)?' checked':'') + '>' + "Intel&reg; AMT state" + '</label><br />';
  4058. x += '<label><input id=d2c7 type=checkbox' + ((deviceViewSettings.devsCols.indexOf('lastseen') >= 0)?' checked':'') + '>' + "Last Seen" + '</label><br />';
  4059. setDialogMode(2, "Device View Columns", 3, onDeviceViewSettingsEx, x);
  4060. }
  4061. function onDeviceViewSettingsEx() {
  4062. var cols = [];
  4063. if (Q('d2c1').checked) { cols.push('links'); }
  4064. if (Q('d2c2').checked) { cols.push('user'); }
  4065. if (Q('d2c3').checked) { cols.push('ip'); }
  4066. if (Q('d2c4').checked) { cols.push('conn'); }
  4067. if (Q('d2c5').checked) { cols.push('os'); }
  4068. if (Q('d2c6').checked) { cols.push('desc'); }
  4069. if (Q('d2c7').checked) { cols.push('lastseen'); }
  4070. if (Q('d2c8').checked) { cols.push('agtype'); }
  4071. if (Q('d2c9').checked) { cols.push('agver'); }
  4072. if (Q('d2c10').checked) { cols.push('tags'); }
  4073. if (Q('d2c11').checked) { cols.push('windowsav'); }
  4074. if (Q('d2c12').checked) { cols.push('windowsupdate'); }
  4075. if (Q('d2c13').checked) { cols.push('windowsfirewall'); }
  4076. if (Q('d2c14').checked) { cols.push('lastbootuptime'); }
  4077. if (Q('d2c15').checked) { cols.push('amthost'); }
  4078. if (Q('d2c17').checked) { cols.push('amtstate'); }
  4079. deviceViewSettings.devsCols = cols;
  4080. putstore('_deviceViewSettings', JSON.stringify(deviceViewSettings));
  4081. mainUpdate(4);
  4082. }
  4083. function ondockeypress(e) {
  4084. setSessionActivity();
  4085. if (!xxdialogMode && (xxcurrentView == 11) && desktop && Q('DeskControl').checked) {
  4086. // Check what keys we are allows to send
  4087. if (currentNode != null) {
  4088. var meshrights = GetNodeRights(currentNode);
  4089. var inputAllowed = ((features2 & 0x2000) == 0) && ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
  4090. if (inputAllowed == false) return false;
  4091. var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
  4092. if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
  4093. }
  4094. return desktop.m.handleKeys(e);
  4095. }
  4096. if (!xxdialogMode && (xxcurrentView == 12) && terminal && (terminal.State == 3) && (xterm == null)) { return terminal.m.TermHandleKeys(e); }
  4097. if (!xxdialogMode && ((xxcurrentView == 15) || (xxcurrentView == 115))) return agentConsoleHandleKeys(e);
  4098. if (!xxdialogMode && xxcurrentView == 4) {
  4099. if ((e.ctrlKey == true) || (e.altKey == true) || (e.metaKey == true)) return;
  4100. var processed = 0;
  4101. if (e.key) {
  4102. if ((e.key.length === 1) && (userSearchFocus == 0)) { Q('UserSearchInput').value = ((Q('UserSearchInput').value + e.key)); processed = 1; }
  4103. if ((e.keyCode == 8) && (userSearchFocus == 0)) { var x = Q('UserSearchInput').value; Q('UserSearchInput').value = x.substring(0, x.length - 1); processed = 1; }
  4104. if (e.keyCode == 27) { Q('UserSearchInput').value = ''; processed = 1; }
  4105. } else {
  4106. if (e.charCode != 0 && userSearchFocus == 0) { Q('UserSearchInput').value = ((Q('UserSearchInput').value + String.fromCharCode(e.charCode))); processed = 1; }
  4107. }
  4108. if (processed > 0) { if (processed == 1) { onUserSearchInputChanged(); } return haltEvent(e); }
  4109. }
  4110. if (xxdialogMode || xxcurrentView != 1) return;
  4111. if (e.ctrlKey == true && e.charCode == 96) {
  4112. showRealNames = !showRealNames;
  4113. Q('RealNameCheckBox').value = showRealNames;
  4114. putstore('showRealNames', showRealNames ? 1 : 0);
  4115. mainUpdate(6)
  4116. return;
  4117. }
  4118. if (e.ctrlKey == true || e.altKey == true || e.metaKey == true) return;
  4119. if (Q('viewselect').value < 4) {
  4120. var processed = 0;
  4121. if (e.key) {
  4122. if ((e.key.length === 1) && (searchFocus == 0)) { Q('KvmSearchInput').value = Q('SearchInput').value = ((Q('SearchInput').value + e.key)); processed = 1; }
  4123. if ((e.keyCode == 8) && (searchFocus == 0)) { var x = Q('SearchInput').value; Q('KvmSearchInput').value = Q('SearchInput').value = x.substring(0, x.length - 1); processed = 1; }
  4124. if (e.keyCode == 27) { Q('KvmSearchInput').value = Q('SearchInput').value = ''; processed = 1; }
  4125. } else {
  4126. if (e.charCode != 0 && searchFocus == 0) { Q('KvmSearchInput').value = Q('SearchInput').value = ((Q('SearchInput').value + String.fromCharCode(e.charCode))); processed = 1; }
  4127. }
  4128. if (processed > 0) { if (processed == 1) { mainUpdate(5); } return haltEvent(e); }
  4129. }
  4130. if (Q('viewselect').value == 4) {
  4131. if (e.key) {
  4132. if ((e.key.length === 1) && (mapSearchFocus == 0)) { Q('mapSearchLocation').value = ((Q('mapSearchLocation').value + e.key)); processed = 1; }
  4133. //if (e.keyCode == 8 && mapSearchFocus == 0) { var x = Q('mapSearchLocation').value; Q('mapSearchLocation').value = x.substring(0, x.length - 1); processed = 1; }
  4134. if (e.keyCode == 27) { Q('mapSearchLocation').value = ''; mapCloseSearchWindow(); processed = 1; }
  4135. if (e.keyCode == 13) { getSearchLocation(); }
  4136. } else {
  4137. if (e.charCode != 0 && mapSearchFocus == 0) { Q('mapSearchLocation').value = ((Q('mapSearchLocation').value + String.fromCharCode(e.charCode))); processed = 1; }
  4138. }
  4139. }
  4140. }
  4141. function ondockeydown(e) {
  4142. setSessionActivity();
  4143. if (!xxdialogMode && (xxcurrentView == 11) && desktop && Q('DeskControl').checked) {
  4144. // Check what keys we are allows to send
  4145. if (currentNode != null) {
  4146. var meshrights = GetNodeRights(currentNode);
  4147. var inputAllowed = ((features2 & 0x2000) == 0) && ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
  4148. if (inputAllowed == false) return false;
  4149. var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
  4150. if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
  4151. }
  4152. return desktop.m.handleKeyDown(e);
  4153. }
  4154. if (!xxdialogMode && (xxcurrentView == 12) && terminal && (terminal.State == 3) && xterm == null) { terminal.m.TermHandleKeyDown(e); if ((e.keyCode >= 37) && (e.keyCode <= 40)) { haltEvent(e); } }
  4155. if (!xxdialogMode && (xxcurrentView == 13) && (e.keyCode == 116) && (p13filetree != null)) { haltEvent(e); return false; } // F5 Refresh on files
  4156. if (!xxdialogMode && ((xxcurrentView == 15) || (xxcurrentView == 115))) { return agentConsoleHandleKeys(e); }
  4157. if (!xxdialogMode && (xxcurrentView == 4)) {
  4158. if ((e.keyCode === 8) && (userSearchFocus == 0)) { var x = Q('UserSearchInput').value; Q('UserSearchInput').value = (x.substring(0, x.length - 1)); processed = 1; }
  4159. if (e.keyCode === 27) { Q('UserSearchInput').value = ''; processed = 1; }
  4160. if (processed > 0) { if (processed == 1) { mainUpdate(5); } return haltEvent(e); }
  4161. }
  4162. if (xxdialogMode || (xxcurrentView != 1) || (e.ctrlKey == true) || (e.altKey == true) || (e.metaKey == true)) return;
  4163. var processed = 0;
  4164. if (Q('viewselect').value < 4) {
  4165. if ((e.keyCode === 8) && (searchFocus == 0)) { var x = Q('SearchInput').value; Q('KvmSearchInput').value = Q('SearchInput').value = (x.substring(0, x.length - 1)); processed = 1; }
  4166. if (e.keyCode === 27) { Q('KvmSearchInput').value = Q('SearchInput').value = ''; processed = 1; }
  4167. if (processed > 0) { if (processed == 1) { mainUpdate(5); } return haltEvent(e); }
  4168. }
  4169. if (Q('viewselect').value == 4) {
  4170. if ((e.keyCode === 8) && (mapSearchFocus == 0)) { var x = Q('mapSearchLocation').value; Q('mapSearchLocation').value = (x.substring(0, x.length - 1)); processed = 1; }
  4171. if (e.keyCode === 27) { Q('mapSearchLocation').value = ''; mapCloseSearchWindow(); processed = 1; }
  4172. }
  4173. }
  4174. function ondockeyup(e) {
  4175. setSessionActivity();
  4176. if (!xxdialogMode && (xxcurrentView == 11) && desktop && Q('DeskControl').checked) {
  4177. // Check what keys we are allows to send
  4178. if (currentNode != null) {
  4179. var meshrights = GetNodeRights(currentNode);
  4180. var inputAllowed = ((features2 & 0x2000) == 0) && ((meshrights == 0xFFFFFFFF) || (((meshrights & 8) != 0) && ((meshrights & 256) == 0)));
  4181. if (inputAllowed == false) return false;
  4182. var limitedInputAllowed = ((meshrights != 0xFFFFFFFF) && (((meshrights & 8) != 0) && ((meshrights & 256) == 0) && ((meshrights & 4096) != 0)));
  4183. if (limitedInputAllowed == true) { if ((e.altKey == true) || (e.ctrlKey == true) || ((e.keyCode < 32) && (e.keyCode != 8) && (e.keyCode != 13)) || (e.keyCode > 90)) return false; }
  4184. }
  4185. return desktop.m.handleKeyUp(e);
  4186. }
  4187. if (!xxdialogMode && (xxcurrentView == 12) && terminal && (terminal.State == 3) && xterm == null) { return terminal.m.TermHandleKeyUp(e); }
  4188. if (!xxdialogMode && (xxcurrentView == 13) && (e.keyCode == 116) && (p13filetree != null)) { p13folderup(9999); haltEvent(e); return false; } // F5 Refresh on files
  4189. if (!xxdialogMode && (xxcurrentView == 4)) { if (((e.keyCode === 8) && (searchFocus == 0)) || e.keyCode === 27) { return haltEvent(e); } }
  4190. if (xxdialogMode && (e.keyCode == 27)) { dialogclose(0); }
  4191. if ((e.shiftKey == true) && (e.keyCode == 27)) { dialogclose(0); Q('KvmSearchInput').value = Q('SearchInput').value = ''; mainUpdate(5); if (desktop != null) { connectDesktop(); } if (terminal != null) { connectTerminal(); } if (files != null) { connectFiles(); } go(1); return; } // Shift-ESC: Reset the web page
  4192. if (xxdialogMode || xxcurrentView != 0 || e.ctrlKey == true || (e.altKey == true) || (e.metaKey == true)) return;
  4193. if (Q('viewselect').value < 4) { if (((e.keyCode === 8) && (searchFocus == 0)) || (e.keyCode === 27)) { return haltEvent(e); } }
  4194. if (Q('viewselect').value == 4) { if (((e.keyCode === 8) && (mapSearchFocus == 0)) || (e.keyCode === 27)) { return haltEvent(e); } }
  4195. }
  4196. //function ondocfocus() { }
  4197. // TODO: Add handleReleaseKeys() for Intel AMT.
  4198. function ondocblur() { if (!xxdialogMode && xxcurrentView == 11 && desktop && Q('DeskControl').checked && desktop.m.handleReleaseKeys) { return desktop.m.handleReleaseKeys(); } }
  4199. // Highlights the device group hovered
  4200. function devGrpMouseHover(element, over) {
  4201. setSessionActivity();
  4202. var view = Q('viewselect').value;
  4203. var e = element.children[1].children[1];
  4204. e.children[0].classList.remove('g1s');
  4205. e.children[1].classList.remove('e2s');
  4206. e.children[2].classList.remove('g2s');
  4207. if (over == 1) {
  4208. e.children[0].classList.add('g1s');
  4209. e.children[1].classList.add('e2s');
  4210. e.children[2].classList.add('g2s');
  4211. }
  4212. }
  4213. // Highlights the device being hovered
  4214. function devMouseHover(element, over) {
  4215. setSessionActivity();
  4216. var view = Q('viewselect').value;
  4217. if (view == 1) {
  4218. var e = element.children[0].children[0].children[1].children[0].children[0].children[0];
  4219. e.children[1].classList.remove('g1s');
  4220. e.children[2].classList.remove('e2s');
  4221. e.children[3].classList.remove('g2s');
  4222. if (over == 1) {
  4223. e.children[1].classList.add('g1s');
  4224. e.children[2].classList.add('e2s');
  4225. e.children[3].classList.add('g2s');
  4226. }
  4227. } else if (view == 2) {
  4228. var e = element;
  4229. e.children[2].classList.remove('g1s');
  4230. e.children[4].classList.remove('e2s');
  4231. e.children[3].classList.remove('g2s');
  4232. if (over == 1) {
  4233. e.children[2].classList.add('g1s');
  4234. e.children[4].classList.add('e2s');
  4235. e.children[3].classList.add('g2s');
  4236. }
  4237. }
  4238. }
  4239. var deviceHeaderId = 0;
  4240. var deviceHeaderTotal = 0;
  4241. var deviceHeadersTitles = {};
  4242. var deviceHeaderCount;
  4243. var deviceHeaders = {};
  4244. var oldviewmode = 0;
  4245. function updateDevices() {
  4246. if ((nodes == null) || (xxcurrentView != 1)) { return; }
  4247. var r = '', c = 0, current = null, count = 0, scount = 0, displayedMeshes = {}, view = Q('viewselect').value, groups = {}, groupCount = {};
  4248. QV('xdevices', view != 4);
  4249. QV('xdevicesmap', view == 4);
  4250. QV('devListToolbar', view < 3);
  4251. QV('kvmListToolbar', (view == 3) || (view == 5));
  4252. QV('devMapToolbar', view == 4);
  4253. QV('devListToolbarSize', (view == 3) || (view == 5));
  4254. QV('devListToolbarSettings', view == 2);
  4255. QV('NoMeshesPanel', (nodes.length == 0) && (meshcount == 0));
  4256. //QV('devListToolbarView', (meshcount != 0) && (nodes.length > 0));
  4257. QV('devListToolbarViewIcons', nodes.length > 0);
  4258. QV('devListToolbarSort', (nodes.length > 0) && (view != 4));
  4259. if (nodes.length == 0) { view = 1; sort = 0; }
  4260. if (view == 4) {
  4261. setTimeout( function() { if (xxmap.map != null) { xxmap.map.updateSize(); } }, 200);
  4262. // TODO
  4263. } else {
  4264. // 3 wide, list view or desktop view
  4265. deviceHeaderId = 0;
  4266. deviceHeaderCount = {};
  4267. deviceHeaderTotal = 0;
  4268. deviceHeaders = {};
  4269. deviceHeadersTitles = {};
  4270. var kvmDivs = [];
  4271. // Perform node sort
  4272. if (sort == 0) { nodes.sort(meshSort); }
  4273. else if (sort == 1) { nodes.sort(powerSort); }
  4274. else if (sort == 2) { if (showRealNames == true) { nodes.sort(deviceHostSort); } else { nodes.sort(deviceSort); } }
  4275. else if (sort == 5) {
  4276. // If the last seen column is not turned on, turn it on first (we require this to sort the data)
  4277. if (!(deviceViewSettings && deviceViewSettings.devsCols && deviceViewSettings.devsCols.indexOf('lastseen') >= 0)) {
  4278. // Request last connection data if not requested yet
  4279. if (requestedLastConnects == false) { requestedLastConnects = true; meshserver.send({ action: 'lastconnects' }); }
  4280. // Force initialize the view settings
  4281. if (deviceViewSettings == null) { deviceViewSettings = {}; }
  4282. if (!Array.isArray(deviceViewSettings.devsCols)) { deviceViewSettings.devsCols = ['user','ip','conn', 'lastseen']; }
  4283. else { deviceViewSettings.devsCols.push('lastseen'); }
  4284. }
  4285. nodes.sort(lastConnectSort);
  4286. }
  4287. // Compute the width of the device view.
  4288. var totalDeviceViewWidth = Q('column_l').clientWidth - 60;
  4289. var deviceBoxWidth = Math.floor(totalDeviceViewWidth / 301);
  4290. deviceBoxWidth = 301 + Math.floor((totalDeviceViewWidth - (deviceBoxWidth * 301)) / deviceBoxWidth);
  4291. // Compute number of colums
  4292. var colcount = 4;
  4293. if (deviceViewSettings && deviceViewSettings.devsCols) {
  4294. colcount = deviceViewSettings.devsCols.length + 1;
  4295. if (deviceViewSettings.devsCols.indexOf('desc') >= 0) { colcount--; } // Description is not an extra column.
  4296. }
  4297. // Go thru the list of nodes and display them
  4298. for (var i in nodes) {
  4299. var node = nodes[i];
  4300. if (node.v == false) continue;
  4301. var mesh2 = meshes[node.meshid];
  4302. //if ((view == 3) && (mesh2.mtype == 1)) continue;
  4303. var meshrights = GetNodeRights(node);
  4304. if (sort == 0) {
  4305. // Mesh header
  4306. if (((meshes[node.meshid]?node.meshid:'*') != current)) {
  4307. if (((view == 1) || (view == 3) || (view == 5)) && (current != null)) { r += '</div>'; } // Close collapse div
  4308. deviceHeaderSet();
  4309. var extra = '';
  4310. if (view == 2) { r += '<tr><td colspan=' + colcount + '>'; }
  4311. if (meshes[node.meshid] && (meshes[node.meshid].mtype == 1)) { extra = '<span class=devHeaderx>' + ", Intel&reg; AMT only" + '</span>'; }
  4312. if (meshes[node.meshid] && (meshes[node.meshid].mtype == 3)) { if (meshes[node.meshid].relayid) { extra = '<span class=devHeaderx>' + ", Relayed Devices" + '</span>'; } else { extra = '<span class=devHeaderx>' + ", Local Devices" + '</span>'; } }
  4313. if ((view == 1) && (current != null)) { if (c == 2) { r += '<td><div style=width:301px></div></td>'; } if (r != '') { r += '</tr></table>'; } }
  4314. if (view == 2) { r += '<div>'; }
  4315. r += '<div class=DevSt style=width:100%;padding-top:4px><span style=float:right>';
  4316. r += '<span id=DevxHeader' + deviceHeaderId + ' class=devHeaderx></span>' + extra;
  4317. r += '</span>';
  4318. if ((view == 1) || (view == 2) || (view == 3) || (view == 5)) {
  4319. var collapsed = CollapsedGroups[node.meshid];
  4320. r += '<img class=collapseImage cmenu=expandAllContextMenu id="DevxColImg' + deviceHeaderId + '" src=images/c' + ((collapsed === true)?'1':'2') + '.png height=8 width=8 style=margin-left:2px;margin-right:2px;cursor:pointer onclick=toggleCollapseGroup("' + deviceHeaderId + '","' + node.meshid + '",' + view + ')></img>'; // Collapse action
  4321. }
  4322. if (meshes[node.meshid]) {
  4323. r += '<span id=MxMESH cmenu=meshContextMenu tabindex=0 style=cursor:pointer onclick=gotoMesh("' + node.meshid + '") onkeypress="if (event.key==\'Enter\') gotoMesh(\'' + node.meshid + '\')">' + EscapeHtml(meshes[node.meshid].name) + '</span>' + getMeshActions(mesh2, meshrights) + '</div>';
  4324. current = node.meshid;
  4325. } else {
  4326. r += '<span id=MxMESH><i>' + "Individual Devices" + '</i></span></div>';
  4327. current = '*';
  4328. }
  4329. if (view == 2) { r += '</div>'; }
  4330. displayedMeshes[current] = 1;
  4331. c = 0;
  4332. if ((view == 1) || (view == 3) || (view == 5)) { r += '<div id=DevxCol' + deviceHeaderId + ((collapsed === true)?' style=display:none':'') + '>'; } // Open collapse div
  4333. }
  4334. } else if (sort == 1) {
  4335. // Power header
  4336. var pwr = node.pwr?node.pwr:0;
  4337. if (pwr !== current) {
  4338. if (((view == 1) || (view == 3) || (view == 5)) && (current != null)) { r += '</div>'; } // Close collapse div
  4339. deviceHeaderSet();
  4340. if ((view == 1) && (current !== null)) { if (c == 2) { r += '<td><div style=width:301px></div></td>'; } if (r != '') { r += '</tr></table>'; } }
  4341. if (view == 2) { r += '<tr><td colspan=' + colcount + '>'; }
  4342. r += '<div class=DevSt style=width:100%;padding-top:4px><span id=DevxHeader' + deviceHeaderId + ' class=devHeaderx style=float:right></span>';
  4343. if ((view == 1) || (view == 2) || (view == 3) || (view == 5)) {
  4344. var collapsed = CollapsedGroups['pwr:' + pwr];
  4345. r += '<img class=collapseImage cmenu=expandAllContextMenu id="DevxColImg' + deviceHeaderId + '" src=images/c' + ((collapsed === true)?'1':'2') + '.png height=8 width=8 style=margin-left:2px;margin-right:2px;cursor:pointer onclick=toggleCollapseGroup("' + deviceHeaderId + '","pwr:' + pwr + '",' + view + ')></img>'; // Collapse action
  4346. }
  4347. r += '<span>' + PowerStateStr2(node.pwr) + '</span></div>';
  4348. current = pwr;
  4349. c = 0;
  4350. if ((view == 1) || (view == 3) || (view == 5)) { r += '<div id=DevxCol' + deviceHeaderId + ((collapsed === true)?' style=display:none':'') + '>'; } // Open collapse div
  4351. }
  4352. } else if (sort == 2 || sort == 5) {
  4353. // Device header
  4354. if (current == null) { current = '1'; }
  4355. }
  4356. count++;
  4357. if (view == 1) {
  4358. // Draw the device standin
  4359. r += '<div name=xxdevice1 id=xv1' + node._id + ' style=display:inline-block;position:relative;width:' + deviceBoxWidth + 'px;height:50px;padding-top:1px;padding-bottom:1px></div>';
  4360. } else if (view == 2) {
  4361. // Draw the device standin
  4362. var collapseName = node.meshid;
  4363. if (sort == 1) { collapseName = ('pwr:' + (node.pwr?node.pwr:0)); }
  4364. else if ((sort == 3) || (sort == 4)) { collapseName = 'tag:**xx**xx*TaG*xx**xx**'; }
  4365. var collapsed = (sort != 3) & (sort != 4) & CollapsedGroups[collapseName];
  4366. r += '<tr name=xxdevice2 colname=DevxCol' + collapseName + ' style=height:20px' + (collapsed?';display:none':'') + ' id=xv2' + node._id + '></tr>';
  4367. } else if (((view == 3) || (view == 5)) && (node.conn & 1) && (((meshrights & 8) || (meshrights & 256)) != 0) && (node.agent) && ((node.agent.caps & 1) != 0)) { // Check if we have rights and agent is capable of KVM.
  4368. // Draw the device (TODO: See if we can replace this with a standin in the future)
  4369. if ((Object.keys(checkedNodeids).length == 0) || checkedNodeids[node._id]) {
  4370. r += updateDeviceViewHtml(view, null, node);
  4371. kvmDivs.push(node._id);
  4372. }
  4373. }
  4374. // If we are displaying devices by tags, put the device in the right tag group.
  4375. if (((sort == 3) || (sort == 4)) && (r != '')) {
  4376. if (node.tags) {
  4377. for (var j in node.tags) {
  4378. var tag = node.tags[j];
  4379. if (sort == 4) { if (mesh2) { tag = mesh2.name + ' - ' + node.tags[j]; } else { tag = '**INDV*~*DEVS** - ' + node.tags[j]; } }
  4380. var collapsed = CollapsedGroups['tag:' + encodeURIComponentEx(tag)];
  4381. var r2 = r.replace('**xx**xx*TaG*xx**xx**', encodeURIComponentEx(tag) + (collapsed?' style=display:none':''));
  4382. if (groups[tag] == null) { groups[tag] = r2; groupCount[tag] = 1; } else { groups[tag] += r2; groupCount[tag] += 1; }
  4383. if ((view == 3) || (view == 5)) break;
  4384. }
  4385. } else {
  4386. // If sorted by "Groups-Tags" and a device has no tags, put in a group only section.
  4387. if ((sort == 4) && (mesh2 != null) && ((node.tags == null) || (node.tags.length == 0))) {
  4388. var tag = mesh2.name;
  4389. var collapsed = CollapsedGroups['tag:' + encodeURIComponentEx(tag)];
  4390. var r2 = r.replace('**xx**xx*TaG*xx**xx**', encodeURIComponentEx(tag) + (collapsed?' style=display:none':''));
  4391. if (groups[tag] == null) { groups[tag] = r2; groupCount[tag] = 1; } else { groups[tag] += r2; groupCount[tag] += 1; }
  4392. }
  4393. }
  4394. r = '';
  4395. }
  4396. deviceHeaderTotal++;
  4397. if (typeof deviceHeaderCount[node.state] == 'undefined') { deviceHeaderCount[node.state] = 1; } else { deviceHeaderCount[node.state]++; }
  4398. }
  4399. if ((view == 1) && (current != null)) { r += '</div>'; } // Close collapse div
  4400. // Above 32 devices, gray out the auto connect feature.
  4401. if (kvmDivs.length >= 32) { Q('autoConnectDesktopCheckbox').checked = false; }
  4402. QE('autoConnectDesktopCheckbox', kvmDivs.length < 32);
  4403. // If displaying devices by groups, sort the group names and display the devices.
  4404. if ((sort == 3) || (sort == 4)) {
  4405. var groupNames = [], tagDeviceHeaderId = 0;
  4406. for (var i in groups) { groupNames.push(i); }
  4407. //groupNames.sort(function (a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); });
  4408. groupNames.sort(function (a, b) { return sortCollator.compare(a.toLowerCase(), b.toLowerCase()); });
  4409. for (var j in groupNames) {
  4410. var i = groupNames[j];
  4411. if (view == 2) {
  4412. r += '<tr><td colspan=' + colcount + '><div class=DevSt style=width:100%;padding-top:4px>';
  4413. var collapsed = CollapsedGroups['tag:' + encodeURIComponentEx(i)];
  4414. r += '<img class=collapseImage cmenu=expandAllContextMenu id="DevxColImg' + tagDeviceHeaderId + '" src=images/c' + ((collapsed === true)?'1':'2') + '.png height=8 width=8 style=margin-left:2px;margin-right:2px;cursor:pointer onclick=toggleCollapseGroup("' + tagDeviceHeaderId + '","tag:' + encodeURIComponentEx(i) + '",2)></img>'; // Collapse action
  4415. r += '<span class=devHeaderx style=float:right>' + groupCount[i] + ' node' + ((groupCount[i] > 1) ? 's' : '') + '</span><span>' + EscapeHtml(i).split('|').join(' &rarr; ').split('**INDV*~*DEVS**').join('<i>' + "Individual Devices" + '</i>') + '</span></div>' + groups[i];
  4416. } else {
  4417. r += '<div class=DevSt style=width:100%;padding-top:4px><span class=devHeaderx style=float:right>' + format(((groupCount[i] > 1) ? '{0} nodes' : '{0} node'), groupCount[i]) + '</span>';
  4418. var collapsed = CollapsedGroups['tag:' + encodeURIComponentEx(i)];
  4419. r += '<img class=collapseImage cmenu=expandAllContextMenu id="DevxColImg' + tagDeviceHeaderId + '" src=images/c' + ((collapsed === true)?'1':'2') + '.png height=8 width=8 style=margin-left:2px;margin-right:2px;cursor:pointer onclick=toggleCollapseGroup("' + tagDeviceHeaderId + '","tag:' + encodeURIComponentEx(i) + '")></img>'; // Collapse action
  4420. r += '<span>' + EscapeHtml(i).split('|').join(' &rarr; ').split('**INDV*~*DEVS**').join('<i>' + "Individual Devices" + '</i>') + '</span></div>';
  4421. r += '<div id=DevxCol' + tagDeviceHeaderId + ((collapsed === true)?' style=display:none':'') + '>' + groups[i] + '</div>'; // Open collapse div
  4422. }
  4423. tagDeviceHeaderId++;
  4424. }
  4425. }
  4426. // If there is nothing to display, explain the problem
  4427. var viewNothing = false;
  4428. if ((r == '') && (nodes.length > 0) && ((Q('SearchInput').value != '') || (Q('DevFilterSelect').value != 0))) {
  4429. viewNothing = true;
  4430. if (sort == 3) {
  4431. r = '<div style="margin:30px">' + "No devices are included in any groups, click on a device's \"Groups\" to add to a group." + '</div>';
  4432. } else {
  4433. r = '<div style="margin:30px">' + "No devices matching this search." + ' <a onclick=clearDeviceSearch()>' + "Clear search filter" + '</a></div>';
  4434. }
  4435. }
  4436. if ((view == 1) && (c == 2)) r += '<td><div style=width:301px></div></td>'; // Adds device padding
  4437. // Display all empty device groups, we need to do this because users can add devices to these at any time.
  4438. if ((sort == 0) && ((Q('SearchInput').value == '') && (Q('DevFilterSelect').value == 0)) && (view < 3)) {
  4439. var deviceHeaderId2 = deviceHeaderId, sortedMeshes = [];
  4440. for (var i in meshes) { sortedMeshes.push(meshes[i]); }
  4441. sortedMeshes.sort(nameSort);
  4442. for (var i in sortedMeshes) {
  4443. var mesh = sortedMeshes[i], meshrights = GetMeshRights(mesh);
  4444. if (displayedMeshes[mesh._id] == null) {
  4445. if ((current != '') && (r != '')) { r += '</tr></table>'; }
  4446. r += '<table style=width:100%;padding-top:4px cellpadding=0 cellspacing=0><tr><td colspan=3 class=DevSt>';
  4447. // Collapsing header & start collapsing area
  4448. deviceHeaderId2++;
  4449. var collapsed = CollapsedGroups[mesh._id];
  4450. r += '<img class=collapseImage cmenu=expandAllContextMenu id="DevxColImg' + deviceHeaderId2 + '" src=images/c' + ((collapsed === true)?'1':'2') + '.png height=8 width=8 style=margin-left:2px;margin-right:2px;cursor:pointer onclick=toggleCollapseGroup("' + deviceHeaderId2 + '","' + mesh._id + '")></img>'; // Collapse action
  4451. r += '<span id=MxMESH style=cursor:pointer onclick=gotoMesh("' + mesh._id + '")>' + EscapeHtml(mesh.name) + '</span><span>';
  4452. r += getMeshActions(mesh, meshrights);
  4453. r += '</span></td></tr><tr>';
  4454. if (mesh.mtype == 1) {
  4455. r += '<td><div style=padding:10px><i>' + "No Intel&reg; AMT devices in this device group";
  4456. if (((meshrights & 4) != 0) && ((userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.siteadmin & 4096) == 0))) { r += ', <a href=# style=cursor:pointer onclick=\'return addDeviceToMesh("' + mesh._id + '")\'>' + "add one" + '</a>'; }
  4457. } else if (mesh.mtype == 2) {
  4458. r += '<td><div id=DevxCol' + deviceHeaderId2 + ((collapsed === true)?' style=display:none':'') + '>'; // Open collapse div
  4459. r += '<div style=padding:10px><i>' + "No devices in this device group";
  4460. if (((meshrights & 4) != 0) && ((userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.siteadmin & 4096) == 0))) { r += ', <a href=# style=cursor:pointer onclick=\'return addAgentToMesh("' + mesh._id + '")\'>' + "add one" + '</a>'; }
  4461. } else if (mesh.mtype == 3) {
  4462. r += '<td><div id=DevxCol' + deviceHeaderId2 + ((collapsed === true)?' style=display:none':'') + '>'; // Open collapse div
  4463. r += '<div style=padding:10px><i>' + "No local devices in this device group";
  4464. if (((meshrights & 4) != 0) && ((userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.siteadmin & 4096) == 0))) { r += ', <a href=# style=cursor:pointer onclick=\'return addLocalDeviceToMesh("' + mesh._id + '")\'>' + "add one" + '</a>'; }
  4465. } else if (mesh.mtype == 4) {
  4466. r += '<td><div id=DevxCol' + deviceHeaderId2 + ((collapsed === true)?' style=display:none':'') + '>'; // Open collapse div
  4467. r += '<div style=padding:10px><i>' + "No devices in this device group";
  4468. }
  4469. r += '.</i></div></td>';
  4470. r += '</div>'; // End collapsing area
  4471. current = mesh._id;
  4472. count++;
  4473. }
  4474. }
  4475. }
  4476. if ((r != '') && (viewNothing == false)) {
  4477. r += '</table>';
  4478. if (view == 2) {
  4479. var colums = '';
  4480. // Use defaults if needed
  4481. if (deviceViewSettings == null) { deviceViewSettings = {}; }
  4482. if (!Array.isArray(deviceViewSettings.devsCols)) { deviceViewSettings.devsCols = ['user','ip','conn']; }
  4483. // Display configured columns
  4484. if (deviceViewSettings.devsCols.indexOf('agtype') >= 0) { colums += '<th style=color:gray;width:100px>' + "Agent Type"; }
  4485. if (deviceViewSettings.devsCols.indexOf('agver') >= 0) { colums += '<th style=color:gray;width:100px>' + "Agent Version"; }
  4486. if (deviceViewSettings.devsCols.indexOf('os') >= 0) { colums += '<th style=color:gray;width:160px>' + "OS"; }
  4487. if (deviceViewSettings.devsCols.indexOf('tags') >= 0) { colums += '<th style=color:gray;width:120px>' + "Tags"; }
  4488. if (deviceViewSettings.devsCols.indexOf('windowsav') >= 0) { colums += '<th style=color:gray;width:100px>' + "Windows AV"; }
  4489. if (deviceViewSettings.devsCols.indexOf('windowsupdate') >= 0) { colums += '<th style=color:gray;width:120px>' + "Windows Update"; }
  4490. if (deviceViewSettings.devsCols.indexOf('windowsfirewall') >= 0) { colums += '<th style=color:gray;width:120px>' + "Windows Firewall"; }
  4491. if (deviceViewSettings.devsCols.indexOf('lastbootuptime') >= 0) { colums += '<th style=color:gray;width:120px>' + "Last Boot Up Time"; }
  4492. if (deviceViewSettings.devsCols.indexOf('links') >= 0) { colums += '<th style=color:gray;width:120px>' + "Links"; }
  4493. if (deviceViewSettings.devsCols.indexOf('user') >= 0) { colums += '<th style=color:gray;width:120px>' + "User"; }
  4494. if (deviceViewSettings.devsCols.indexOf('ip') >= 0) { colums += '<th style=color:gray;width:120px>' + "Address"; }
  4495. if (deviceViewSettings.devsCols.indexOf('conn') >= 0) { colums += '<th style=color:gray;width:100px>' + "Connectivity"; }
  4496. if (deviceViewSettings.devsCols.indexOf('amthost') >= 0) { colums += '<th style=color:gray;width:100px>' + "AMT Host"; }
  4497. if (deviceViewSettings.devsCols.indexOf('amtstate') >= 0) { colums += '<th style=color:gray;width:100px>' + "AMT State"; }
  4498. if (deviceViewSettings.devsCols.indexOf('lastseen') >= 0) {
  4499. colums += '<th style=color:gray;width:120px>' + "Last Seen";
  4500. if (requestedLastConnects == false) { requestedLastConnects = true; meshserver.send({ action: 'lastconnects' }); }
  4501. }
  4502. // This height of 1 div at the end to fix a problem in Linux firefox browsers
  4503. r = '<table style=width:100%;margin-top:4px;table-layout:fixed cellpadding=0 cellspacing=0><th style=color:gray>' + colums + r + '</tr></table><div style=height:1px></div>';
  4504. }
  4505. } else {
  4506. r += '</table>';
  4507. if (sort == 3) { r = '<div style="margin:10px"><i>' + "No devices with tags found." + '</i></div>'; }
  4508. }
  4509. // Add a "Add Device Group" option
  4510. r += '<div style=border-top-style:solid;border-top-width:1px;border-top-color:#DDDDDD;cursor:pointer;font-size:small;margin-top:4px>';
  4511. if ((view < 3) && (sort == 0) && (Object.keys(meshes).length > 0) && ((userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.siteadmin & 64) == 0))) {
  4512. r += '<a href=# onclick="return account_createMesh()" title="' + "Create a new group of devices." + '" style=cursor:pointer>' + "Add Device Group" + '</a>&nbsp';
  4513. }
  4514. if ((userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.siteadmin & 128) == 0)) {
  4515. r += '<a href=# onclick=\'return p10showMeshCmdDialog(0)\' style=cursor:pointer title="' + "Download MeshCmd, a command line tool that performs many functions." + '">' + "MeshCmd" + '</a>&nbsp';
  4516. if ((navigator.platform.toLowerCase() == 'win32') || (navigator.platform.toLowerCase() == 'macintel')) { r += '<a href=# onclick=\'return p10showMeshRouterDialog()\' style=cursor:pointer title="' + "Download MeshCentral Router, a TCP port mapping tool." + '">' + "Router" + '</a>&nbsp'; }
  4517. }
  4518. r += '</div><br/>';
  4519. QH('xdevices', r);
  4520. deviceHeaderSet();
  4521. for (var i in deviceHeaders) { QH(i, deviceHeaders[i]); }
  4522. for (var i in deviceHeadersTitles) { Q(i).title = deviceHeadersTitles[i]; }
  4523. p1updateInfo();
  4524. // Take care of KVM surfaces in desktop view mode
  4525. if (((view == 3) || (view == 5)) && (xxcurrentView == 1)) {
  4526. // Figure out and adjust the size to fill the width of the div
  4527. var vsize = [{ x: 180, y: 101 }, { x: 302, y: 169 }, { x: 454, y: 255 }][Q('sizeselect').selectedIndex];
  4528. if ((view == 3)) { // If we are in fixed width mode, correct the size for the screen width.
  4529. var realw = vsize.x + 2, tw = totalDeviceViewWidth - 5, xw = Math.floor(tw / realw);
  4530. xw = realw + Math.floor((tw - (xw * realw)) / xw);
  4531. vsize.y = vsize.y * (xw / vsize.x);
  4532. vsize.x = xw;
  4533. }
  4534. for (var i in multiDesktop) { multiDesktop[i].xxdelete = true; }
  4535. for (var i in kvmDivs) {
  4536. var id = kvmDivs[i], shortid = id.split('/')[2], desk = multiDesktop[id];
  4537. if (desk != null) {
  4538. // This device already has a canvas, use it.
  4539. var width = (view == 5)?((desk.m.width * vsize.y) / desk.m.height):vsize.x;
  4540. desk.m.CanvasId.setAttribute('style', 'background-color:black;width:' + width + 'px;height:' + vsize.y + 'px');
  4541. Q('xkvmid_' + shortid).appendChild(desk.m.CanvasId);
  4542. delete desk.xxdelete;
  4543. QH('skvmid_' + shortid, ["Disconnected", "Connecting...", "Setup...", '', ''][((desk.m.State == null)?desk.m.state:desk.m.State)]);
  4544. } else {
  4545. var node = getNodeFromId(id);
  4546. if ((desktopNode == node) && (desktop != null)) { // Check if the main desktop is this device, if it is, use that.
  4547. // This device already has a canvas, use it.
  4548. var c = desktop.m.CanvasId;
  4549. var width = (view == 5)?((desktop.m.width * vsize.y) / desktop.m.height):vsize.x;
  4550. c.setAttribute('id', 'kvmid_' + shortid);
  4551. c.setAttribute('style', 'background-color:black;width:' + width + 'px;height:' + vsize.y + 'px');
  4552. c.setAttribute('onclick', 'toggleKvmDevice(\'' + id + '\')');
  4553. c.removeAttribute('onmousedown');
  4554. c.removeAttribute('onmouseup');
  4555. c.removeAttribute('onmousemove');
  4556. Q('xkvmid_' + shortid).appendChild(c);
  4557. QH('skvmid_' + shortid, ["Disconnected", "Connecting...", "Setup...", '', ''][((desktop.m.State == null)?desktop.m.state:desktop.m.State)]);
  4558. if (desktop.m.SendCompressionLevel) { desktop.m.SendCompressionLevel(multidesktopsettings.agentencoding, multidesktopsettings.quality, multidesktopsettings.scaling, multidesktopsettings.framerate); }
  4559. desktop.shortid = shortid;
  4560. desktop.onStateChanged = onMultiDesktopStateChange;
  4561. desktop.m.onRemoteInputLockChanged = null;
  4562. desktop.m.onKeyboardStateChanged = null;
  4563. multiDesktop[id] = desktop;
  4564. desktop = desktopNode = currentNode = null;
  4565. // Setup a replacement desktop
  4566. QH('DeskParent', '<canvas id="Desk" oncontextmenu="return false" onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event)></canvas>');
  4567. } else {
  4568. // This is a new device, create a canvas for it.
  4569. var c = document.createElement('canvas');
  4570. c.setAttribute('id', 'kvmid_' + shortid);
  4571. c.setAttribute('width', 640);
  4572. c.setAttribute('height', 480);
  4573. c.setAttribute('oncontextmenu', 'return false');
  4574. c.setAttribute('style', 'background-color:black;width:' + vsize.x + 'px;height:' + vsize.y + 'px');
  4575. c.setAttribute('onclick', 'toggleKvmDevice(\'' + id + '\')');
  4576. try { Q('xkvmid_' + shortid).appendChild(c); } catch (ex) {}
  4577. // Check if we need to auto-connect
  4578. if (Q('autoConnectDesktopCheckbox').checked == true) { setTimeout(function() { connectMultiDesktop(node, 1); }, 100); }
  4579. }
  4580. }
  4581. }
  4582. for (var i in multiDesktop) {
  4583. // If a device is no longer viewed, disconnect it.
  4584. if (multiDesktop[i].xxdelete == true) { multiDesktop[i].Stop(); delete multiDesktop[i]; }
  4585. else if (debugmode && multiDesktop[i].m && multiDesktop[i].m.onScreenSizeChange) {
  4586. mdeskAdjust(multiDesktop[i].m, multiDesktop[i].m.ScreenWidth, multiDesktop[i].m.ScreenHeight, multiDesktop[i].m.CanvasId); // Adjust screen size change
  4587. }
  4588. }
  4589. deskAdjust();
  4590. } else {
  4591. disconnectAllKvmFunction();
  4592. Q('autoConnectDesktopCheckbox').checked = false;
  4593. }
  4594. }
  4595. oldviewmode = view;
  4596. onDevicesScrollEx();
  4597. }
  4598. var onDevicesTouchActive = false;
  4599. var onDevicesScrollnagleTimer = null;
  4600. function onDevicesScroll(top) {
  4601. if(typeof top == 'undefined') top = false;
  4602. if(top) Q('xdevices').scrollTop = 0;
  4603. if ((onDevicesScrollnagleTimer != null) || (onDevicesTouchActive)) return;
  4604. onDevicesScrollnagleTimer = setTimeout(onDevicesScrollEx, 250);
  4605. }
  4606. function onDeviceTouch(x) {
  4607. if (onDevicesTouchActive == x) return;
  4608. onDevicesTouchActive = x;
  4609. if (x == false) onDevicesScrollEx();
  4610. }
  4611. function onDevicesScrollEx() {
  4612. if (onDevicesScrollnagleTimer != null) { clearTimeout(onDevicesScrollnagleTimer); onDevicesScrollnagleTimer = null; }
  4613. var visibleTop = Q('xdevices').scrollTop - 200, visibleBottom = Q('xdevices').scrollTop + Q('xdevices').clientHeight + 200;
  4614. var view = Q('viewselect').value, devdivs = document.getElementsByName('xxdevice' + view);
  4615. for (var i = 0; i < devdivs.length; i++) {
  4616. if ((devdivs[i].offsetTop >= visibleTop) && (devdivs[i].offsetTop < visibleBottom)) {
  4617. var node = getNodeFromId(devdivs[i].id.substring(3));
  4618. if (node != null) { updateDeviceViewHtml(view, devdivs[i], node); } else { devdivs[i].innerHTML = ''; }
  4619. } else {
  4620. devdivs[i].innerHTML = '';
  4621. }
  4622. }
  4623. }
  4624. // Update a single device in the current view
  4625. function updateDeviceViewDevice(node) {
  4626. var view = Q('viewselect').value;
  4627. if ((view != 1) && (view != 2)) { mainUpdate(4); return; }
  4628. if (typeof node == 'string') { node = getNodeFromId(node); }
  4629. if (node == null) return;
  4630. var devdiv = Q('xv' + view + node._id);
  4631. if ((devdiv != null) && (devdiv.innerHTML != '')) { updateDeviceViewHtml(view, devdiv, node); } // Only update if the device is visible
  4632. }
  4633. function updateDeviceViewHtml(view, div, node) {
  4634. var title = EscapeHtml(node.name);
  4635. if (title.length == 0) { title = '<i>' + "None" + '</i>'; }
  4636. if ((node.rname != null) && (node.rname.length > 0)) { title += ' / ' + EscapeHtml(node.rname); }
  4637. var name = EscapeHtml(node.name);
  4638. if (showRealNames == true && node.rname != null) name = EscapeHtml(node.rname);
  4639. if (name.length == 0) { name = '<i>' + "None" + '</i>'; }
  4640. if (((view == 1) && (div.parentNode.style['display'] == 'none')) || ((view == 2) && (div.style['display'] == 'none'))) { div.innerHTML = ''; return; } // If this section is collapsed, don't render
  4641. // Add device notification icons
  4642. var devNotify = '', devNotifySub = '';
  4643. // This device is "starred"
  4644. if (stars[node._id] == 1) {
  4645. if (view == 2) {
  4646. devNotifySub += '<img class=deviceNotifySmallDotSub src=images/icon-star-notify-10.png width=10 height=10>';
  4647. } else {
  4648. devNotifySub += '<img class=deviceNotifyDotSub src=images/icon-star-notify-16.png width=16 height=16>';
  4649. }
  4650. }
  4651. // This device has session information
  4652. if (node.sessions != null) {
  4653. // Display any agent messages
  4654. if (node.sessions.msg != null) {
  4655. if (view == 2) {
  4656. devNotifySub += '<div onclick=showDeviceMessages(\'' + node._id + '\',null,event) style="width:10;height:10" class=deviceNotifySmallDotSub></div>';
  4657. } else {
  4658. devNotifySub += '<div onclick=showDeviceMessages(\'' + node._id + '\',null,event) style="width:16;height:16" class=deviceNotifyDotSub>' + Object.keys(node.sessions.msg).length + '</div>';
  4659. }
  4660. }
  4661. // Sessions are active
  4662. if ((node.sessions.kvm != null) || (node.sessions.terminal != null) || (node.sessions.files != null) || (node.sessions.tcp != null) || (node.sessions.udp != null)) {
  4663. if (view == 2) {
  4664. devNotifySub += '<img onclick=showDeviceSessions(\'' + node._id + '\',null,event) class=deviceNotifySmallDotSub src=images/icon-relay-notify10.png width=10 height=10>';
  4665. } else {
  4666. devNotifySub += '<img onclick=showDeviceSessions(\'' + node._id + '\',null,event) class=deviceNotifyDotSub src=images/icon-relay-notify.png width=16 height=16>';
  4667. }
  4668. }
  4669. // Help is required
  4670. if (node.sessions.help != null) {
  4671. if (view == 2) {
  4672. devNotifySub += '<img onclick=showDeviceHelpRequests(\'' + node._id + '\',null,event) class=deviceNotifySmallDotSub src=images/icon-help-notify-10.png width=10 height=10>';
  4673. } else {
  4674. devNotifySub += '<img onclick=showDeviceHelpRequests(\'' + node._id + '\',null,event) class=deviceNotifyDotSub src=images/icon-help-notify-16.png width=16 height=16>';
  4675. }
  4676. }
  4677. // Battery state
  4678. if ((node.sessions.battery != null) && (view == 1)) {
  4679. var bat = node.sessions.battery;
  4680. var statestr = '';
  4681. if (bat.state == 'ac') { statestr = "Device is plugged-in"; }
  4682. if (bat.state == 'dc') { statestr = "Device is battery powered"; }
  4683. var levelstr = '', levelnum = -1;
  4684. if ((typeof bat.level == 'number') && (bat.level >= 0) && (bat.level <= 100)) {
  4685. levelstr = bat.level + '%';
  4686. levelnum = (Math.floor((bat.level + 10) / 25) + 1);
  4687. if (levelnum > 5) { lvl = 5; }
  4688. if (bat.state == 'ac') { if (bat.level == 100) { levelnum = 11; } else { levelnum += 5; } }
  4689. }
  4690. if (levelnum > 0) {
  4691. devNotify += '<div class="deviceBatterySmall deviceBatterySmall' + levelnum + '" title="' + ((statestr != null)?(statestr + ', ' + levelstr):levelstr) + '"></div>';
  4692. }
  4693. }
  4694. }
  4695. // Add any device icons
  4696. if (devNotifySub != '') {
  4697. if (view == 2) {
  4698. devNotify += '<div class=deviceNotifySmallDot>' + devNotifySub + '</div>';
  4699. } else {
  4700. devNotify += '<div class=deviceNotifyDot>' + devNotifySub + '</div>';
  4701. }
  4702. }
  4703. // Node
  4704. var icon = node.icon;
  4705. if (((!node.conn) || (node.conn == 0)) && (node.mtype != 3)) { icon += ' gray'; }
  4706. if (view == 1) {
  4707. div.innerHTML = '<table id=devs cmenu=devsContentMenu onmouseover=devMouseHover(this,1) onmouseout=devMouseHover(this,0) style=width:100%;height:100%><tr><td style=width:22px><input class="' + node.meshid + ' DeviceCheckbox" onchange=p1devcheck(event) value=devid_' + node._id + ' type=checkbox ' + (checkedNodeids[node._id]?' checked':'') + '></td><td><table onmouseup=gotoDevice(\'' + node._id + '\',null,null,event) border=0 cellspacing=0 style=width:100%;height:100%;cursor:pointer><tr><td style=width:50px tabindex=0 onkeypress="if (event.key==\'Enter\') gotoDevice(\'' + node._id + '\',null,null,event)"><div class="i' + icon + '" style=width:50px></div></td><td class=g1t></td><td class=e2t><div class=e1t style=width:' + ((div.clientWidth) - 120) + 'px title="' + title + '">' + name + '</div><div>' + NodeStateStr(node) + '</div></td><td class=g2t></td></tr></table></td></tr></table>' + devNotify;
  4708. } else if (view == 2) {
  4709. var states = [];
  4710. if (node.conn) {
  4711. if ((node.conn & 1) != 0) {
  4712. if (node.mtype == 4) {
  4713. if (node.porttype == 'PDU') {
  4714. states.push('<span title="' + "Switch port is ready for use." + '">' + "Switch" + '</span>');
  4715. } else {
  4716. states.push('<span title="' + "IP-KVM port is connected and ready for use." + '">' + "IP-KVM" + '</span>');
  4717. }
  4718. } else {
  4719. states.push('<span title="' + "Mesh agent is connected and ready for use." + '">' + "Agent" + '</span>');
  4720. }
  4721. }
  4722. if ((node.conn & 2) != 0) { states.push('<span title="' + "Intel&reg; AMT CIRA is connected and ready for use." + '">' + "CIRA" + '</span>'); }
  4723. else if ((node.conn & 4) != 0) { states.push('<span title="' + "Intel&reg; AMT is routable." + '">' + "AMT" + '</span>'); }
  4724. if ((node.conn & 8) != 0) { states.push('<span title="' + "Mesh agent is reachable using another agent as relay." + '">' + "Relay" + '</span>'); }
  4725. if ((node.conn & 16) != 0) { states.push('<span title="' + "MQTT connection to the device is active." + '">' + "MQTT" + '</span>'); }
  4726. }
  4727. if (node.mtype == 3) {
  4728. var mesh = meshes[node.meshid];
  4729. if (mesh && mesh.relayid) {
  4730. states.push('<span title="' + "Local network connection thru a relay agent." + '">' + "Relay" + '</span>');
  4731. } else {
  4732. states.push('<span title="' + "Local network connection." + '">' + "Local" + '</span>');
  4733. }
  4734. }
  4735. if (node.desc && (deviceViewSettings.devsCols.indexOf('desc') >= 0)) { name = '<div style=float:right' + (stars[node._id] == 1 ? ';padding-right:15px' : '') + '>' + EscapeHtml(node.desc) + '</div><div>' + name + '</div>'; }
  4736. var collapseName = node.meshid;
  4737. if (sort == 1) { collapseName = ('pwr:' + (node.pwr?node.pwr:0)); }
  4738. else if ((sort == 3) || (sort == 4)) { collapseName = 'tag:**xx**xx*TaG*xx**xx**'; }
  4739. var collapsed = (sort != 3) & (sort != 4) & CollapsedGroups[collapseName];
  4740. var r = '<td style=position:relative><div id=devs cmenu=devsContentMenu class=bar18 tabindex=0 onmouseover=devMouseHover(this,1) onmouseout=devMouseHover(this,0) style=height:18px;width:100%;font-size:medium onkeypress="if (event.key==\'Enter\') gotoDevice(\'' + node._id + '\',null,null,event)">';
  4741. r += '<div class=deviceBarCheckbox><input class="' + node.meshid + ' DeviceCheckbox" value=devid_' + node._id + ' type=checkbox onchange=p1devcheck(event) ' + (checkedNodeids[node._id]?' checked':'') + '></div>';
  4742. r += '<div class=deviceBarIcon onmouseup=gotoDevice(\'' + node._id + '\',null,null,event)><div class="j' + icon + '" style=width:16px;margin-top:1px;margin-left:2px;height:16px></div></div>';
  4743. r += '<div class=g1 style=height:18px;float:left></div><div class=g2 style=height:18px;float:right></div>';
  4744. r += '<div class=style10 style=cursor:pointer;font-size:14px;max-height:18px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap title="' + title + '" onmouseup=gotoDevice(\'' + node._id + '\',null,null,event)>' + name + '</div></div>' + devNotify + '</td>';
  4745. // Use defaults if needed
  4746. if (deviceViewSettings == null) { deviceViewSettings = {}; }
  4747. if (!Array.isArray(deviceViewSettings.devsCols)) { deviceViewSettings.devsCols = ['user','ip','conn']; }
  4748. // Display configured columns
  4749. if (deviceViewSettings.devsCols.indexOf('agtype') >= 0) { r += '<td style=text-align:center;font-size:x-small;padding-left:4px;padding-right:4px title="' + EscapeHtml(((node.mtype != 3) && node.agent && node.agent.id && (node.agent.id <= agentsStr.length))?agentsStr[node.agent.id]:'') + '">' + EscapeHtml(((node.mtype != 3) && node.agent && node.agent.id && (node.agent.id <= agentsStr.length))?agentsStr[node.agent.id]:'').replace(',', '<br />'); } // Agent type
  4750. if (deviceViewSettings.devsCols.indexOf('agver') >= 0) { r += '<td style=text-align:center;font-size:x-small;padding-left:4px;padding-right:4px title="' + EscapeHtml((node.agent && node.agent.core)?node.agent.core:'') + '">' + EscapeHtml((node.agent && node.agent.core)?node.agent.core:'').replace(',', '<br />'); } // Agent core
  4751. if (deviceViewSettings.devsCols.indexOf('os') >= 0) { // Operating System
  4752. if (node.mtype == 3) {
  4753. var osstr = ''; if (node.agent.id == 4) { osstr = 'Windows'; } if (node.agent.id == 6) { osstr = 'Linux'; } if (node.agent.id == 29) { osstr = 'MacOS'; }
  4754. r += '<td style=text-align:center;font-size:x-small title="' + EscapeHtml(osstr) + '">' + EscapeHtml(osstr);
  4755. } else {
  4756. r += '<td style=text-align:center;font-size:x-small title="' + EscapeHtml(node.osdesc?node.osdesc:'') + '">' + EscapeHtml(node.osdesc?node.osdesc:'');
  4757. }
  4758. }
  4759. if (deviceViewSettings.devsCols.indexOf('tags') >= 0) { // Tags
  4760. r += '<td style=text-align:center;font-size:x-small>';
  4761. var groupingTags = '';
  4762. if (node.tags != null) {
  4763. for (var i in node.tags) {
  4764. groupingTags += '<span class=tagSpan>' + EscapeHtml(node.tags[i]) + '</span> ';
  4765. }
  4766. }
  4767. r += '<span style=line-height:20px>' + groupingTags + '</span>';
  4768. }
  4769. if (deviceViewSettings.devsCols.indexOf('windowsav') >= 0) { // Windows AV
  4770. r += '<td style=text-align:center>' + ((node.wsc && node.wsc.antiVirus != null) ? (node.wsc.antiVirus == 'OK' ? '<span style=color:green>' + "OK" + '</span>' : '<span style=color:red>' + "BAD" + '</span>') : "");
  4771. }
  4772. if (deviceViewSettings.devsCols.indexOf('windowsupdate') >= 0) {// Windows Update
  4773. r += '<td style=text-align:center>' + ((node.wsc && node.wsc.autoUpdate != null) ? (node.wsc.autoUpdate == 'OK' ? '<span style=color:green>' + "OK" + '</span>' : '<span style=color:red>' + "BAD" + '</span>') : "");
  4774. }
  4775. if (deviceViewSettings.devsCols.indexOf('windowsfirewall') >= 0) { // Windows Firewall
  4776. r += '<td style=text-align:center>' + ((node.wsc && node.wsc.firewall != null) ? (node.wsc.firewall == 'OK' ? '<span style=color:green>' + "OK" + '</span>' : '<span style=color:red>' + "BAD" + '</span>') : "");
  4777. }
  4778. if (deviceViewSettings.devsCols.indexOf('lastbootuptime') >= 0) { // Last Boot Up Time
  4779. r += '<td style=text-align:center;font-size:x-small>' + ((node.lastbootuptime != null) ? printDateTime(new Date(node.lastbootuptime)) : "");
  4780. }
  4781. if (deviceViewSettings.devsCols.indexOf('links') >= 0) { r += '<td style=text-align:center;font-size:x-small>' + getShortRouterLinks(node); } // Links
  4782. if (deviceViewSettings.devsCols.indexOf('user') >= 0) { r += '<td style=text-align:center>' + getUserShortStr(node); } // User
  4783. if (deviceViewSettings.devsCols.indexOf('ip') >= 0) { var ip = ''; if (node.mtype == 3) { ip = node.host; } else if (node.ip) { ip = node.ip; } r += '<td style=text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis title="' + EscapeHtml(ip) + '">' + EscapeHtml(ip); } // IP address
  4784. if (deviceViewSettings.devsCols.indexOf('conn') >= 0) { r += '<td style=text-align:center>' + states.join('&nbsp;+&nbsp;'); } // Connectivity
  4785. if (deviceViewSettings.devsCols.indexOf('amthost') >= 0) { r += '<td style=text-align:center>' + (((node.intelamt == null) || (node.intelamt.host == null)) ? '' : EscapeHtml(node.intelamt.host)); }
  4786. if (deviceViewSettings.devsCols.indexOf('amtstate') >= 0) {
  4787. var amtstate = '';
  4788. if (node.intelamt) {
  4789. if (node.intelamt.state == 0) { amtstate = ' <span title="' + "Intel&reg; AMT is not activated" + '">' + "Pre" + '</span>'; }
  4790. if (node.intelamt.state == 1) { amtstate = ' <span title="' + "Intel&reg; AMT is in activation mode" + '">' + "In" + '</span>'; }
  4791. if ((node.intelamt.state == 2) && node.intelamt.flags) { if (node.intelamt.flags & 2) { amtstate = ' <span title="' + "Intel&reg; AMT is activated in Client Control Mode" + '">' + "CCM" + '</span>'; } else if (node.intelamt.flags & 4) { amtstate += ' <span title="' + "Intel&reg; AMT is activated in Admin Control Mode" + '">' + "ACM" + '</span>'; } }
  4792. }
  4793. r += '<td style=text-align:center>' + amtstate;
  4794. }
  4795. if (deviceViewSettings.devsCols.indexOf('lastseen') >= 0) { r += '<td style=text-align:center;font-size:x-small>'; if (node.conn > 0) { r += "Connected"; } else if (node.lastconnect != null) { r += printDateTime(new Date(node.lastconnect)); } }
  4796. div.innerHTML = r;
  4797. } else if ((view == 3) || (view == 5)) {
  4798. // Draw the device and canvas
  4799. var r = '<div id=devs cmenu=devsContentMenu style=display:inline-block;position:relative;margin:1px;background-color:lightgray;border-radius:5px;position:relative><div tabindex=0 style=padding:3px;cursor:pointer onmouseup=gotoDevice(\'' + node._id + '\',11,null,event) onkeypress="if (event.key==\'Enter\') gotoDevice(\'' + node._id + '\',11,null,event)">' + devNotify;
  4800. //r += '<input class="' + node.meshid + ' DeviceCheckbox" onclick=p1updateInfo() value=devid_' + node._id + ' type=checkbox style=float:left>';
  4801. r += '<div class="j' + icon + '" style=width:16px;float:left></div>&nbsp;' + name + '</div>';
  4802. r += '<span onmouseup=gotoDevice(\'' + node._id + '\',null,null,event)></span><div id=xkvmid_' + node._id.split('/')[2] + '><div id=skvmid_' + node._id.split('/')[2] + ' tabindex=0 style="position:absolute;color:white;left:5px;top:27px;text-shadow:0px 0px 5px #000;z-index:10;cursor:default" onclick=toggleKvmDevice(\'' + node._id + '\') onkeypress="if (event.key==\'Enter\') toggleKvmDevice(\'' + node._id + '\')">' + "Disconnected" + '</div></div>';
  4803. r += '</div>';
  4804. return r;
  4805. }
  4806. }
  4807. // Return HTML with a list of MeshCentral Router links
  4808. function getShortRouterLinks(node) {
  4809. var x = '', meshrights = GetNodeRights(node);
  4810. // RDP link, show this link only of the remote machine is Windows.
  4811. if ((((node.conn & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((meshrights & 8) != 0) && (node.agent.id != 14)) {
  4812. if (webRelayPort != 0) {
  4813. x += '<a href=# onclick=p10WebRouter("' + node._id + '",1,' + (node.httpport ? node.httpport : 80) + ')>' + "HTTP" + ((node.httpport && (node.httpport != 80)) ? '/' + node.httpport : '') + '</a>&nbsp;';
  4814. x += '<a href=# onclick=p10WebRouter("' + node._id + '",2,' + (node.httpsport ? node.httpsport : 443) + ')>' + "HTTPS" + ((node.httspport && (node.httpsport != 443)) ? '/' + node.httpsport : '') + '</a>&nbsp;';
  4815. }
  4816. if ((node.agent.id > 0) && (node.agent.id < 5)) {
  4817. if (navigator.platform.toLowerCase() == 'win32') {
  4818. if ((serverinfo.devicemeshrouterlinks == null) || (serverinfo.devicemeshrouterlinks.rdp != false)) {
  4819. x += '<a href=# cmenu=altPortContextMenu id=rdpMCRouterLink onclick=p10MCRouter("' + node._id + '",3)>' + "RDP" + '</a>&nbsp;';
  4820. }
  4821. }
  4822. }
  4823. if (node.agent.id > 4) {
  4824. if ((navigator.platform.toLowerCase() == 'win32') || (navigator.platform.toLowerCase() == 'macintel')) {
  4825. if ((serverinfo.devicemeshrouterlinks == null) || (serverinfo.devicemeshrouterlinks.ssh != false)) {
  4826. x += '<a href=# onclick=p10MCRouter("' + node._id + '",4,' + (node.sshport ? node.sshport : 22) + ')>' + "SSH" + '</a>&nbsp;';
  4827. }
  4828. }
  4829. if (navigator.platform.toLowerCase() == 'win32') {
  4830. if ((serverinfo.devicemeshrouterlinks == null) || (serverinfo.devicemeshrouterlinks.scp != false)) {
  4831. x += '<a href=# onclick=p10MCRouter("' + node._id + '",5,' + (node.sshport ? node.sshport : 22) + ')>' + "SCP" + '</a>&nbsp;';
  4832. }
  4833. }
  4834. }
  4835. if ((navigator.platform.toLowerCase() == 'win32') || (navigator.platform.toLowerCase() == 'macintel')) {
  4836. if ((serverinfo.devicemeshrouterlinks != null) && (Array.isArray(serverinfo.devicemeshrouterlinks.extralinks))) {
  4837. for (var i in serverinfo.devicemeshrouterlinks.extralinks) {
  4838. var r = serverinfo.devicemeshrouterlinks.extralinks[i], p = '\"' + r.protocol + '\"';
  4839. if (doesDeviceMatchFilterTags(node, r.filter)) {
  4840. if ((node.mtype == 3) && ((r.protocol == 'mcrdesktop') || (r.protocol == 'mcrfiles'))) continue;
  4841. if (typeof r.protocol == 'number') { p = r.protocol; } else if (r.protocol == 'http') { p = 1; } else if (r.protocol == 'https') { p = 2; } else if (r.protocol == 'rdp') { p = 3; } else if (r.protocol == 'ssh') { p = 4; } else if (r.protocol == 'scp') { p = 5; } else if (r.protocol == 'mcrdesktop') { p = 6; } else if (r.protocol == 'mcrfiles') { p = 7; }
  4842. x += '<a href=# onclick=p10MCRouter("' + node._id + '",' + p + ',' + r.port + (r.ip?(',\"' + r.ip + '\"'):',null') + ',' + (r.localport?r.localport:0) + ')>' + r.name + '</a>&nbsp;';
  4843. }
  4844. }
  4845. }
  4846. }
  4847. }
  4848. return x;
  4849. }
  4850. // Check if this device matches any of the given filters
  4851. function doesDeviceMatchFilterTags(node, filter) {
  4852. if (filter == null) return true; // No filters, every device matches.
  4853. if (!Array.isArray(filter)) return false; // Bad filter
  4854. if (filter.indexOf(node.meshid) >= 0) return true; // Device group match
  4855. if (filter.indexOf(node._id) >= 0) return true; // Nodeid match
  4856. if (Array.isArray(node.tags)) { for (var i in node.tags) { if (filter.indexOf('tag:' + node.tags[i]) >= 0) return true; } } // Tag match
  4857. return false;
  4858. }
  4859. // Show device help requests
  4860. function showDeviceHelpRequests(nodeid, force, e) {
  4861. if (e) haltEvent(e);
  4862. if (xxdialogMode && !force) return false;
  4863. var node = null, x = '';
  4864. if (nodeid == null) { node = currentNode; } else { node = getNodeFromId(nodeid); }
  4865. if ((node == null) || (node.sessions == null)) { setDialogMode(0); return false; }
  4866. if (node.sessions.help != null) { for (var j in node.sessions.help) { x += '<div style="background-color:#CCC;padding:6px;border-radius:6px"><a style="float:right" onclick=closeDeviceHelpRequest("' + encodeURIComponentEx(j) + '","' + encodeURIComponentEx(node._id) + '")>' + "Dismiss" + '</a><div style=margin-bottom:6px><b>' + EscapeHtml(j) + '</b></div><div style=margin-bottom:6px>' + EscapeHtml(node.sessions.help[j]) + '</div></div>'; } }
  4867. if (x != '') { setDialogMode(2, "Help Requests" + ' - ' + EscapeHtml(node.name), 1, null, x, 'HELPREQ-' + node._id); } else { setDialogMode(0); }
  4868. return false;
  4869. }
  4870. function closeDeviceHelpRequest(appid, nodeid) {
  4871. setDialogMode(0);
  4872. meshserver.send({ action: 'msg', type: 'localapp', nodeid: decodeURIComponent(nodeid), appid: decodeURIComponent(appid), value: { cmd: 'cancelhelp' } });
  4873. }
  4874. // Show currently active sessions on this device
  4875. function showDeviceSessions(nodeid, force, e) {
  4876. if (e) haltEvent(e);
  4877. if (xxdialogMode && !force) return false;
  4878. var node = null, x = '';
  4879. if (nodeid == null) { node = currentNode; } else { node = getNodeFromId(nodeid); }
  4880. if ((node == null) || (node.sessions == null)) { setDialogMode(0); return false; }
  4881. for (var i in node.sessions) {
  4882. if ((i == 'kvm') && (node.sessions.multidesk == null)) {
  4883. x += '<u>' + "Remote Desktop" + '</u>';
  4884. for (var j in node.sessions.kvm) {
  4885. if (j.startsWith('user/')) {
  4886. var trash = '';
  4887. 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>'; }
  4888. x += addHtmlValue2(getUserName(j), ((node.sessions.kvm[j] == 1)?"1 session":nobreak(format("{0} sessions", node.sessions.kvm[j]))) + trash);
  4889. } else if (j == 'busy') {
  4890. x += addHtmlValue2("Device is busy", ((node.sessions.kvm[j] == 1)?"1 session":nobreak(format("{0} sessions", node.sessions.kvm[j]))));
  4891. }
  4892. }
  4893. } else if (i == 'multidesk') {
  4894. x += '<u>' + "Remote Desktop" + '</u>';
  4895. for (var j in node.sessions.multidesk) {
  4896. var trash = '';
  4897. 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>'; }
  4898. x += addHtmlValue2(getUserName(j), ((node.sessions.multidesk[j] == 1)?"1 session":nobreak(format("{0} sessions", node.sessions.multidesk[j]))) + trash);
  4899. }
  4900. } else if (i == 'terminal') {
  4901. x += '<u>' + "Terminal" + '</u>';
  4902. for (var j in node.sessions.terminal) {
  4903. var trash = '';
  4904. 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>'; }
  4905. x += addHtmlValue2(getUserName(j), ((node.sessions.terminal[j] == 1)?"1 session":nobreak(format("{0} sessions", node.sessions.terminal[j]))) + trash);
  4906. }
  4907. } else if (i == 'files') {
  4908. x += '<u>' + "Files" + '</u>';
  4909. for (var j in node.sessions.files) {
  4910. var trash = '';
  4911. 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>'; }
  4912. x += addHtmlValue2(getUserName(j), ((node.sessions.files[j] == 1)?"1 session":nobreak(format("{0} sessions", node.sessions.files[j]))) + trash);
  4913. }
  4914. } else if (i == 'tcp') {
  4915. x += '<u>' + "TCP Routing" + '</u>';
  4916. for (var j in node.sessions.tcp) {
  4917. var trash = '';
  4918. 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>'; }
  4919. x += addHtmlValue2(getUserName(j), ((node.sessions.tcp[j] == 1)?"1 session":nobreak(format("{0} sessions", node.sessions.tcp[j]))) + trash);
  4920. }
  4921. } else if (i == 'udp') {
  4922. x += '<u>' + "UDP Routing" + '</u>';
  4923. for (var j in node.sessions.udp) {
  4924. var trash = '';
  4925. 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>'; }
  4926. x += addHtmlValue2(getUserName(j), ((node.sessions.udp[j] == 1)?"1 session":nobreak(format("{0} sessions", node.sessions.udp[j]))) + trash);
  4927. }
  4928. }
  4929. }
  4930. if (x != '') { setDialogMode(2, "Sessions" + ' - ' + EscapeHtml(node.name), 1, null, x, 'SESSIONS-' + node._id); } else { setDialogMode(0); }
  4931. return false;
  4932. }
  4933. function endDeviceSession(protocol, nodeid, userid) {
  4934. var userIdSplit = decodeURIComponent(userid).split('/'), uid = userIdSplit[0] + '/' + userIdSplit[1] + '/' + userIdSplit[2], guestname = null;
  4935. if ((userIdSplit.length == 4) && (userIdSplit[3].startsWith('guest:'))) { guestname = atob(userIdSplit[3].substring(6)); }
  4936. if (protocol == 'multidesk') {
  4937. meshserver.send({ action: 'endDesktopMultiplex', nodeid: decodeURIComponent(nodeid), xuserid: uid, guestname, guestname });
  4938. } else {
  4939. meshserver.send({ action: 'msg', type: 'endtunnel', nodeid: decodeURIComponent(nodeid), xuserid: uid, guestname, guestname, protocol: protocol });
  4940. }
  4941. }
  4942. // Show currently active sessions on this device
  4943. function showDeviceMessages(nodeid, force, e) {
  4944. if (e) haltEvent(e);
  4945. if (xxdialogMode && !force) return false;
  4946. var node = null, x = '<div style=max-height:200px;overflow-y:auto>', count = 0;
  4947. if (nodeid == null) { node = currentNode; } else { node = getNodeFromId(nodeid); }
  4948. if ((node == null) || (node.sessions == null) || (node.sessions.msg == null)) { setDialogMode(0); return false; }
  4949. for (var i in node.sessions.msg) {
  4950. var msg = i, icon = 5;
  4951. if (typeof node.sessions.msg[i].msg == 'string') { msg = node.sessions.msg[i].msg; }
  4952. if ((typeof node.sessions.msg[i].msgid == 'number') && (eventsMessageId[node.sessions.msg[i].msgid] != null)) { msg = eventsMessageId[node.sessions.msg[i].msgid]; }
  4953. if (typeof node.sessions.msg[i].icon == 'number') { icon = node.sessions.msg[i].icon; }
  4954. if ((icon < 1) || (icon > 9)) { icon = 5; }
  4955. x += '<table style=width:96%><td style=width:24px><div class=NotifyIconSmall' + icon + '></div><td><div style="border-radius:5px;background-color:#BBB;width:100%;padding:8px">' + EscapeHtml(msg) + '</div></table>';
  4956. count++;
  4957. }
  4958. x += '</div>';
  4959. if (count > 0) setDialogMode(2, "Agent Messages" + ' - ' + EscapeHtml(node.name), 1, null, x, 'MESSAGES-' + node._id);
  4960. return false;
  4961. }
  4962. function toggleCollapseGroup(id, id2, type) {
  4963. var x;
  4964. if (type == 2) {
  4965. // Table rows collapse
  4966. var xrows = document.getElementsByName('xxdevice2');
  4967. if (xrows.length == 0) return;
  4968. var rows = [];
  4969. for (var i = 0; i < xrows.length; i++) { if (xrows[i].attributes.colname.value.substring(7) == id2) { rows.push(xrows[i]); } }
  4970. var x = (rows[0].style['display'] == 'none');
  4971. if (x) { delete CollapsedGroups[id2]; } else { CollapsedGroups[id2] = true; }
  4972. for (var i = 0; i < rows.length; i++) { rows[i].style['display'] = (x ? '' : 'none'); }
  4973. } else {
  4974. // Simple DIV collapse
  4975. x = (QS('DevxCol' + id)['display'] == 'none');
  4976. if (x) { delete CollapsedGroups[id2]; } else { CollapsedGroups[id2] = true; }
  4977. QV('DevxCol' + id, x);
  4978. }
  4979. Q('DevxColImg' + id).src = x?'images/c2.png':'images/c1.png';
  4980. putstore('_collapse', JSON.stringify(CollapsedGroups));
  4981. updateCollapseAllButton();
  4982. onDevicesScrollEx();
  4983. onDevicesScrollEx(); // TODO: Not sure why, but second call is needed for proper update
  4984. }
  4985. function toggleKvmDevice(node) {
  4986. if (typeof node == 'string') { node = getNodeFromId(node); } // Convert nodeid to node if needed
  4987. var rights = GetNodeRights(node);
  4988. if ((rights & 8) || (rights & 256)) { // Requires remote control rights or desktop view only rights
  4989. //var conn = 0;
  4990. //if ((node.conn & 1) != 0) { conn = 1; } else if ((node.conn & 6) != 0) { conn = 2; } // Check what type of connect we can do (Agent vs AMT)
  4991. if (node.conn & 1) { connectMultiDesktop(node, 1); }
  4992. }
  4993. }
  4994. function getUserShortStr(node) {
  4995. if (node == null || node.users == null || (!Array.isArray(node.users)) || node.users.length == 0) return '';
  4996. if (node.users.length > 1) { return '<span title="' + EscapeHtml(node.users.join(', ')) + '">' + nobreak(format("{0} users", node.users.length)) + '</span>'; }
  4997. var u = node.users[0], su = u, i = u.indexOf('\\');
  4998. if (i > 0) { su = u.substring(i + 1); }
  4999. su = EscapeHtml(su);
  5000. if (su.length > 15) { su = su.substring(0, 14) + '&#8230;'; }
  5001. if (node.lusers && node.lusers.length > 0) {
  5002. return addKeyLinkConditional(su, EscapeHtml(u) + ' (' + "Locked" + ')', (node.lusers && node.lusers.indexOf(u) >= 0));
  5003. } else {
  5004. return '<span title="' + EscapeHtml(u) + '">' + su + '</span>';
  5005. }
  5006. }
  5007. function autoConnectDesktops() { if (Q('autoConnectDesktopCheckbox').checked == true) { connectAllKvmFunction(); } }
  5008. function connectAllKvmFunction(force) {
  5009. if (xxdialogMode) return false;
  5010. if (force !== true) { // We need to count how many devices will need to be connected, if it's a lot, prompt first.
  5011. var count = 0;
  5012. for (var i in nodes) {
  5013. var node = nodes[i], nodeid = nodes[i]._id;
  5014. if ((multiDesktop[nodeid] == null) && ((Object.keys(checkedNodeids).length == 0) || checkedNodeids[nodeid])) {
  5015. var rights = GetNodeRights(node);
  5016. if ((rights & 8) || (rights & 256)) { // Requires remote control rights or desktop view only rights
  5017. //var conn = 0;
  5018. //if ((node.conn & 1) != 0) { conn = 1; } else if ((node.conn & 6) != 0) { conn = 2; } // Check what type of connect we can do (Agent vs AMT)
  5019. if ((node.conn & 1) && (node.v == true)) { count++; }
  5020. }
  5021. }
  5022. }
  5023. if (count > 8) { setDialogMode(2, "Connect All", 3, function() { connectAllKvmFunction(true); }, format("Are you sure you want to connect to {0} devices?", count)); return; }
  5024. }
  5025. // Perform connect all
  5026. for (var i in nodes) {
  5027. if ((nodes[i].v == true) && (multiDesktop[nodes[i]._id] == null) && ((Object.keys(checkedNodeids).length == 0) || checkedNodeids[nodes[i]._id])) {
  5028. toggleKvmDevice(nodes[i]._id);
  5029. }
  5030. }
  5031. }
  5032. function disconnectAllKvmFunction() { if (xxdialogMode) return false; for (var nodeid in multiDesktop) { multiDesktop[nodeid].Stop(); } multiDesktop = {}; }
  5033. function onMultiDesktopStateChange(desk, state) { try { QH('skvmid_' + desk.shortid, ["Disconnected", "Connecting...", "Setup...", '', ''][state]); } catch (ex) {} }
  5034. function onDeviceSearchChanged(e) {
  5035. var url = new URL(window.location.href);
  5036. if ((e != null) && ((e.target.id == 'SearchInput') || (e.target.id == 'KvmSearchInput'))){ // search box changed or cleared
  5037. if (e.target.id == 'SearchInput') {
  5038. Q('KvmSearchInput').value = Q('SearchInput').value;
  5039. } else {
  5040. Q('SearchInput').value = Q('KvmSearchInput').value;
  5041. }
  5042. if (e.target.value != '') {
  5043. url.searchParams.set('filter', e.target.value);
  5044. if (urlargs.filter) { urlargs.filter = e.target.value; }
  5045. } else {
  5046. url.searchParams.delete('filter');
  5047. if (urlargs.filter) { delete urlargs.filter; }
  5048. }
  5049. } else if ((e != null) && (e.target.id == 'DevFilterSelect')){ // devfilter box changed
  5050. // DO NOTHING
  5051. } else {
  5052. url.searchParams.delete('filter');
  5053. if (urlargs.filter) { delete urlargs.filter; }
  5054. }
  5055. if (((features & 0x10000000) == 0) && (xxcurrentView > 0)) {
  5056. try { window.history.replaceState({}, document.title, decodeURIComponent(url.toString())); } catch(ex) { }
  5057. }
  5058. mainUpdate(5);
  5059. }
  5060. function showMultiDesktopSettings() {
  5061. QV('td7amtkvm', false);
  5062. QV('td7meshkvm', true);
  5063. QV('td7rdpkvm', false);
  5064. QV('d7desktopOtherSettings', false);
  5065. d7bitmapquality.value = multidesktopsettings.quality;
  5066. d7bitmapscaling.value = multidesktopsettings.scaling;
  5067. d7encoding.value = multidesktopsettings.agentencoding;
  5068. if (multidesktopsettings.framerate) { d7framelimiter.value = multidesktopsettings.framerate; } else { d7framelimiter.value = 100; }
  5069. changeDesktopSettingsTab(null, 'd7meshkvm');
  5070. setDialogMode(7, "Remote Desktop Settings", 3, showMultiDesktopSettingsChanged);
  5071. }
  5072. function showMultiDesktopSettingsChanged() {
  5073. multidesktopsettings.quality = d7bitmapquality.value;
  5074. multidesktopsettings.scaling = d7bitmapscaling.value;
  5075. multidesktopsettings.framerate = d7framelimiter.value;
  5076. multidesktopsettings.agentencoding = d7encoding.value;
  5077. localStorage.setItem('multidesktopsettings', JSON.stringify(multidesktopsettings));
  5078. // Make changes to all current connections
  5079. for (var i in multiDesktop) { multiDesktop[i].m.SendCompressionLevel(multidesktopsettings.agentencoding, multidesktopsettings.quality, multidesktopsettings.scaling, multidesktopsettings.framerate); }
  5080. }
  5081. function connectMultiDesktop(node, contype) {
  5082. var nodeid = node._id, shortid = nodeid.split('/')[2];
  5083. var desk = multiDesktop[nodeid];
  5084. if (desk == null) {
  5085. if (Q('kvmid_' + shortid) == null) return; // Check if this device is being displayed, if not, exit now.
  5086. if (contype == 2) {
  5087. // Setup the Intel AMT remote desktop
  5088. if ((node.intelamt.user == null) || (node.intelamt.user == '')) { return; }
  5089. desk = CreateAmtRedirect(CreateAmtRemoteDesktop('kvmid_' + shortid), authCookie);
  5090. desk.shortid = shortid;
  5091. //desk.debugmode = debugmode;
  5092. desk.onStateChanged = onMultiDesktopStateChange;
  5093. desk.m.bpp = 1;
  5094. desk.m.useZRLE = true;
  5095. desk.m.showmouse = true;
  5096. desk.m.onKvmData = function (data) { console.log('KVM Data received in multi-desktop mode, this is not supported.'); }; // KVM Data Channel not supported in multi-desktop right now.
  5097. desk.m.onScreenSizeChange = mdeskAdjust; // Multi-Desktop Adjust
  5098. desk.Start(nodeid, 16994, '*', '*', 0);
  5099. desk.contype = 2;
  5100. multiDesktop[nodeid] = desk;
  5101. } else if (contype == 1) {
  5102. // Setup the Mesh Agent remote desktop
  5103. desk = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('kvmid_' + shortid), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
  5104. desk.m.UseExtendedKeyFlag = (node.agent.id < 5); // Only use extended keys on Windows agents for now
  5105. desk.m.mouseCursorActive(xxcurrentView == 11);
  5106. desk.shortid = shortid;
  5107. desk.attemptWebRTC = attemptWebRTC;
  5108. desk.webrtcconfig = webrtcconfiguration;
  5109. desk.onStateChanged = onMultiDesktopStateChange;
  5110. //desk.onConsoleMessageChange = function () { console.log('CONSOLEMSG:', desk.consoleMessage); }
  5111. desk.m.ImageType = multidesktopsettings.agentencoding; // Send 4 if WebP is supported, otherwise send 1 for JPEG.
  5112. desk.m.CompressionLevel = multidesktopsettings.quality;
  5113. desk.m.ScalingLevel = multidesktopsettings.scaling;
  5114. if (multidesktopsettings.framerate) { desk.m.FrameRateTimer = multidesktopsettings.framerate; }
  5115. if (multidesktopsettings.swapmouse) { desk.m.SwapMouse = multidesktopsettings.swapmouse; }
  5116. if (multidesktopsettings.rmw) { desk.m.ReverseMouseWheel = multidesktopsettings.rmw; }
  5117. if (multidesktopsettings.remotekeymap == true) { desk.m.remoteKeyMap = multidesktopsettings.remotekeymap; }
  5118. //desk.m.onDisplayinfo = deskDisplayInfo;
  5119. desk.m.onScreenSizeChange = mdeskAdjust; // Multi-Desktop Adjust
  5120. //if (debugmode > 0) { desk.m.onScreenSizeChange = mdeskAdjust; }
  5121. desk.Start(nodeid);
  5122. desk.contype = 1;
  5123. multiDesktop[nodeid] = desk;
  5124. }
  5125. } else {
  5126. // Disconnect and clean up the remote desktop
  5127. desk.Stop();
  5128. delete multiDesktop[nodeid];
  5129. }
  5130. }
  5131. function getMeshActions(mesh, meshrights) {
  5132. if ((meshrights & 4) == 0) return '';
  5133. var r = '';
  5134. if (mesh.mtype == 1) {
  5135. if ((features & 1) == 0) { // If not WAN-Only
  5136. r += ' <a href=# style=cursor:pointer;font-size:small title="' + "Add a new Intel&reg; AMT computer that is located on the local network." + '" onclick=\'return addDeviceToMesh("' + mesh._id + '")\'>' + "Add Local" + '</a>';
  5137. r += ' <a href=# style=cursor:pointer;font-size:small title="' + "Add a new Intel&reg; AMT computer by scanning the local network." + '" onclick=\'return addAmtScanToMesh("' + mesh._id + '")\'>' + "Scan Network" + '</a>';
  5138. }
  5139. if (mesh.amt && (mesh.amt.type > 0)) { // CCM Deactivate, CCM or ACM activation, Full Automatic
  5140. r += ' <a href=# style=cursor:pointer;font-size:small title="' + "Perform Intel&reg; AMT activation and configuration." + '" onclick=\'return showAmtSetup("' + mesh._id + '")\'>' + "Setup" + '</a>';
  5141. }
  5142. }
  5143. if ((mesh.mtype == 2) && ((userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.siteadmin & 4096) == 0))) { // Agent device group
  5144. r += ' <a href=# style=cursor:pointer;font-size:small title="' + "Add a new computer to this device group by installing the mesh agent." + '" onclick=\'return addAgentToMesh("' + mesh._id + '")\'>' + "Add Agent" + '</a>';
  5145. if ((features & 2) == 0) { r += ' <a href=# style=cursor:pointer;font-size:small title="' + "Invite someone to install the mesh agent on this device group." + '" onclick=\'return inviteAgentToMesh("' + mesh._id + '")\'>' + "Invite" + '</a>'; }
  5146. }
  5147. if (mesh.mtype == 3) { // Local device group
  5148. r += ' <a href=# style=cursor:pointer;font-size:small title="' + "Add device located on the local network." + '" onclick=\'return addLocalDeviceToMesh("' + mesh._id + '")\'>' + "Add Device" + '</a>';
  5149. }
  5150. //if (mesh.amt && (mesh.amt.type > 2)) { // ACM activation or Full Automatic
  5151. // r += ' <a href=# style=cursor:pointer;font-size:small title="' + "Switch Intel AMT to Admin Control Mode (ACM)." + '" onclick=\'return showAmtAcmSetup()\'>' + "ACM" + '</a>';
  5152. //}
  5153. return r;
  5154. }
  5155. function addLocalDeviceToMesh(meshid) {
  5156. if (xxdialogMode) return false;
  5157. var mesh = meshes[meshid];
  5158. var x = format("Add a local device to device group \"{0}\".", EscapeHtml(mesh.name)) + '<br /><br />';
  5159. x += addHtmlValue("Device Name", '<input id=dp1devicename style=width:230px maxlength=32 autocomplete=off onchange=validateLocalDeviceToMesh() onkeyup=validateLocalDeviceToMesh() />');
  5160. x += addHtmlValue("Hostname", '<input id=dp1hostname style=width:230px maxlength=32 autocomplete=off placeholder="' + "Same as device name" + '" onchange=validateLocalDeviceToMesh() onkeyup=validateLocalDeviceToMesh() />');
  5161. x += addHtmlValue("Type", '<select id=dp1type style=width:236px><option value=4>' + "Windows (RDP)" + '</option><option value=6>' + "Linux (SSH/SCP/VNC)" + '</option><option value=29>' + "macOS (SSH/SCP/VNC)" + '</option></select>');
  5162. setDialogMode(2, "Add local device", 3, addLocalDeviceToMeshEx, x, meshid);
  5163. validateLocalDeviceToMesh();
  5164. Q('dp1devicename').focus();
  5165. }
  5166. function validateLocalDeviceToMesh() {
  5167. QE('idx_dlgOkButton', Q('dp1devicename').value.length > 0);
  5168. }
  5169. function addLocalDeviceToMeshEx(button, meshid) {
  5170. var host = Q('dp1hostname').value;
  5171. if (host == '') host = Q('dp1devicename').value;
  5172. meshserver.send({ action: 'addlocaldevice', meshid: meshid, devicename: Q('dp1devicename').value, hostname: host, type: parseInt(Q('dp1type').value) });
  5173. }
  5174. function addDeviceToMesh(meshid) {
  5175. if (xxdialogMode) return false;
  5176. var mesh = meshes[meshid];
  5177. var x = format("Add a new Intel&reg; AMT device to device group \"{0}\".", EscapeHtml(mesh.name)) + '<br /><br />';
  5178. x += addHtmlValue("Device Name", '<input id=dp1devicename style=width:230px maxlength=32 autocomplete=off onchange=validateDeviceToMesh() onkeyup=validateDeviceToMesh() />');
  5179. x += addHtmlValue("Hostname", '<input id=dp1hostname style=width:230px maxlength=32 autocomplete=off placeholder="' + "Same as device name" + '" onchange=validateDeviceToMesh() onkeyup=validateDeviceToMesh() />');
  5180. x += addHtmlValue("Username", '<input id=dp1username style=width:230px maxlength=32 autocomplete=off placeholder="' + "admin" + '" onchange=validateDeviceToMesh() onkeyup=validateDeviceToMesh() />');
  5181. x += addHtmlValue("Password", '<input id=dp1password type=password style=width:230px autocomplete=off maxlength=32 onchange=validateDeviceToMesh() onkeyup=validateDeviceToMesh() />');
  5182. x += addHtmlValue("Security", '<select id=dp1tls style=width:236px><option value=0>' + "No TLS security" + '</option><option value=1>' + "TLS security required" + '</option></select>');
  5183. setDialogMode(2, "Add Intel&reg; AMT device", 3, addDeviceToMeshEx, x, meshid);
  5184. validateDeviceToMesh();
  5185. Q('dp1devicename').focus();
  5186. return false;
  5187. }
  5188. // Intel AMT device import for a device group
  5189. // function showAmtImport(meshid) {
  5190. // if (xxdialogMode) return false;
  5191. // var x = '<form method=post enctype=multipart/form-data action=amtimport.ashx target=fileUploadFrame><input type=text name=link style=display:none id=amtImportDevGroup value="' + meshid + '" /><input type=file name=files id=amtImportInput style=width:100% onchange="p5updateUploadDialogOk(\'p5uploadinput\')" /><input type=hidden name=authCookie value=' + authCookie + ' /><input type=submit id=p5loginSubmit style=display:none /></form>';
  5192. // setDialogMode(2, "Import Intel AMT devices", 3, p5uploadFileEx, x);
  5193. // p5updateUploadDialogOk('amtImportInput');
  5194. // }
  5195. function showAmtImport(meshid) {
  5196. if (xxdialogMode) return;
  5197. var x = "Create many Intel&reg; AMT device at once by importing a JSON file with the following format:" + '<br /><pre>[\r\n {\r\n "fqdn":"mycomputer.com",\r\n "uuid":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",\r\n "version":"12.0.1",\r\n "username":"admin",\r\n "password":"password",\r\n "tls":true\r\n }\r\n]</pre><input style=width:370px type=file id=d4importFile accept=".json" onchange=showAmtImportValidate() />';
  5198. setDialogMode(2, "Intel&reg; AMT Device Import", 3, showAmtImportEx, x, meshid);
  5199. QE('idx_dlgOkButton', false);
  5200. }
  5201. function showAmtImportValidate() {
  5202. QE('idx_dlgOkButton', Q('d4importFile').value != null);
  5203. }
  5204. function showAmtImportEx(b, meshid) {
  5205. var fr = new FileReader();
  5206. fr.onload = function (r) {
  5207. var j = null;
  5208. try { j = JSON.parse(r.target.result); } catch (ex) { setDialogMode(2, "Intel&reg; AMT Device Import", 1, null, format("Invalid JSON file: {0}.", ex)); return; }
  5209. if (j != null) { meshserver.send({ action: 'importamtdevices', meshid: meshid, amtdevices: j }); } else { setDialogMode(2, "Intel&reg; AMT Device Import", 1, null, "Invalid JSON file format."); }
  5210. };
  5211. fr.readAsText(Q('d4importFile').files[0]);
  5212. }
  5213. // Intel AMT activation and configuration for agentless device groups
  5214. function showAmtSetup(meshid) {
  5215. if (xxdialogMode) return false;
  5216. var servername = serverinfo.name, mesh = meshes[meshid];
  5217. 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.
  5218. var url, domainUrlNoSlash = domainUrl.substring(0, domainUrl.length - 1);
  5219. if (serverinfo.https == true) {
  5220. var portStr = (serverinfo.port == 443) ? '' : (':' + serverinfo.port);
  5221. url = 'wss://' + servername + portStr + domainUrl;
  5222. } else {
  5223. var portStr = (serverinfo.port == 80) ? '' : (':' + serverinfo.port);
  5224. url = 'ws://' + servername + portStr + domainUrl;
  5225. }
  5226. var x = format("Add, activate and configure Intel&reg; AMT to group \"{0}\" by periodically running MeshCmd as administrator on the remote device.", EscapeHtml(mesh.name)) + '<br /><br />';
  5227. x += '<textarea readonly=readonly style=width:100%;resize:none;height:100px;overflow:auto;font-size:12px readonly>meshcmd amtconfig --url ' + url + 'apf.ashx --id \'' + meshid.split('/')[2] + '\' --serverhttpshash ' + serverinfo.tlshash + '</textarea>';
  5228. if (serverinfo.amtAcmFqdn != null) {
  5229. x += ('<div style=margin-top:8px>' + "For ACM activation, Intel&reg; AMT will need to be set to the following trusted FQDN:" + ' <b>' + serverinfo.amtAcmFqdn.join(', ') + '</b></div>');
  5230. }
  5231. setDialogMode(2, "Intel&reg; AMT setup", 9, null, x);
  5232. Q('idx_dlgOkButton').focus();
  5233. return false;
  5234. }
  5235. // Intel AMT ACM activation using setup.bin
  5236. function showAmtAcmSetup() {
  5237. if (xxdialogMode) return false;
  5238. var x = '<table><tr><td><img src=images/usbkey70.png height=70 width=31 style=margin-left:4px;margin-right:8px><td><div>' + "Activate Intel&reg; AMT in Admin Control Mode (ACM) using a FAT formated USB key. Place setup.bin on it and boot one or more computers with this key." + '</div><div style=margin-top:6px>' + "Start by entering the old and new MBEx password. " + '</div></table>';
  5239. x += addHtmlValue("Old Password", '<input id=dp1password0 type=password style=width:230px autocomplete=off maxlength=32 onchange=validateAmtAcmSetupEx() onkeyup=validateAmtAcmSetupEx() />');
  5240. x += addHtmlValue("New Password*", '<input id=dp1password1 type=password style=width:230px autocomplete=off maxlength=32 onchange=validateAmtAcmSetupEx() onkeyup=validateAmtAcmSetupEx() />');
  5241. x += addHtmlValue("New Password*", '<input id=dp1password2 type=password style=width:230px autocomplete=off maxlength=32 onchange=validateAmtAcmSetupEx() onkeyup=validateAmtAcmSetupEx() />');
  5242. if ((features2 & 0x00000020) && (currentMesh.mtype == 1) && (serverinfo.amtProvServerMeshId == currentMesh._id)) { x += '<label><input id=dp1lanprov type=checkbox /> ' + "Use for bare-metal LAN activation." + '</label>'; } // Intel AMT LAN provisioning server is active.
  5243. x += '<div><span id=dp10passNotify style="font-size:10px"> ' + "* 8-16 characters, 1 upper, 1 lower, 1 numeric, 1 non-alpha numeric." + '</span></div>';
  5244. setDialogMode(2, "Intel&reg; AMT ACM", 3, showAmtAcmSetupEx, x);
  5245. Q('dp1password0').focus();
  5246. validateAmtAcmSetupEx();
  5247. }
  5248. function validateAmtAcmSetupEx() {
  5249. var p0 = Q('dp1password0').value, p1 = Q('dp1password1').value, p2 = Q('dp1password2').value, ok = true;
  5250. if ((p0 != 'admin') && (checkPasswordRequirements(p0, { min: 8, max:16, numeric: 1, lower: 1, upper: 1, nonalpha: 1 }) == false)) { ok = false; }
  5251. if ((p1 != p2) || (checkPasswordRequirements(p1, { min: 8, max:16, numeric: 1, lower: 1, upper: 1, nonalpha: 1 }) == false)) { ok = false; }
  5252. QE('idx_dlgOkButton', ok);
  5253. }
  5254. function showAmtAcmSetupEx() {
  5255. var baremetal = ((features2 & 0x00000020) && (currentMesh.mtype == 1) && (serverinfo.amtProvServerMeshId == currentMesh._id) && (Q('dp1lanprov').checked));
  5256. meshserver.send({ action: 'amtsetupbin', oldmebxpass: Q('dp1password0').value, newmebxpass: Q('dp1password1').value, baremetal: baremetal });
  5257. }
  5258. // Display the Intel AMT scanning dialog box
  5259. function addAmtScanToMesh(meshid) {
  5260. if (xxdialogMode) return false;
  5261. var x = "Enter a range of IP addresses to scan for Intel&reg; AMT devices." + '<br /><br />';
  5262. var amtscanoptions = '';//decodeURIComponent('{{{amtscanoptions}}}').split(',');
  5263. if (amtscanoptions != '') {
  5264. x += '<datalist id=iprangelist>';
  5265. for (var i in amtscanoptions) { x += '<option>' + amtscanoptions[i] + '</option>'; }
  5266. x += '</datalist>';
  5267. x += addHtmlValue("IP Range", '<table style=width:250px><tr><td style=width:99%><input id=dp1range list=iprangelist style=width:90% placeholder="192.168.1.0/24" onkeyup=addAmtScanToMeshKeyUp(event) onselect=addAmtScanToMeshKeyUp() onclick=addAmtScanToMeshKeyUp() /><td style=width:1%><input id=dp1rangebutton type=button value="' + "Scan" + '" onclick=addAmtScanToMeshButton()></input></tr></table>');
  5268. } else {
  5269. x += addHtmlValue("IP Range", '<table style=width:250px><tr><td style=width:99%><input id=dp1range style=width:100% list=iprangelist value="192.168.1.0/24" onkeyup=addAmtScanToMeshKeyUp(event) /><td style=width:1%><input id=dp1rangebutton type=button value="' + "Scan" + '" onclick=addAmtScanToMeshButton()></input></tr></table>');
  5270. }
  5271. x += '<div id=dp1results style="width:100%;height:200px;background-color:white;border:1px gray solid;overflow-y:scroll"></div>';
  5272. x += '<input type=hidden id=amtScanMeshId value="' + meshid + '" />';
  5273. x += '<div style="padding:10px;margin-bottom:5px"><input id="idx_dlgCancelButton2" type="button" value="' + "Cancel" + '" style="" onclick="dialogclose(0)" /><input id="idx_dlgOkButton2" type="button" value="OK" style="" onclick="dialogclose(1)" /><div><input id="d2scanSelectButton" type="button" value="' + "Select All" + '" onclick="scanSelectAll()" /></div></div>';
  5274. setDialogMode(2, "Scan for Intel&reg; AMT devices", 0, addAmtScanToMeshEx, x, meshid);
  5275. QE('idx_dlgOkButton2', false);
  5276. QE('d2scanSelectButton', false);
  5277. QH('dp1results', '<div style=width:100%;text-align:center;margin-top:12px;color:gray;line-height:1.5>' + "Sample IP range values" + '<br />192.168.0.100<br />192.168.1.0/24<br />192.167.0.1-192.168.0.100</div>');
  5278. focusTextBox('dp1range');
  5279. addAmtScanToMeshKeyUp();
  5280. return false;
  5281. }
  5282. function scanSelectAll() {
  5283. var elements = document.getElementsByClassName('DevScanCheckbox'), checkcount = 0;
  5284. for (var i=0;i<elements.length;i++) { elements[i].checked = true; }
  5285. addAmtScanToMeshCheckbox();
  5286. }
  5287. function addAmtScanToMeshKeyUp(e) {
  5288. QE('dp1rangebutton', (Q('dp1range').value.split('.').length > 3));
  5289. if (e && (e.keyCode == 13)) { haltEvent(e); addAmtScanToMeshButton(); }
  5290. }
  5291. // Called when OK is pressed on the Intel AMT scanning box
  5292. function addAmtScanToMeshEx(button) {
  5293. var elements = document.getElementsByClassName('DevScanCheckbox'), checkcount = 0;
  5294. for (var i=0;i<elements.length;i++) {
  5295. if (elements[i].checked) {
  5296. var ipaddr = elements[i].getAttribute('tag');
  5297. var amtinfo = amtScanResults[ipaddr];
  5298. var name = amtinfo.hostname;
  5299. if (amtinfo.hosttype == 'host') { name = capitalizeFirstLetter(name.split('.')[0]); }
  5300. meshserver.send({ action: 'addamtdevice', meshid: Q('amtScanMeshId').value, devicename: name, hostname: amtinfo.hostname, amtusername: '', amtpassword: '', amttls: amtinfo.tls });
  5301. }
  5302. }
  5303. }
  5304. // If the user presses the "Scan" button on the Intel AMT scanning dialog box, start a scan.
  5305. function addAmtScanToMeshButton() {
  5306. var range = Q('dp1range').value.trim();
  5307. var rangeSplit = range.split(' ');
  5308. if (rangeSplit.length > 1) { range = rangeSplit[rangeSplit.length - 1]; }
  5309. range = range.trim();
  5310. if (range == '') return;
  5311. QE('dp1range', false);
  5312. QE('dp1rangebutton', false);
  5313. QH('dp1results', '<div style=width:100%;text-align:center;margin-top:20px>' + "Scanning..." + '</div><div style=width:100%;text-align:center;margin-top:6px;color:#666>' + range + '</div>');
  5314. xxdialogTag = 'AMTSCAN:' + range;
  5315. meshserver.send({ action: 'scanamtdevice', range: range });
  5316. }
  5317. // Called when a scanned computer is checked or unchecked.
  5318. function addAmtScanToMeshCheckbox() {
  5319. var elements = document.getElementsByClassName('DevScanCheckbox'), checkcount = 0;
  5320. for (var i=0;i<elements.length;i++) { if (elements[i].checked) checkcount++; }
  5321. QE('idx_dlgOkButton2', checkcount > 0);
  5322. }
  5323. // Return true is the input string looks like an email address
  5324. function checkEmail(str) {
  5325. var x = str.split('@');
  5326. var ok = ((x.length == 2) && (x[0].length > 0) && (x[1].split('.').length > 1) && (x[1].length > 2));
  5327. if (ok == true) { var y = x[1].split('.'); for (var i in y) { if (y[i].length == 0) { ok = false; } } }
  5328. return ok;
  5329. }
  5330. function inviteAgentToMesh(meshid) {
  5331. if (xxdialogMode) return false;
  5332. var x = '', mesh = meshes[meshid];
  5333. if (features & 64) {
  5334. x += addHtmlValue("Invitation Type", '<select id=d2InviteType onchange=d2ChangedInviteType() style=width:236px><option value=0>' + "Link invitation" + '</option><option value=1>' + "Email invitation" + '</option></select>') + '<hr />';
  5335. x += '<div id=emailInviteDiv style=display:none>' + format("Invite someone to install the mesh agent. An email with be sent with the link to the mesh agent installation for the \"{0}\" device group.", EscapeHtml(mesh.name)) + '<br /><br />';
  5336. x += addHtmlValue("Name (optional)", '<input id=agentInviteName value="" style=width:230px maxlength=64 />');
  5337. x += addHtmlValue("Email", '<input id=agentInviteEmail style=width:230px placeholder="' + "[email protected]" + '" onkeyup=validateAgentInvite()></input>');
  5338. x += addHtmlValue("Operating System", '<select id=agentInviteNameOs onchange=d2ChangedInviteType() style=width:236px><option value=4>' + "Send installation link" + '</option><option value=0 selected>' + "Any supported" + '</option><option value=1>' + "Windows only" + '</option><option value=3>' + "Apple macOS only" + '</option><option value=2>' + "Linux only" + '</option><option value=5>' + "MeshCentral Assistant" + '</option></select>');
  5339. x += '<div id=d2agentexpirediv>';
  5340. x += addHtmlValue("Link Expiration", '<select id=agentInviteExpire style=width:236px><option value=1>' + "1 hour" + '</option><option value=8>' + "8 hours" + '</option><option value=24>' + "1 day" + '</option><option value=168>' + "1 week" + '</option><option value=5040>' + "1 month" + '</option><option value=0>' + "Unlimited" + '</option></select>');
  5341. x += '</div>';
  5342. x += '<div id=d2agentInstallTypeDiv2>';
  5343. x += addHtmlValue("Installation Type", '<select id=agentInviteType style=width:236px><option value=0>' + "Background and interactive" + '</option><option value=2>' + "Background only" + '</option><option value=1>' + "Interactive only" + '</option></select>');
  5344. x += '</div>';
  5345. x += addHtmlValue("Message" + '<br />' + "(optional)", '<textarea id=agentInviteMessage value="" style=width:230px;height:100px;resize:none maxlength=1024 /></textarea>');
  5346. x += '</div>';
  5347. }
  5348. x += '<div id=urlInviteDiv>' + format("Invite someone to install the mesh agent by sharing an invitation link. This link points the user to installation instructions for the \"{0}\" device group. The link is public and no account for this server is needed.", EscapeHtml(mesh.name)) + '<br /><br />';
  5349. x += addHtmlValue("Link Expiration", '<select id=d2inviteExpire style=width:236px onchange=d2RequestInvitationLink()><option value=1>' + "1 hour" + '</option><option value=8>' + "8 hours" + '</option><option value=24>' + "1 day" + '</option><option value=168>' + "1 week" + '</option><option value=5040>' + "1 month" + '</option><option value=0>' + "Unlimited" + '</option></select>');
  5350. x += addHtmlValue("Agents", '<select id=d2agentType style=width:236px onchange=d2ChangedInviteType()><option value=0>' + "All Available Agents" + '</option><option value=1>' + "Windows MeshAgent" + '</option><option value=2>' + "Linux MeshAgent" + '</option><option value=4>' + "MacOS MeshAgent" + '</option><option value=8>' + "MeshCentral Assistant" + '</option><option value=16>' + "Android MeshAgent" + '</option></select>');
  5351. x += '<div id=d2agentInstallTypeDiv>';
  5352. x += addHtmlValue("Installation Type", '<select id=d2agentInviteType style=width:236px onchange=d2RequestInvitationLink()><option value=0>' + "Background and interactive" + '</option><option value=2>' + "Background only" + '</option><option value=1>' + "Interactive only" + '</option></select>');
  5353. x += '</div>';
  5354. x += '<div id=agentInvitationLinkDiv style="text-align:center;font-size:large;margin:16px;display:none"><a href=# id=agentInvitationLink rel="noreferrer noopener" target="_blank" style=cursor:pointer></a> <img src=images/link4.png height=10 width=10 title="' + "Copy link to clipboard" + '" style=cursor:pointer onclick=d2CopyInviteToClip()></div></div>';
  5355. setDialogMode(2, "Invite", 3, performAgentInvite, x, meshid);
  5356. if (features & 64) { Q('d2InviteType').focus(); d2ChangedInviteType(); } else { Q('d2inviteExpire').focus(); validateAgentInvite(); }
  5357. d2RequestInvitationLink();
  5358. return false;
  5359. }
  5360. function d2RequestInvitationLink() {
  5361. meshserver.send({ action: 'createInviteLink', meshid: xxdialogTag, expire: parseInt(Q('d2inviteExpire').value), flags: parseInt(Q('d2agentInviteType').value), agents: parseInt(Q('d2agentType').value) });
  5362. }
  5363. function d2ChangedInviteType() {
  5364. if (features & 64) {
  5365. QV('urlInviteDiv', Q('d2InviteType').value == 0);
  5366. QV('d2agentexpirediv', Q('agentInviteNameOs').value == 4);
  5367. QV('d2agentInstallTypeDiv2', Q('agentInviteNameOs').value < 2);
  5368. QV('emailInviteDiv', Q('d2InviteType').value == 1);
  5369. }
  5370. QV('d2agentInstallTypeDiv', parseInt(Q('d2agentType').value) < 2);
  5371. validateAgentInvite();
  5372. d2RequestInvitationLink();
  5373. }
  5374. function d2CopyInviteToClip() { copyTextToClip(Q('agentInvitationLink').href); }
  5375. function d2CopySecretToClip() { copyTextToClip(Q('d2optsecret').getAttribute('secret')); }
  5376. function validateAgentInvite() {
  5377. if ((features & 64) && (Q('d2InviteType').value == 1)) {
  5378. QE('idx_dlgOkButton', checkEmail(Q('agentInviteEmail').value));
  5379. QV('idx_dlgCancelButton', true);
  5380. } else {
  5381. QE('idx_dlgOkButton', true);
  5382. QV('idx_dlgCancelButton', false);
  5383. }
  5384. }
  5385. function performAgentInvite(button, meshid) {
  5386. if ((features & 64) && (Q('d2InviteType').value == 1)) {
  5387. meshserver.send({ action: 'inviteAgent', meshid: meshid, email: Q('agentInviteEmail').value, name: Q('agentInviteName').value, os: Q('agentInviteNameOs').value, flags: Q('agentInviteType').value, msg: Q('agentInviteMessage').value, expire: parseInt(Q('agentInviteExpire').value) });
  5388. }
  5389. }
  5390. function addAgentToMesh(meshid) {
  5391. if (xxdialogMode) return false;
  5392. var mesh = meshes[meshid], x = '', installType = 0, moreoptions = '';
  5393. var opts = '<select id=aginsSelect onchange=addAgentToMeshClick() style=width:236px><option value=0>' + "Windows" + '</option><option value=1>' + "Linux / BSD" + '</option><option value=5>' + "Linux / BSD / macOS Binary Installer" + '</option><option value=2>' + "Apple macOS" + '</option>';
  5394. if ((features & 2) == 0) { opts += '<option value=6>' + "Mobile device" + '</option>'; } // Don't display mobile setup in LAN mode.
  5395. opts += '<option value=7>' + "MeshCentral Assistant" + '</option>';
  5396. opts += '<option value=3>' + "Windows (UnInstall)" + '</option><option value=4>' + "Linux / BSD (UnInstall)" + '</option><option value=8>' + "Apple macOS (UnInstall)" + '</option></select>';
  5397. x += addHtmlValue("Operating System", opts);
  5398. var servername = serverinfo.name;
  5399. 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.
  5400. var domainUrlNoSlash = domainUrl.substring(0, domainUrl.length - 1), portStr = '';
  5401. if (serverinfo.https == true) { portStr = (serverinfo.port == 443)?'':(':' + serverinfo.port); } else { portStr = (serverinfo.port == 80) ? '' : (':' + serverinfo.port); }
  5402. // Add Linux/macOS binary installer option
  5403. var binaryInstallAgentsOrder = [ 6, 5, 10005, 25, 26, 28, 30, 32, 36, 37, 40, 41, 45, 16, 29 ];
  5404. var binaryInstallAgents = { 6 : 'Linux x86-64', 5 : 'Linux x86-32', 10005 : 'Apple OSX Universal', 25 : 'Linux ARM-HF, Rasberry Pi', 26 : 'Linux ARM64-HF', 28: 'Linux MIPS24KC (OpenWRT)', 30 : 'FreeBSD x86-64', 32: 'Linux ARM 64 bit (glibc/2.24 NOKVM)', 36: 'OpenWRT x86-64', 37: 'OpenBSD x86-64', 40: 'Linux MIPSEL24KC (OpenWRT)', 41: 'ARMADA/CORTEX-A53/MUSL (OpenWRT)', 45: 'RISC-V x86-64', 16: 'Apple macOS x86-64', 29: 'Apple macOS ARM-64' };
  5405. for (var i in binaryInstallAgentsOrder) { moreoptions += '<option value=' + binaryInstallAgentsOrder[i] + '>' + binaryInstallAgents[binaryInstallAgentsOrder[i]] + '</option>' }
  5406. x += '<div id=aginsSysTypeDiv>';
  5407. x += addHtmlValue("System Type", '<select id=aginsSysType onchange=addAgentToMeshClick() style=width:236px>' + moreoptions + '</select>');
  5408. x += '</div>';
  5409. // Add agent and assistant installation type option
  5410. x += '<div id=aginsTypeDiv>';
  5411. x += addHtmlValue("Installation Type", '<select id=aginsType onchange=addAgentToMeshClick() style=width:236px><option value=0>' + "Background & interactive" + '</option><option value=2>' + "Background only" + '</option><option value=1>' + "Interactive only" + '</option></select>');
  5412. x += '</div><div id=asinsTypeDiv>';
  5413. x += addHtmlValue("Installation Type", '<select id=asinsType onchange=addAgentToMeshClick() style=width:236px><option value=2>' + "Application, Connect on user request" + '</option><option value=3>' + "Application, Always connected" + '</option><option value=0>' + "System Tray, Connect on user request" + '</option><option value=1>' + "System Tray, Always connected" + '</option><option value=4>' + "System Tray, Monitor only" + '</option></select>');
  5414. x += '</div><hr>';
  5415. // \/:*?"<>|
  5416. var meshfilename = mesh.name
  5417. meshfilename = meshfilename.split('\\').join('').split('/').join('').split(':').join('').split('*').join('').split('?').join('').split('"').join('').split('<').join('').split('>').join('').split('|').join('').split(' ').join('').split('\'').join('');
  5418. // Windows agent install
  5419. x += '<div id=agins_windows>' + format("To add a new computer to device group \"{0}\", download the mesh agent and install it the computer to manage. This agent has server and device group information embedded within it.", EscapeHtml(mesh.name)) + '<br /><br />';
  5420. x += addHtmlValue("Mesh Agent", '<a id=aginsw32lnk name="meshagents?id=3&meshid=' + meshid.split('/')[2] + '&installflags=0' + (urlargs.key?('&key=' + urlargs.key):'') + '" title="' + "x86 32bit version of the MeshAgent" + '">' + "Windows x86-32 (.exe)" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy Windows x86 32bit agent URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshagents?id=3&meshid=' + meshid.split('/')[2] + '&installflags=",1)>');
  5421. x += addHtmlValue("Mesh Agent", '<a id=aginsw64lnk name="meshagents?id=4&meshid=' + meshid.split('/')[2] + '&installflags=0' + (urlargs.key?('&key=' + urlargs.key):'') + '" title="' + "x86 64bit version of the MeshAgent" + '">' + "Windows x86-64 (.exe)" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy Windows x86 64bit agent URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshagents?id=4&meshid=' + meshid.split('/')[2] + '&installflags=",1)>');
  5422. x += addHtmlValue("Mesh Agent", '<a id=aginswa64lnk name="meshagents?id=43&meshid=' + meshid.split('/')[2] + '&installflags=0' + (urlargs.key?('&key=' + urlargs.key):'') + '" title="' + "ARM 64bit version of the MeshAgent" + '">' + "Windows ARM-64 (.exe)" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy Windows ARM 64bit agent URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshagents?id=43&meshid=' + meshid.split('/')[2] + '&installflags=",1)>');
  5423. if (debugmode > 0) { x += addHtmlValue("Settings File", '<a id=aginswmshlnk name="meshsettings?id=' + meshid.split('/')[2] + '&installflags=0' + (urlargs.key?('&key=' + urlargs.key):'') + '">' + format("{0} settings (.msh)", EscapeHtml(mesh.name)) + '</a>'); }
  5424. x += '</div>';
  5425. // Linux agent install
  5426. x += '<div id=agins_linux style=display:none>' + format("To add a computer to \"{0}\" run the following command. Root credentials will be needed.", EscapeHtml(mesh.name)) + '<br />';
  5427. x += '<textarea id=agins_linux_area rows=2 cols=20 readonly=readonly style=border-radius:4px;padding:6px;margin-top:4px;margin-bottom:4px;background-color:#FFF9D3;border:0;width:100%;resize:none;height:160px;overflow:auto;font-size:12px></textarea>';
  5428. x += '<div style=font-size:x-small;float:right>' + "* For BSD, run \"pkg install wget sudo bash\" first." + '</div><a style=text-decoration:none title="' + "Copy to clipboard" + '" onclick=copyAgentIdValue("agins_linux_area")><img src=images/link4.png height=10 width=10 style=cursor:pointer> ' + "Copy" + '</a></div>';
  5429. // macOS agent install
  5430. x += '<div id=agins_osx style=display:none>' + format("To add a new computer to device group \"{0}\", download the mesh agent and install it the computer to manage. This agent installer has server and device group information embedded within it.", EscapeHtml(mesh.name)) + '<br /><br />';
  5431. x += addHtmlValue("Mesh Agent", '<a onclick=downloadFile("meshosxagent?id=16&meshid=' + meshid.split('/')[2] + (urlargs.key?('&key=' + urlargs.key):'') + '") title="' + "x86-64bit version of macOS Mesh Agent" + '">' + "macOS x86-64bit" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy macOS agent URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshosxagent?id=16&meshid=' + meshid.split('/')[2] + '",0)>');
  5432. x += addHtmlValue("Mesh Agent", '<a onclick=downloadFile("meshosxagent?id=29&meshid=' + meshid.split('/')[2] + (urlargs.key?('&key=' + urlargs.key):'') + '") title="' + "ARM 64bit version of macOS Mesh Agent" + '">' + "macOS ARM (64bit)" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy macOS agent URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshosxagent?id=29&meshid=' + meshid.split('/')[2] + '",0)>');
  5433. x += addHtmlValue("Mesh Agent", '<a onclick=downloadFile("meshosxagent?id=10005&meshid=' + meshid.split('/')[2] + (urlargs.key?('&key=' + urlargs.key):'') + '") title="' + "Universal version of macOS Mesh Agent" + '">' + "macOS (Universal)" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy macOS agent URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshosxagent?id=10005&meshid=' + meshid.split('/')[2] + '",0)>');
  5434. x += '</div>';
  5435. // QR code agent install
  5436. x += '<div id=agins_qrcode style=display:none;min-height:180px><a id=agins_qrimage_a rel=\"noreferrer noopener\" target=_blank><div id=agins_qrimage style=float:right;margin-left:10px;width:180px;height:180px;cursor:pointer></div></a><div>' + format("To add a mobile device to group \"{0}\", download the MeshAgent application and scan this QR code.", EscapeHtml(mesh.name)) + '</div>';
  5437. x += '<table style=width:180px>';
  5438. x += '<tr><td style=text-align:center><a title="' + "Google Play Store" + '"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></td></tr>';
  5439. x += '<tr><td style=text-align:center><a title="' + "Amazon App Store" + '" 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></td></tr>';
  5440. x += '</table>';
  5441. x += addHtmlValue("Android APK", '<a onclick=downloadFile("meshagents?id=14' + (urlargs.key?('&key=' + urlargs.key):'') + '",null,true) title="' + "APK version of the MeshAgent" + '">' + "APK" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshagents?id=14&meshid=' + meshid.split('/')[2] + (urlargs.key?('&key=' + urlargs.key):'') + '")>');
  5442. x += '</div>'
  5443. // MeshCentral Assistant
  5444. x += '<div id=agins_assistant style=display:none><table><tr><td style=vertical-align:top><div>' + format("MeshCentral Assistant is a Windows tool that users can use to ask for help. Use the link below to download a version that will connect to device group \"{0}\".", EscapeHtml(mesh.name)) + '</div><div></div><br />';
  5445. x += '<a id=asinslnk name="meshagents?id=10006&meshid=' + meshid.split('/')[2] + (urlargs.key?('&key=' + urlargs.key):'') + '" title="' + "MeshCentral Assistant for Windows" + '">' + "Assistant for Windows (.exe)" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshagents?id=10006&meshid=' + meshid.split('/')[2] + (urlargs.key?('&key=' + urlargs.key):'') + '")>';
  5446. x += '</td><td><img id=agass_img1 src=images/assistant-100.png width=74 height=100 srcset="images/assistant-200.png 2x"><img id=agass_img2 src=images/assistant2-100.png width=74 height=100 srcset="images/assistant2-200.png 2x"></td></tr></table></div>';
  5447. // MeshCentral Assistant2
  5448. x += '<div id=agins_assistant2 style=display:none><table><tr><td style=vertical-align:top><div>' + "MeshCentral Assistant is a Windows tool that users can use to ask for help. Use the link below to download a version that will monitor the background agent." + '</div><div></div><br />';
  5449. x += '<a id=asinslnk2 name="meshagents?id=10006&meshid=' + meshid.split('/')[2] + (urlargs.key?('&key=' + urlargs.key):'') + '" title="' + "MeshCentral Assistant for Windows" + '">' + "Assistant for Windows (.exe)" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshagents?id=10006&meshid=' + meshid.split('/')[2] + (urlargs.key?('&key=' + urlargs.key):'') + '")>';
  5450. x += '</td><td><img src=images/assistant-100.png width=74 height=100 srcset="images/assistant-200.png 2x"></td></tr></table></div>';
  5451. // Windows agent uninstall
  5452. x += '<div id=agins_windows_un style=display:none>' + "To remove a mesh agent, download the file below, run it and click \"uninstall\"." + '<br /><br />';
  5453. x += addHtmlValue("Mesh Agent", '<a id=aginsw32unlnk name="meshagents?id=3&meshid=' + meshid.split('/')[2] + '&installflags=0' + (urlargs.key?('&key=' + urlargs.key):'') + '" title="' + "x86 32bit version of the MeshAgent" + '">' + "Windows x86-32 (.exe)" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy Windows x86 32bit agent URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshagents?id=3&meshid=' + meshid.split('/')[2] + '&installflags=",1)>');
  5454. x += addHtmlValue("Mesh Agent", '<a id=aginsw64unlnk name="meshagents?id=4&meshid=' + meshid.split('/')[2] + '&installflags=0' + (urlargs.key?('&key=' + urlargs.key):'') + '" title="' + "x86 64bit version of the MeshAgent" + '">' + "Windows x86-64 (.exe)" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy Windows x86 64bit agent URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshagents?id=4&meshid=' + meshid.split('/')[2] + '&installflags=",1)>');
  5455. x += addHtmlValue("Mesh Agent", '<a id=aginswa64unlnk name="meshagents?id=43&meshid=' + meshid.split('/')[2] + '&installflags=0' + (urlargs.key?('&key=' + urlargs.key):'') + '" title="' + "ARM 64bit version of the MeshAgent" + '">' + "Windows ARM-64 (.exe)" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy Windows ARM 64bit agent URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshagents?id=43&meshid=' + meshid.split('/')[2] + '&installflags=",1)>');
  5456. x += '</div>';
  5457. // Linux agent uninstall
  5458. x += '<div id=agins_linux_un style=display:none>' + "To remove a mesh agent, run the following command. Root credentials will be needed." + '<br />';
  5459. x += '<textarea id=agins_linux_area_un rows=2 cols=20 readonly=readonly style=border-radius:4px;padding:6px;margin-top:4px;margin-bottom:4px;background-color:#FFF9D3;border:0;width:100%;resize:none;height:160px;overflow:auto;font-size:12px></textarea>';
  5460. x += '<a style=text-decoration:none title="' + "Copy to clipboard" + '" onclick=copyAgentIdValue("agins_linux_area_un")><img src=images/link4.png height=10 width=10 style=cursor:pointer> Copy</a></div>';
  5461. // macOS agent uninstall
  5462. x += '<div id=agins_osx_un style=display:none>' + "To remove a mesh agent, download the file below, right click on the \".mpkg\" file and click \"Show Package Contents\", then right click on \"Uninstall.command\" and click \"Open\"" + '<br /><br />';
  5463. x += addHtmlValue("Mesh Agent", '<a onclick=downloadFile("meshosxagent?id=10005&meshid=' + meshid.split('/')[2] + (urlargs.key?('&key=' + urlargs.key):'') + '") title="' + "Universal version of macOS Mesh Agent" + '">macOS Agent (Universal)</a> <img src=images/link4.png height=10 width=10 title="' + "Copy macOS agent URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshosxagent?id=10005&meshid=' + meshid.split('/')[2] + '",0)>');
  5464. x += '</div>';
  5465. // Linux binary installer
  5466. x += '<div id=agins_linux_inst style=display:none>' + "This is a executable on OS's with graphical user interfaces" + '<br /><br />' + "Apple macOS executables will need to be removed from quarantine to run 'xattr -r -d com.apple.quarantine meshagent'" + '<br /><br />' + "You need to 'chmod +x meshagent' and run this file" + '<br /><br />';
  5467. x += addHtmlValue("Mesh Agent", '<a id=aginsbinlnk name="meshagents?id=' + meshid.split('/')[2] + '&installflags=0' + (urlargs.key?('&key=' + urlargs.key):'') + '">' + "meshagent" + '</a> <img src=images/link4.png height=10 width=10 title="' + "Copy agent URL to clipboard" + '" style=cursor:pointer onclick=copyAgentUrl("meshagents?id=' + meshid.split('/')[2] + '&installflags=",1)>');
  5468. x += addHtmlValue("Command", '<input id=aginsbincmd type=text style="width:216px" readonly value=\'wget -O meshagent' + (((features & 0x80000000) != 0)?' --no-check-certificate':'') + ' \"https://' + servername + portStr + domainUrl + 'meshagents?id=' + meshid.split('/')[2].split('$').join('%24').split('@').join('%40') + '\' /> <img src=images/link4.png height=10 width=10 title="' + "Copy agent URL to clipboard" + '" style=cursor:pointer onclick=copyAgentIdValue("aginsbincmd")>');
  5469. x += '</div>';
  5470. setDialogMode(2, "Add Mesh Agent", 2, null, x, 'fileDownload');
  5471. // Create the QR code
  5472. new QRCode(Q('agins_qrimage'), { text: serverinfo.magenturl + ',' + serverinfo.agentCertHash + ',' + meshid.split('/')[2], width: 180, height: 180, colorDark: '#000000', colorLight: '#EEE', correctLevel: QRCode.CorrectLevel.M });
  5473. Q('agins_qrimage_a').setAttribute('href', serverinfo.magenturl + ',' + serverinfo.agentCertHash + ',' + meshid.split('/')[2]);
  5474. if ((features & 0x2000) == 0)
  5475. {
  5476. Q('agins_linux_area').value = '(wget "https://' + servername + portStr + domainUrl + 'meshagents?script=1' + (urlargs.key?('&key=' + urlargs.key):'') + '"' + (((features & 0x80000000) != 0)?' --no-check-certificate':'') + ' -O ./meshinstall.sh || wget "https://' + servername + portStr + domainUrl + 'meshagents?script=1" --no-proxy' + (((features & 0x80000000) != 0)?' --no-check-certificate':'') + ' -O ./meshinstall.sh) && chmod 755 ./meshinstall.sh && sudo -E ./meshinstall.sh https://' + servername + portStr + domainUrlNoSlash + ' \'' + meshid.split('/')[2] + '\' || ./meshinstall.sh https://' + servername + portStr + domainUrlNoSlash + ' \'' + meshid.split('/')[2] + '\'\r\n';
  5477. Q('agins_linux_area_un').value = '(wget "https://' + servername + portStr + domainUrl + 'meshagents?script=1' + (urlargs.key?('&key=' + urlargs.key):'') + '"' + (((features & 0x80000000) != 0)?' --no-check-certificate':'') + ' -O ./meshinstall.sh || wget "https://' + servername + portStr + domainUrl + 'meshagents?script=1" --no-proxy' + (((features & 0x80000000) != 0)?' --no-check-certificate':'') + ' -O ./meshinstall.sh) && chmod 755 ./meshinstall.sh && sudo -E ./meshinstall.sh uninstall https://' + servername + portStr + domainUrlNoSlash + ' \'' + meshid.split('/')[2] + '\' || ./meshinstall.sh uninstall https://' + servername + portStr + domainUrlNoSlash + ' \'' + meshid.split('/')[2] + '\'\r\n';
  5478. //Q('agins_linux_area_un').value = '(wget "https://' + servername + portStr + domainUrl + 'meshagents?script=1' + (urlargs.key?('&key=' + urlargs.key):'') + '"' + (((features & 0x80000000) != 0)?' --no-check-certificate':'') + ' -O ./meshinstall.sh || wget "https://' + servername + portStr + domainUrl + 'meshagents?script=1" --no-proxy' + (((features & 0x80000000) != 0)?' --no-check-certificate':'') + ' -O ./meshinstall.sh) && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh uninstall\r\n';
  5479. }
  5480. else
  5481. {
  5482. // Server asked that agent be installed to preferably not use a HTTP proxy.
  5483. Q('agins_linux_area').value = 'wget "https://' + servername + portStr + domainUrl + 'meshagents?script=1' + (urlargs.key?('&key=' + urlargs.key):'') + '" --no-proxy' + (((features & 0x80000000) != 0)?' --no-check-certificate':'') + ' -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo -E ./meshinstall.sh https://' + servername + portStr + domainUrlNoSlash + ' \'' + meshid.split('/')[2] + '\' || ./meshinstall.sh https://' + servername + portStr + domainUrlNoSlash + ' \'' + meshid.split('/')[2] + '\'\r\n';
  5484. Q('agins_linux_area_un').value = 'wget "https://' + servername + portStr + domainUrl + 'meshagents?script=1' + (urlargs.key?('&key=' + urlargs.key):'') + '" --no-proxy' + (((features & 0x80000000) != 0)?' --no-check-certificate':'') + ' -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo -E ./meshinstall.sh uninstall https://' + servername + portStr + domainUrlNoSlash + ' \'' + meshid.split('/')[2] + '\' || ./meshinstall.sh uninstall https://' + servername + portStr + domainUrlNoSlash + ' \'' + meshid.split('/')[2] + '\'\r\n';
  5485. //Q('agins_linux_area_un').value = 'wget "https://' + servername + portStr + domainUrl + 'meshagents?script=1' + (urlargs.key?('&key=' + urlargs.key):'') + '" --no-proxy' + (((features & 0x80000000) != 0)?' --no-check-certificate':'') + ' -O ./meshinstall.sh && chmod 755 ./meshinstall.sh && sudo ./meshinstall.sh uninstall\r\n';
  5486. }
  5487. Q('aginsSelect').focus();
  5488. addAgentToMeshClick();
  5489. return false;
  5490. }
  5491. function copyAgentIdValue(id) { copyTextToClip(Q(id).value); }
  5492. function copyAgentUrl(url,addflag) {
  5493. var servername = serverinfo.name;
  5494. 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.
  5495. var domainUrlNoSlash = domainUrl.substring(0, domainUrl.length - 1);
  5496. var portStr = (serverinfo.port == 443) ? '' : (':' + serverinfo.port);
  5497. var c = 'https://' + servername + portStr + domainUrl + url;
  5498. if (addflag == 1) c += Q('aginsType').value;
  5499. c += (urlargs.key?('&key=' + urlargs.key):'');
  5500. if (Q('aginsSelect').value == 5) { c += '&meshinstall=' + Q('aginsSysType').value; }
  5501. if (Q('aginsSelect').value == 7) { c += '&ac=' + Q('asinsType').value; }
  5502. copyTextToClip(c);
  5503. }
  5504. function addAgentToMeshClick() {
  5505. var v = Q('aginsSelect').value;
  5506. QV('agins_windows', v == 0);
  5507. QV('agins_linux', v == 1);
  5508. QV('agins_osx', v == 2);
  5509. QV('agins_windows_un', v == 3);
  5510. QV('agins_linux_un', v == 4);
  5511. QV('agins_linux_inst', v == 5);
  5512. QV('aginsSysTypeDiv', v == 5);
  5513. QV('agins_qrcode', v == 6);
  5514. QV('agins_assistant', (v == 7) && (Q('asinsType').value != 4));
  5515. QV('agins_assistant2', (v == 7) && (Q('asinsType').value == 4));
  5516. QV('agins_osx_un', v == 8);
  5517. Q('aginsbinlnk').onclick = function() { downloadFile((Q('aginsbinlnk').name.split('installflags=')[0]) + 'installflags=' + Q('aginsType').value + (urlargs.key?('&key=' + urlargs.key):'') + '&meshinstall=' + Q('aginsSysType').value); };
  5518. Q('aginsbincmd').value = (Q('aginsbincmd').value.split('&installflags=')[0]) + '&installflags=' + Q('aginsType').value + (urlargs.key?('&key=' + urlargs.key):'') + '&meshinstall=' + Q('aginsSysType').value + '\"';
  5519. QV('aginsTypeDiv', (v == 0) || (v == 5));
  5520. QV('asinsTypeDiv', (v == 7));
  5521. // Fix the links if needed
  5522. Q('aginsw32lnk').onclick = function() { downloadFile((Q('aginsw32lnk').name.split('installflags=')[0]) + 'installflags=' + Q('aginsType').value + (urlargs.key?('&key=' + urlargs.key):''), null, true); }
  5523. Q('aginsw64lnk').onclick = function() { downloadFile((Q('aginsw64lnk').name.split('installflags=')[0]) + 'installflags=' + Q('aginsType').value + (urlargs.key?('&key=' + urlargs.key):''), null, true); }
  5524. Q('aginswa64lnk').onclick = function() { downloadFile((Q('aginswa64lnk').name.split('installflags=')[0]) + 'installflags=' + Q('aginsType').value + (urlargs.key?('&key=' + urlargs.key):''), null, true); }
  5525. Q('aginsw32unlnk').onclick = function() { downloadFile((Q('aginsw32lnk').name.split('installflags=')[0]) + 'installflags=' + Q('aginsType').value + (urlargs.key?('&key=' + urlargs.key):''), null, true); }
  5526. Q('aginsw64unlnk').onclick = function() { downloadFile((Q('aginsw64lnk').name.split('installflags=')[0]) + 'installflags=' + Q('aginsType').value + (urlargs.key?('&key=' + urlargs.key):''), null, true); }
  5527. Q('aginswa64unlnk').onclick = function() { downloadFile((Q('aginswa64lnk').name.split('installflags=')[0]) + 'installflags=' + Q('aginsType').value + (urlargs.key?('&key=' + urlargs.key):''), null, true); }
  5528. if (debugmode > 0) { Q('aginswmshlnk').onclick = function() { downloadFile((Q('aginswmshlnk').name.split('installflags=')[0]) + 'installflags=' + Q('aginsType').value + (urlargs.key?('&key=' + urlargs.key):''), null, true); } }
  5529. Q('asinslnk').onclick = function() { downloadFile(Q('asinslnk').name + '&ac=' + Q('asinsType').value, null, true); }
  5530. Q('asinslnk2').onclick = function() { downloadFile(Q('asinslnk').name + '&ac=' + Q('asinsType').value, null, true); }
  5531. if (v == 7) {
  5532. QV('agass_img1', Q('asinsType').value < 2);
  5533. QV('agass_img2', Q('asinsType').value >= 2);
  5534. }
  5535. }
  5536. function validateDeviceToMesh() {
  5537. QE('idx_dlgOkButton', (Q('dp1devicename').value.length > 0) && (passwordcheck(Q('dp1password').value)));
  5538. }
  5539. function addDeviceToMeshEx(button, meshid) {
  5540. var amtuser = Q('dp1username').value;
  5541. if (amtuser == '') amtuser = 'admin';
  5542. var host = Q('dp1hostname').value;
  5543. if (host == '') host = Q('dp1devicename').value;
  5544. meshserver.send({ action: 'addamtdevice', meshid: meshid, devicename: Q('dp1devicename').value, hostname: host, amtusername: amtuser, amtpassword: Q('dp1password').value, amttls: Q('dp1tls').value });
  5545. }
  5546. function deviceHeaderSet() {
  5547. if (deviceHeaderId == 0) { deviceHeaderId = 1; return; }
  5548. deviceHeaders['DevxHeader' + deviceHeaderId] = ((deviceHeaderTotal == 1) ? "1 node" : format("{0} nodes", deviceHeaderTotal));
  5549. //var title = '';
  5550. //for (x in deviceHeaderCount) { if (title.length > 0) title += ', '; title += deviceHeaderCount[x] + ' ' + PowerStateStr2(x); }
  5551. //deviceHeadersTitles["DevxHeader" + deviceHeaderId] = title;
  5552. deviceHeaderId++;
  5553. deviceHeaderCount = {};
  5554. deviceHeaderTotal = 0;
  5555. }
  5556. var powerStateStrings = ['', '<span title="' + "Device is powered on." + '">' + "Powered" + '</span>', '<span title="' + "Device is in sleep state (S1)." + '">' + "Sleeping" + '</span>', '<span title="' + "Device is in sleep state (S2)." + '">' + "Sleeping" + '</span>', '<span title="' + "Device is in deep sleep state (S3)." + '">' + "Deep Sleep" + '</span>', '<span title="' + "Device is in hibernating state (S4)." + '">' + "Hibernating" + '</span>', '<span title="' + "Device is in powered off state (S5)." + '">' + "Soft-Off" + '</span>', '<span title="' + "Device is detected but power state could not be obtained." + '">' + "Present" + '</span>', '<span title="' + "Device is powered off." + '">' + "Off" + '</span>'];
  5557. 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"];
  5558. var powerColorTable = ['pwsTransparent', 'pwsBlack', 'pwsBlue', 'pwsBlue2', 'pwsLightblue', 'pwsBlueviolet', 'pwsDarkgreen', 'pwsLightseagreen', 'pwsLightseagreen2'];
  5559. function NodeStateStr(node) {
  5560. var states = [];
  5561. if (node.state > 0 && node.state < powerStatetable.length) state.push(powerStatetable[node.state]);
  5562. if (node.conn) {
  5563. if ((node.conn & 1) != 0) {
  5564. if (node.mtype == 4) {
  5565. if (node.porttype == 'PDU') {
  5566. states.push('<span title="' + "Power switch is ready for use." + '">' + "Switch" + '</span>');
  5567. } else {
  5568. states.push('<span title="' + "IP-KVM port is up and ready for use." + '">' + "IP-KVM" + '</span>');
  5569. }
  5570. } else {
  5571. states.push('<span title="' + "Mesh agent is connected and ready for use." + '">' + "Agent" + '</span>');
  5572. }
  5573. }
  5574. if ((node.conn & 2) != 0) { states.push('<span title="' + "Intel&reg; AMT CIRA is connected and ready for use." + '">' + "CIRA" + '</span>'); }
  5575. else if ((node.conn & 4) != 0) { states.push('<span title="' + "Intel&reg; AMT is routable." + '">' + "AMT" + '</span>'); }
  5576. if ((node.conn & 8) != 0) { states.push('<span title="' + "Mesh agent is reachable using another agent as relay." + '">' + "Relay" + '</span>'); }
  5577. if ((node.conn & 16) != 0) { states.push('<span title="' + "MQTT connection to the device is active." + '">' + "MQTT" + '</span>'); }
  5578. }
  5579. if ((node.pwr != null) && (node.pwr != 0)) { states.push(powerStateStrings[node.pwr]); }
  5580. if (states.length == 0) return '&nbsp;';
  5581. return states.join(', ');
  5582. }
  5583. function PowerStateStr(x) {
  5584. if (x < powerStatetable.length) return powerStatetable[x];
  5585. return '';
  5586. }
  5587. function PowerStateStr2(x) {
  5588. if ((x != 0) && (x < powerStatetable.length)) return powerStatetable[x];
  5589. return "Unknown";
  5590. }
  5591. function updateCollapseAllButton() {
  5592. var x = (Object.keys(CollapsedGroups).length > 0)
  5593. QV('CollapseAllButton', !x);
  5594. QV('ExpandAllButton', x);
  5595. QV('CollapseAllButton2', !x);
  5596. QV('ExpandAllButton2', x);
  5597. }
  5598. function selectallButtonFunction() {
  5599. var elements = document.getElementsByClassName('DeviceCheckbox'), checkcount = Object.keys(checkedNodeids).length;
  5600. for (var i = 0; i < elements.length; i++) { elements[i].checked = (checkcount == 0); }
  5601. checkedNodeids = {};
  5602. if (checkcount == 0) {
  5603. checkedNodeids = {};
  5604. var devdivs = document.getElementsByName('xxdevice' + Q('viewselect').value);
  5605. // Checking that the parent style is null will insure that "select all" does not select any devices in collapsed groups
  5606. for (var i = 0; i < devdivs.length; i++) { if ((devdivs[i].parentElement.attributes.style == null) || (devdivs[i].parentElement.attributes.style.value.indexOf('display') < 0)) { checkedNodeids[devdivs[i].id.substring(3)] = 1; } }
  5607. }
  5608. p1updateInfo();
  5609. }
  5610. function p1devcheck(e) {
  5611. if (e.srcElement.checked) { checkedNodeids[e.srcElement.value.substring(6)] = 1; } else { delete checkedNodeids[e.srcElement.value.substring(6)]; }
  5612. p1updateInfo();
  5613. }
  5614. function p1updateInfo() {
  5615. var checkcount = Object.keys(checkedNodeids).length;
  5616. if (checkcount > 0) {
  5617. QE('GroupActionButton', true);
  5618. Q('SelectAllButton').value = "Select None";
  5619. QV('cxmdesktop', true);
  5620. } else {
  5621. QE('GroupActionButton', false);
  5622. Q('SelectAllButton').value = "Select All";
  5623. QV('cxmdesktop', false);
  5624. }
  5625. // Custom UI
  5626. if (customui != null) {
  5627. if (customui.devicesbarbuttons) {
  5628. for (var i in customui.devicesbarbuttons) {
  5629. if (customui.devicesbarbuttons[i].selection == 'one') { QE('cui:' + i, checkcount == 1); }
  5630. if (customui.devicesbarbuttons[i].selection == 'many') { QE('cui:' + i, checkcount >= 1); }
  5631. }
  5632. }
  5633. }
  5634. }
  5635. function groupActionFunction() {
  5636. var addedOptions = [], nodeids = getCheckedDevices(), added = 0;
  5637. // Remind the user to add two factor authentication
  5638. if ((features & 0x00040000) && (count2factoraAuths() == 0)) { 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\" tab and look at the \"Account Security\" section."); return; }
  5639. // Check if any of the selected devices have a MQTT connection active
  5640. if (features & 0x00400000) {
  5641. for (var i in nodeids) { if ((getNodeFromId(nodeids[i]).conn & 16) != 0) { addedOptions.push({ v:103, name: "Send MQTT Message" });; break; } }
  5642. }
  5643. // Display the options that are allowed depending on what devices are selected.
  5644. addedOptions.push({ v: 105, name: "Export device information", s:true });
  5645. for (var i in nodeids) {
  5646. var node = getNodeFromId(nodeids[i]), rights = GetNodeRights(node);
  5647. if ((rights & 1) && ((added & 2) == 0)) { added |= 2; addedOptions.push({ v: 102, name: "Move to device group" }); }
  5648. if (((node.conn & 1) != 0) && ((rights & 0x8000) != 0) && ((added & 1) == 0)) { added |= 1; addedOptions.push({ v: 104, name: "Uninstall Agent" }); }
  5649. if ((rights & 64) && ((added & 8) == 0)) { added |= 8; addedOptions.push({ v: 100, name: "Wake-up devices" }); }
  5650. if ((rights & 262144) && ((added & 4) == 0)) { added |= 4; addedOptions.push({ v: 4, name: "Sleep devices" }); addedOptions.push({ v: 3, name: "Reset devices" }); addedOptions.push({ v: 2, name: "Power off devices" }); }
  5651. if ((rights & 131072) && ((added & 16) == 0)) { added |= 16; addedOptions.push({ v: 106, name: "Run commands" }); }
  5652. if ((rights & 16384) && ((added & 32) == 0)) { added |= 32; addedOptions.push({ v: 108, name: "Device notification" }); }
  5653. if ((rights & 4) && ((added & 64) == 0)) { added |= 64; addedOptions.push({ v: 107, name: "Edit tags" }); }
  5654. if ((rights & 8) && ((added & 256) == 0)) { added |= 256; addedOptions.push({ v: 109, name: "Upload files" }); }
  5655. if ((rights & 32768) && ((added & 128) == 0)) { added |= 128; addedOptions.push({ v: 101, name: "Delete devices" }); }
  5656. if ((node.agent != null) && (features2 & 0x00000010) && (rights == 0xFFFFFFFF) && ((added & 512) == 0)) {
  5657. added |= 512;
  5658. addedOptions.push({ v: 110, name: "Force agent update" });
  5659. addedOptions.push({ v: 111, name: "Clear agent core" });
  5660. addedOptions.push({ v: 112, name: "Upload default server core" });
  5661. }
  5662. }
  5663. var addedOptionsStr = '';
  5664. addedOptions.sort(nameSort);
  5665. for (var i in addedOptions) { addedOptionsStr += '<option value=' + addedOptions[i].v + (addedOptions[i].s?' selected':'') + '>' + addedOptions[i].name + '</option>'; }
  5666. var x = "Select an operation to perform on all selected devices. Actions will be performed only with proper rights." + '<br /><br />';
  5667. x += addHtmlValue("Operation", '<select id=d2groupop>' + addedOptionsStr + '</select>');
  5668. setDialogMode(2, "Group Action", 3, groupActionFunctionEx, x);
  5669. }
  5670. // Get the list of checked devices
  5671. function getCheckedDevices() { return Object.keys(checkedNodeids); }
  5672. function uncheckAllDevices() {
  5673. var elements = document.getElementsByClassName('DeviceCheckbox');
  5674. for (var i=0;i<elements.length;i++) { elements[i].checked = false; }
  5675. checkedNodeids = {};
  5676. }
  5677. function groupActionFunctionEx() {
  5678. var op = Q('d2groupop').value;
  5679. if (op == 100) {
  5680. // Group wake
  5681. meshserver.send({ action: 'wakedevices', nodeids: getCheckedDevices() });
  5682. uncheckAllDevices();
  5683. } else if (op == 101) {
  5684. // Group delete, ask for confirmation
  5685. var chkNodeIds = getCheckedDevices(), x = '';
  5686. if (chkNodeIds.length == 0) return;
  5687. else if (chkNodeIds.length == 1){ x += "Confirm removal of selected device?" + '<br /><br />'; }
  5688. else {
  5689. x += format("Confirm removal of {0} selected devices?" + '<br />', chkNodeIds.length);
  5690. var nconn = 0, ndisc = 0;
  5691. for (var i in chkNodeIds) { var n = getNodeFromId(chkNodeIds[i]); if ((n.conn != null) && (n.conn > 0)) { nconn++; } else { ndisc++; } }
  5692. var y = '';
  5693. if (nconn == 1) { y += format("1 selected device is online." + '<br />', nconn); }
  5694. else if (nconn > 1) { y += format("{0} selected devices are online." + '<br />', nconn); }
  5695. if (ndisc == 1) { y += format("1 selected device is offline." + '<br />', ndisc); }
  5696. else if (ndisc > 1) { y += format("{0} selected devices are offline." + '<br />', ndisc); }
  5697. if (y != '') { x += '<div style=padding:8px>' + y + '</div>'; } else { x += '<br />'; }
  5698. }
  5699. x += '<label><input id=d2check type=checkbox onchange=d2groupActionFunctionDelCheck() />' + "Confirm" + '</label>';
  5700. setDialogMode(2, "Delete Devices", 3, d2groupActionFunctionDelExec, x);
  5701. QE('idx_dlgOkButton', false);
  5702. } else if (op == 102) {
  5703. // Move computers to a different group
  5704. p10showChangeGroupDialog(getCheckedDevices());
  5705. } else if (op == 103) {
  5706. // Send MQTT Message
  5707. p10showSendMqttMsgDialog(getCheckedDevices());
  5708. } else if (op == 104) {
  5709. // Uninstall agent
  5710. p10showSendUninstallAgentDialog(getCheckedDevices());
  5711. } else if (op == 105) {
  5712. // Export device information
  5713. p2downloadDeviceInfo();
  5714. } else if (op == 106) {
  5715. // Run commands
  5716. d2runCommandDialog({ nodeids: getCheckedDevices(), title: "Run commands on selected devices.", func: uncheckAllDevices });
  5717. } else if (op == 107) {
  5718. // Edit tags
  5719. var x = "Perform batch device tag operation" + '<br /><br />';
  5720. x += addHtmlValue("Operation", '<select id=d2deviceop style=float:right;width:250px><option value=1>' + "Add tags" + '</option><option value=2>' + "Set tags" + '</option><option value=3>' + "Remove tags" + '</option></select>');
  5721. x += addHtmlValue("Tags", '<input id=dp10devicevalue maxlength=4096 placeholder="' + "Tag1, Tag2, Tag3" + '" />');
  5722. // Get a list of all possible device tags
  5723. var allTags = [], y = '';
  5724. 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]); } } } }
  5725. if (allTags.length > 0) {
  5726. allTags.sort();
  5727. 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> '; }
  5728. x += '<div style=margin-top:8px;width:370px;line-height:26px;max-height:160px;overflow-y:auto>' + y + '</div>';
  5729. }
  5730. setDialogMode(2, "Edit Device Tags", 3, d2groupActionFunctionTagsExec, x);
  5731. } else if (op == 108) {
  5732. // Device notification
  5733. var x = '<div style=margin-bottom:4px>'+ "Perform batch device notification" + '</div>';
  5734. x += '<select id=d2deviceop style=width:100%;margin-bottom:4px><option value=2>' + "Toast Notification" + '</option><option value=1>' + "Message Box" + '</option><option value=3>' + "Alert Box" + '</option></select>';
  5735. x += '<input id=dp2notifyTitle maxlength=256 placeholder="' + "Title" + '" style=width:100%;box-sizing:border-box;margin-bottom:4px />';
  5736. x += '<textarea id=d2notifyMsg style=background-color:#fcf3cf;width:100%;height:140px;resize:none;overflow-y:scroll;box-sizing:border-box;margin-bottom:4px></textarea>';
  5737. x += '<select style=width:100% id=d2notifyTimeout>';
  5738. x += '<option disabled value="">Only Applicable to Message Box Notifications</option>';
  5739. x += '<option value=2 selected>' + "Show for 2 Minutes (Default)" + '</option>';
  5740. x += '<option value=10>' + "Show for 10 minutes" + '</option>';
  5741. x += '<option value=30>' + "Show for 30 minutes" + '</option>';
  5742. x += '<option value=60>' + "Show for 60 minutes" + '</option>';
  5743. x += '<option value=0>' + "Show message until dismissed by user" + '</option>';
  5744. x += '</select>';
  5745. setDialogMode(2, "Device Notification", 3, d2groupActionFunctionNotifyExec, x);
  5746. Q('d2notifyMsg').focus();
  5747. } else if (op == 109) {
  5748. // Upload files
  5749. var wintype = false, linuxtype = false, chkNodeIds = getCheckedDevices();
  5750. for (var i in chkNodeIds) { var n = getNodeFromId(chkNodeIds[i]); if (n.agent) { if (isWindowsNode(n)) { wintype = true; } else { linuxtype = true; } } }
  5751. var x = "Upload selected files to all selected devices" + '<br /><br />';
  5752. x += '<form method=post enctype=multipart/form-data action=uploadfilebatch.ashx target=fileUploadFrame>';
  5753. x += '<input type=hidden name=authCookie value=' + authCookie + ' /><input type=hidden name=nodeIds value=' + chkNodeIds.join(',') + ' /><input type=submit id=d2batchUploadSubmit style=display:none />';
  5754. x += '<input type=file name=files id=d2uploadinput style=width:100% multiple=multiple onchange="d2batchUploadValidate()" /><br /><br />';
  5755. if (wintype) { x += addHtmlValue("Windows Path", '<input style=width:250px type=text onchange="d2batchUploadValidate()" onkeyup="d2batchUploadValidate()" name=winpath id=d2winuploadpath placeholder="C:\\temp" value="" />'); }
  5756. if (linuxtype) { x += addHtmlValue("Linux Path", '<input style=width:250px type=text onchange="d2batchUploadValidate()" onkeyup="d2batchUploadValidate()" name=linuxpath id=d2linuxuploadpath placeholder="/tmp" value="" />'); }
  5757. x += '<br /><label><input type=checkbox name=createFolder />' + "Create folder if it does not exists?" + '</label><br />';
  5758. x += '<label><input type=checkbox name=overwriteFiles />' + "Overwrite if file exists?" + '</label></form>';
  5759. setDialogMode(2, "Batch File Upload", 3, d2batchUploadValidateOk, x);
  5760. d2batchUploadValidate();
  5761. } else if (op == 110) {
  5762. // Force agent update
  5763. var x = "Force agent update on selected devices?" + '<br /><br />';
  5764. setDialogMode(2, "Force agent update", 3, d2groupActionFunctionAgentUpdateExec, x);
  5765. } else if (op == 111) {
  5766. // Clear agent core
  5767. var x = "Clear agent core on selected devices?" + '<br /><br />';
  5768. setDialogMode(2, "Clear agent core", 3, d2groupActionFunctionAgentClearCoreExec, x);
  5769. } else if (op == 112) {
  5770. // Upload default server core
  5771. var x = "Upload default server core on selected devices?" + '<br /><br />';
  5772. setDialogMode(2, "Upload default server core", 3, d2groupActionFunctionAgentDefaultCodeExec, x);
  5773. } else {
  5774. // Power operation
  5775. meshserver.send({ action: 'poweraction', nodeids: getCheckedDevices(), actiontype: parseInt(op) });
  5776. uncheckAllDevices();
  5777. }
  5778. }
  5779. function d2batchUploadValidate() { QE('idx_dlgOkButton', (Q('d2uploadinput').files.length != 0) && ((Q('d2winuploadpath') == null) || (Q('d2winuploadpath').value != '')) && ((Q('d2linuxuploadpath') == null) || (Q('d2linuxuploadpath').value != ''))); }
  5780. function d2batchUploadValidateOk() { Q('d2batchUploadSubmit').click(); }
  5781. function d2groupActionFunctionAgentUpdateExec() { meshserver.send({ action: 'updateAgents', nodeids: getCheckedDevices() }); }
  5782. function d2groupActionFunctionAgentClearCoreExec() { meshserver.send({ action: 'uploadagentcore', nodeids: getCheckedDevices(), type: 'clear' }); }
  5783. function d2groupActionFunctionAgentDefaultCodeExec() { meshserver.send({ action: 'uploadagentcore', nodeids: getCheckedDevices(), type: 'default' }); }
  5784. function d2groupActionFunctionNotifyExec() {
  5785. var op = Q('d2deviceop').value, title = Q('dp2notifyTitle').value, msg = Q('d2notifyMsg').value, chkNodeIds = getCheckedDevices(), timeout = parseFloat(Q('d2notifyTimeout').value);
  5786. if (msg.length == 0) return;
  5787. if (title == '') { title = decodeURIComponent('{{{extitle}}}'); }
  5788. if (title == '') { title = "MeshCentral"; }
  5789. if (isNaN(timeout)) { timeout = 2; }
  5790. if (op == 1) { // MessageBox
  5791. for (var i = 0; i < chkNodeIds.length; i++) { meshserver.send({ action: 'msg', type: 'messagebox', nodeid: chkNodeIds[i], title: title, msg: msg, timeout: (timeout * 60000) }); }
  5792. } else if (op == 2) { // Toast
  5793. meshserver.send({ action: 'toast', nodeids: chkNodeIds, title: title, msg: msg });
  5794. } else if (op == 3) { // Old Style MessageBox
  5795. for (var i = 0; i < chkNodeIds.length; i++) { meshserver.send({ action: 'msg', type: 'alertbox', nodeid: chkNodeIds[i], title: title, msg: msg }); }
  5796. }
  5797. }
  5798. function d2groupActionFunctionTagsExec() {
  5799. var chkNodeIds = getCheckedDevices(), op = Q('d2deviceop').value, optags = Q('dp10devicevalue').value;
  5800. if (op == 2) { // Set tags
  5801. for (var i in chkNodeIds) { meshserver.send({ action: 'changedevice', nodeid: chkNodeIds[i], tags: optags }); }
  5802. } else {
  5803. var taggroup = [];
  5804. optags = optags.split(',');
  5805. for (var i in optags) { var tname = optags[i].trim(); if ((tname.length > 0) && (tname.length < 64) && (taggroup.indexOf(tname) == -1)) { taggroup.push(tname); } }
  5806. for (var i in chkNodeIds) {
  5807. var nodeTags = null, tagChanges = false, n = getNodeFromId(chkNodeIds[i]);
  5808. if (n.tags != null) { nodeTags = Clone(n.tags); }
  5809. if (nodeTags == null) { nodeTags = []; }
  5810. for (var j = 0; j < optags.length; j++) {
  5811. if ((op == 1) && (nodeTags.indexOf(optags[j]) == -1)) { nodeTags.push(optags[j]); tagChanges = true; } // Add new tags
  5812. if (op == 3) { var k = nodeTags.indexOf(optags[j]); if (k >= 0) { nodeTags.splice(k, 1); tagChanges = true; } } // Remove tags
  5813. }
  5814. if (tagChanges) { meshserver.send({ action: 'changedevice', nodeid: chkNodeIds[i], tags: nodeTags.join(',') }); }
  5815. }
  5816. }
  5817. }
  5818. function p2downloadDeviceInfo() {
  5819. if (xxdialogMode) return;
  5820. var chkNodeIds = getCheckedDevices(), intelamtpresent = false;
  5821. for (var i in chkNodeIds) { if (getNodeFromId(chkNodeIds[i]).intelamt != null) { intelamtpresent = true; } }
  5822. var x = "Download the list of devices with one of the file formats below." + '<br /><br />';
  5823. x += addHtmlValue("CSV Format", '<a href=# style=cursor:pointer onclick=\'return p2downloadDeviceInfoCSV()\'>' + "devicelist.csv" + '</a>');
  5824. x += addHtmlValue("JSON Format", '<a href=# style=cursor:pointer onclick=\'return p2downloadDeviceInfoJSON()\'>' + "devicelist.json" + '</a>');
  5825. if (intelamtpresent) { x += addHtmlValue("Intel&reg; AMT JSON", '<a href=# style=cursor:pointer onclick=\'return p2downloadAmtInfoJSON()\'>' + "computerlist.json" + '</a>'); }
  5826. x += '<div style=margin-top:8px><label><input type=checkbox id=d2DevInfoDetailsCheck />' + "Include device details" + '</label></div>';
  5827. setDialogMode(2, "Device Information Export", 1, null, x);
  5828. }
  5829. function csvClean(v) {
  5830. if (v == null) return '';
  5831. return v.split(',').join('');
  5832. }
  5833. function p2downloadDeviceInfoCSV() {
  5834. var chkNodeIds = getCheckedDevices();
  5835. if (Q('d2DevInfoDetailsCheck').checked) {
  5836. var tz = null;
  5837. try { tz = Intl.DateTimeFormat().resolvedOptions().timeZone; } catch (ex) {}
  5838. meshserver.send({ action: 'getDeviceDetails', nodeids: chkNodeIds, tz: tz, tf: new Date().getTimezoneOffset(), l: getLang(), type: 'csv' }); // With details
  5839. } else {
  5840. // Without details
  5841. var csv = "id,name,rname,host,icon,ip,osdesc,state,groupname,conn,pwr,av,update,firewall,bitlocker,avdetails,tags,lastbootuptime" + '\r\n', r = [];
  5842. for (var i in chkNodeIds) {
  5843. var n = getNodeFromId(chkNodeIds[i]);
  5844. csv += '"' + n._id.split(',').join('') + '","' + n.name.split(',').join('') + '","' + (n.rname?(n.rname.split(',').join('')):'') + '","' + (n.host?(n.host.split(',').join('')):'') + '","' + n.icon + '","' + (n.ip?n.ip:'') + '","' + (n.osdesc?(n.osdesc.split(',').join('')):'') + '","' + n.state + '","' + meshes[n.meshid].name.split(',').join('') + '","' + (n.conn?n.conn:'') + '","' + (n.pwr?n.pwr:'') + '"';
  5845. if (typeof n.wsc == 'object') { csv += ',"' + csvClean(n.wsc.antiVirus) + '","' + csvClean(n.wsc.autoUpdate) + '","' + csvClean(n.wsc.firewall) + '"'; } else { csv += ',,,'; }
  5846. if (typeof n.volumes == 'object') {
  5847. var bitlockerdetails = '', firstbitlocker = true;
  5848. for (var a in n.volumes) { if (typeof n.volumes[a].protectionStatus !== 'undefined') { if (firstbitlocker) { firstbitlocker = false; } else { bitlockerdetails += '|'; } bitlockerdetails += a + '/' + n.volumes[a].volumeStatus; } }
  5849. csv += ',"' + csvClean(bitlockerdetails) + '"'; }
  5850. else {
  5851. csv += ',';
  5852. }
  5853. if (typeof n.av == 'object') {
  5854. var avdetails = '', firstav = true;
  5855. for (var a in n.av) { if (typeof n.av[a].product == 'string') { if (firstav) { firstav = false; } else { avdetails += '|'; } avdetails += (n.av[a].product + '/' + ((n.av[a].enabled)?'enabled':'disabled') + '/' + ((n.av[a].updated)?'updated':'notupdated')); } }
  5856. csv += ',"' + csvClean(avdetails) + '"'; }
  5857. else {
  5858. csv += ',';
  5859. }
  5860. if (typeof n.tags == 'object') {
  5861. var tagsdetails = '', firsttags = true;
  5862. for (var a in n.tags) { if (firsttags) { firsttags = false; } else { tagsdetails += '|'; } tagsdetails += n.tags[a]; }
  5863. csv += ',"' + csvClean(tagsdetails) + '"'; }
  5864. else {
  5865. csv += ',';
  5866. }
  5867. if (typeof n.lastbootuptime == 'number') { csv += ',"' + n.lastbootuptime + '"'; }
  5868. csv += '\r\n';
  5869. }
  5870. saveAs(stringToUtf8Blob(csv), "devicelist.csv");
  5871. }
  5872. return false;
  5873. }
  5874. // Download information about selected Intel AMT devices.
  5875. function p2downloadAmtInfoJSON() {
  5876. var chkNodeIds = getCheckedDevices();
  5877. var r = { webappversion: '0.9.0', computers: [ ] };
  5878. for (var i in chkNodeIds) {
  5879. var node = getNodeFromId(chkNodeIds[i]);
  5880. if (node.intelamt == null) continue;
  5881. var c = { name: node.name, host: node.host, user: node.intelamt.user, pass: '', tls: (node.intelamt.tls == 1)?1:0, ver: node.intelamt.ver, pstate: node.intelamt.state }
  5882. if (node.intelamt.state == 2) { c.pmode = 1; } // TODO
  5883. if (node.intelamt.realm) { c.digestrealm = node.intelamt.realm; }
  5884. if (node.intelamt.hash) { c.tlscerthash = node.intelamt.hash; }
  5885. r.computers.push(c);
  5886. }
  5887. saveAs(new Blob([JSON.stringify(r, null, 2)], { type: 'application/octet-stream' }), "computerlist.json");
  5888. }
  5889. function p2downloadDeviceInfoJSON() {
  5890. var chkNodeIds = getCheckedDevices();
  5891. if (Q('d2DevInfoDetailsCheck').checked) {
  5892. var tz = null;
  5893. try { tz = Intl.DateTimeFormat().resolvedOptions().timeZone; } catch (ex) {}
  5894. meshserver.send({ action: 'getDeviceDetails', nodeids: chkNodeIds, tz: tz, tf: new Date().getTimezoneOffset(), l: getLang(), type: 'json' }); // With details
  5895. } else {
  5896. // Without details
  5897. var r = [];
  5898. for (var i in chkNodeIds) { r.push(getNodeFromId(chkNodeIds[i])); }
  5899. saveAs(new Blob([JSON.stringify(r, null, 2)], { type: 'application/octet-stream' }), "devicelist.json");
  5900. }
  5901. return false;
  5902. }
  5903. function d2groupActionFunctionDelCheck() { QE('idx_dlgOkButton', Q('d2check').checked); }
  5904. function d2groupActionFunctionDelExec() { meshserver.send({ action: 'removedevices', nodeids: getCheckedDevices() }); uncheckAllDevices(); }
  5905. function d2runCommandDialog(options) {
  5906. var wintype = false, linuxtype = false, agenttype = false;
  5907. for (var i in options.nodeids) {
  5908. var n = getNodeFromId(options.nodeids[i]);
  5909. if (n.agent) { if ((GetNodeRights(n) & 24) == 24) { agenttype = true; }
  5910. if (isWindowsNode(n)) { wintype = true; } else { linuxtype = true; } }
  5911. }
  5912. if ((wintype == true) || (linuxtype == true) || (agenttype == true)) {
  5913. // Fetch run options
  5914. var runopt = { type:1, runAs:0, source:1, cmd:'' };
  5915. try { runopt = JSON.parse(getstore('runopt', runopt)); } catch (ex) {}
  5916. if (options.selectedFile) {
  5917. var filename = options.selectedFile.name.toLowerCase();
  5918. console.log('filename', filename);
  5919. if (filename.endsWith('.bat')) { runopt.type = 1; }
  5920. if (filename.endsWith('.ps1')) { runopt.type = 2; }
  5921. if (filename.endsWith('.sh')) { runopt.type = 3; }
  5922. if (filename.endsWith('.agentconsole')) { runopt.type = 4; }
  5923. }
  5924. var x = '';
  5925. if (options.title) { x += options.title + '<br />'; }
  5926. x += '<select id=d2cmdtype onclick=d2runCommandValidate() style=width:100%;margin-bottom:4px;margin-top:4px>';
  5927. 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>'; }
  5928. if (linuxtype == true) { x += '<option value=3' + ((runopt.type == 3)?' selected':'') + '>' + "Linux/BSD/macOS Command Shell" + '</option>'; }
  5929. if (agenttype == true) { x += '<option value=4' + ((runopt.type == 4)?' selected':'') + '>' + "Agent Console" + '</option>'; } // MESHRIGHT_REMOTECONTROL & MESHRIGHT_AGENTCONSOLE are needed
  5930. x += '</select>';
  5931. 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>';
  5932. if (options.selectedFile == null) {
  5933. 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>';
  5934. if (userinfo.siteadmin & 8) { x += '<option value=2' + ((runopt.source == 2)?' selected':'') + '>' + "Commands from file on server" + '</option>'; }
  5935. 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>';
  5936. x += '<div id=d2runfile style=display:none><input id=d2runfileex type=file onchange=d2runCommandValidate() id=d2localFile name=files onchange=d2runCommandValidate() /></div>';
  5937. 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>'; }
  5938. }
  5939. setDialogMode(2, "Run Commands", 3, d2groupActionFunctionRunCommands, x, options);
  5940. if (options.selectedFile == null) {
  5941. Q('d2runcmd').focus();
  5942. if (userinfo.siteadmin & 8) { d3fileoptions = { dialog: 2, files: 'd2serverfiles', folderup: 'p2FolderUp', currentFolder: 'p2CurrentFolder', func: null }; d3updatefiles(); } // Update the server files
  5943. }
  5944. d2runCommandValidate();
  5945. }
  5946. }
  5947. function d2runCommandValidate() {
  5948. QV('d2cmduser', Q('d2cmdtype').value < 4);
  5949. if (xxdialogTag.selectedFile == null) {
  5950. QV('d2runcmd', Q('d2cmdsource').value == 0);
  5951. QV('d2runfile', Q('d2cmdsource').value == 1);
  5952. QV('d2runsfile', Q('d2cmdsource').value == 2);
  5953. var ok = false;
  5954. if (Q('d2cmdsource').value == 0) { if (Q('d2runcmd').value.length > 0) { ok = true; } } // From text box
  5955. if (Q('d2cmdsource').value == 1) { if (Q('d2runfileex').files.length == 1) { ok = true; } } // From file
  5956. if (Q('d2cmdsource').value == 2) { ok = false; } // From server file
  5957. QE('idx_dlgOkButton', ok);
  5958. } else {
  5959. QE('idx_dlgOkButton', true);
  5960. }
  5961. }
  5962. function d2groupActionFunctionRunCommands(b, options) {
  5963. var type = 3;
  5964. try { type = parseInt(Q('d2cmdtype').value); } catch (ex) { }
  5965. 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
  5966. var cmd = { action: 'runcommands', nodeids: options.nodeids, type: type, runAsUser: parseInt(Q('d2cmduser').value) };
  5967. if (options.selectedFile) {
  5968. // Drag & drop file
  5969. var reader = new FileReader();
  5970. reader.onload = function (e) { cmd.cmds = e.target.result; meshserver.send(cmd); if (options.func) { options.func(); } }
  5971. reader.readAsText(options.selectedFile);
  5972. } else if (Q('d2cmdsource').value == 0) {
  5973. // From text box
  5974. cmd.cmds = Q('d2runcmd').value;
  5975. meshserver.send(cmd);
  5976. if (options.func) { options.func(); }
  5977. } else if (Q('d2cmdsource').value == 1) {
  5978. // From file
  5979. var reader = new FileReader();
  5980. reader.onload = function (e) { cmd.cmds = e.target.result; meshserver.send(cmd); if (options.func) { options.func(); } }
  5981. reader.readAsText(Q('d2runfileex').files[0]);
  5982. } else if (Q('d2cmdsource').value == 2) {
  5983. // From server file
  5984. var files = d3getFileSel();
  5985. if (files.length != 1) return;
  5986. cmd.cmdpath = d3filetreelocation.join('/') + '/' + files[0];
  5987. meshserver.send(cmd);
  5988. if (options.func) { options.func(); }
  5989. }
  5990. }
  5991. function onSortSelectChange(skipsave) {
  5992. sort = document.getElementById('sortselect').selectedIndex;
  5993. if (!skipsave) { putstore('sort', sort); }
  5994. }
  5995. /*
  5996. function nameSort(a, b) { var aa = a.name.toLowerCase(), bb = b.name.toLowerCase(); return sortCollator.compare(a.namel, b.namel); }
  5997. function meshSort(a, b) {
  5998. if (a.meshnamel > b.meshnamel) return 1;
  5999. if (a.meshnamel < b.meshnamel) return -1;
  6000. if (a.meshid > b.meshid) return 1;
  6001. if (a.meshid < b.meshid) return -1;
  6002. if (showRealNames == true) { if (a.rnamel > b.rnamel) return 1; if (a.rnamel < b.rnamel) return -1; return 0; }
  6003. else { if (a.namel > b.namel) return 1; if (a.namel < b.namel) return -1; }
  6004. return 0;
  6005. }
  6006. 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) { 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; }
  6007. function deviceSort(a, b) { if (a.namel > b.namel) return 1; if (a.namel < b.namel) return -1; return 0; }
  6008. function deviceHostSort(a, b) { if (a.rnamel > b.rnamel) return 1; if (a.rnamel < b.rnamel) return -1; return 0; }
  6009. function lastConnectSort(a, b) { var aa = a.lastconnect, bb = b.lastconnect; if (aa == null) { aa = 99999999999999; } if (bb == null) { bb = 99999999999999; } if (a.conn > 0) { aa = 99999999999998; } if (b.conn > 0) { bb = 99999999999998; } if (aa == bb) { return nameSort(a, b); } return (aa - bb); }
  6010. */
  6011. var sortCollator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })
  6012. function nameSort(a, b) { var aa = a.name.toLowerCase(), bb = b.name.toLowerCase(); return sortCollator.compare(aa, bb); }
  6013. function meshSort(a, b) {
  6014. var x = sortCollator.compare(a.meshnamel, b.meshnamel);
  6015. if (x != 0) return x;
  6016. x = sortCollator.compare(a.meshid, b.meshid);
  6017. if (x != 0) return x;
  6018. if (showRealNames == true) { return sortCollator.compare(a.rnamel, b.rnamel); }
  6019. return sortCollator.compare(a.namel, b.namel);
  6020. }
  6021. 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); } } }
  6022. function deviceSort(a, b) { return sortCollator.compare(a.namel, b.namel); }
  6023. function deviceHostSort(a, b) { return sortCollator.compare(a.rnamel, b.rnamel); }
  6024. function lastConnectSort(a, b) { var aa = a.lastconnect, bb = b.lastconnect; if (aa == null) { aa = 99999999999999; } if (bb == null) { bb = 99999999999999; } if (a.conn > 0) { aa = 99999999999998; } if (b.conn > 0) { bb = 99999999999998; } if (aa == bb) { return nameSort(a, b); } return (aa - bb); }
  6025. function onSearchFocus(x) { searchFocus = x; }
  6026. function clearDeviceSearch() { Q('KvmSearchInput').value = Q('SearchInput').value = ''; onOnlineCheckBox(); mainUpdate(1); }
  6027. function onMapSearchFocus(x) { mapSearchFocus = x; }
  6028. function onUserSearchFocus(x) { userSearchFocus = x; }
  6029. function onConsoleFocus(x) { consoleFocus = x; }
  6030. function parseSearchAndInput(x) {
  6031. var s = x.split(' ' + "and" + ' '), r = null;
  6032. for (var i in s) {
  6033. var r2 = getDevicesThatMatchFilter(s[i]);
  6034. if (r == null) { r = r2; } else { var r3 = []; for (var j in r2) { if (r.indexOf(r2[j]) >= 0) { r3.push(r2[j]); } } r = r3; }
  6035. }
  6036. return r;
  6037. }
  6038. function parseSearchOrInput(x) {
  6039. var s = x.split(' ' + "or" + ' '), r = null;
  6040. for (var i in s) { var r2 = parseSearchAndInput(s[i]); if (r == null) { r = r2; } else { for (var j in r2) { if (r.indexOf(r2[j] >= 0)) { r.push(r2[j]); } } } }
  6041. return r;
  6042. }
  6043. function getDevicesThatMatchFilter(x) {
  6044. var r = [];
  6045. var userSearch = null, ipSearch = null, groupSearch = null, tagSearch = null, agentTagSearch = null, wscSearch = null, osSearch = null, amtSearch = null, descSearch = null, connectivitySearch = null;
  6046. if (x.startsWith('user:'.toLowerCase())) { userSearch = x.substring('user:'.length); }
  6047. else if (x.startsWith('u:'.toLowerCase())) { userSearch = x.substring('u:'.length); }
  6048. else if (x.startsWith('ip:'.toLowerCase())) { ipSearch = x.substring('ip:'.length); }
  6049. else if (x.startsWith('group:'.toLowerCase())) { groupSearch = x.substring('group:'.length); }
  6050. else if (x.startsWith('g:'.toLowerCase())) { groupSearch = x.substring('g:'.length); }
  6051. else if (x.startsWith('tag:'.toLowerCase())) { tagSearch = x.substring('tag:'.length); }
  6052. else if (x.startsWith('t:'.toLowerCase())) { tagSearch = x.substring('t:'.length); }
  6053. else if (x.startsWith('atag:'.toLowerCase())) { agentTagSearch = x.substring('atag:'.length); }
  6054. else if (x.startsWith('a:'.toLowerCase())) { agentTagSearch = x.substring('a:'.length); }
  6055. else if (x.startsWith('os:'.toLowerCase())) { osSearch = x.substring('os:'.length); }
  6056. else if (x.startsWith('amt:'.toLowerCase())) { amtSearch = x.substring('amt:'.length); }
  6057. else if (x.startsWith('desc:'.toLowerCase())) { descSearch = x.substring('desc:'.length); }
  6058. else if (x.startsWith('connectivity:'.toLowerCase())) { connectivitySearch = x.substring('connectivity:'.length); }
  6059. else if (x.startsWith('c:'.toLowerCase())) { connectivitySearch = x.substring('c:'.length); }
  6060. else if (x == 'wsc:ok') { wscSearch = 1; }
  6061. else if (x == 'wsc:noav') { wscSearch = 2; }
  6062. else if (x == 'wsc:noupdate') { wscSearch = 3; }
  6063. else if (x == 'wsc:nofirewall') { wscSearch = 4; }
  6064. else if (x == 'wsc:any') { wscSearch = 5; }
  6065. if (x == '') {
  6066. // No search
  6067. for (var d in nodes) { r.push(d); }
  6068. } else if (ipSearch != null) {
  6069. // IP address search
  6070. for (var d in nodes) { if ((nodes[d].ip != null) && (nodes[d].ip.toLowerCase().indexOf(ipSearch) >= 0)) { r.push(d); } }
  6071. } else if (groupSearch != null) {
  6072. // Group filter
  6073. for (var d in nodes) { if (meshes[nodes[d].meshid].name.toLowerCase().indexOf(groupSearch) >= 0) { r.push(d); } }
  6074. } else if (tagSearch != null) {
  6075. // Tag filter
  6076. for (var d in nodes) {
  6077. if ((nodes[d].tags == null) && (tagSearch == '')) { r.push(d); }
  6078. else if (nodes[d].tags != null) { for (var j in nodes[d].tags) { if (nodes[d].tags[j].toLowerCase().indexOf(tagSearch) >= 0) { r.push(d); break; } }}
  6079. }
  6080. } else if (agentTagSearch != null) {
  6081. // Agent Tag filter
  6082. for (var d in nodes) {
  6083. if ((((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))) { r.push(d); };
  6084. }
  6085. } else if (userSearch != null) {
  6086. // User search
  6087. for (var d in nodes) {
  6088. 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) { r.push(d); } } }
  6089. }
  6090. } else if (osSearch != null) {
  6091. // OS search
  6092. for (var d in nodes) { if ((nodes[d].osdesc != null) && (nodes[d].osdesc.toLowerCase().indexOf(osSearch) >= 0)) { r.push(d); }; }
  6093. } else if (amtSearch != null) {
  6094. // Intel AMT search
  6095. for (var d in nodes) { if ((nodes[d].intelamt != null) && ((amtSearch == '') || (nodes[d].intelamt.state == amtSearch))) { r.push(d); } }
  6096. } else if (descSearch != null) {
  6097. // Device description search
  6098. for (var d in nodes) { if ((nodes[d].desc != null) && (nodes[d].desc != '') && ((descSearch == '') || (nodes[d].desc.toLowerCase().indexOf(descSearch) >= 0))) { r.push(d); } }
  6099. } else if (wscSearch != null) {
  6100. // Windows Security Center
  6101. for (var d in nodes) {
  6102. if (nodes[d].wsc) {
  6103. if ((wscSearch == 1) && (nodes[d].wsc.antiVirus == 'OK') && (nodes[d].wsc.autoUpdate == 'OK') && (nodes[d].wsc.firewall == 'OK')) { r.push(d); }
  6104. else if (((wscSearch == 2) || (wscSearch == 5)) && (nodes[d].wsc.antiVirus != 'OK')) { r.push(d); }
  6105. else if (((wscSearch == 3) || (wscSearch == 5)) && (nodes[d].wsc.autoUpdate != 'OK')) { r.push(d); }
  6106. else if (((wscSearch == 4) || (wscSearch == 5)) && (nodes[d].wsc.firewall != 'OK')) { r.push(d); }
  6107. }
  6108. }
  6109. } else if (connectivitySearch != null) {
  6110. // Connectivity search
  6111. for (var d in nodes) {
  6112. if (nodes[d].conn) {
  6113. if ((nodes[d].conn & 1) != 0) {
  6114. if (nodes[d].mtype == 4) {
  6115. if ((nodes[d].porttype == 'PDU') && (connectivitySearch.toLowerCase() == 'switch')) {
  6116. r.push(d);
  6117. } else if (connectivitySearch.toLowerCase() == 'ipkvm') {
  6118. r.push(d);
  6119. }
  6120. } else if (connectivitySearch.toLowerCase() == 'agent') {
  6121. r.push(d);
  6122. }
  6123. }
  6124. if (((nodes[d].conn & 2) != 0) && (connectivitySearch.toLowerCase() == 'cira')) { r.push(d); }
  6125. else if (((nodes[d].conn & 4) != 0) && (connectivitySearch.toLowerCase() == 'amt')) { r.push(d); }
  6126. if (((nodes[d].conn & 8) != 0) && (connectivitySearch.toLowerCase() == 'relay')) { r.push(d); }
  6127. if (((nodes[d].conn & 16) != 0) && (connectivitySearch.toLowerCase() == 'mqtt')) { r.push(d); }
  6128. }
  6129. if (nodes[d].mtype == 3) {
  6130. var mesh = meshes[nodes[d].meshid];
  6131. if (mesh && mesh.relayid && (connectivitySearch.toLowerCase() == 'relay')) {
  6132. r.push(d);
  6133. } else if (mesh && (typeof mesh.relayid == 'undefined') && connectivitySearch.toLowerCase() == 'local') {
  6134. r.push(d);
  6135. }
  6136. }
  6137. }
  6138. } else if (x == '*') {
  6139. // Star filter
  6140. for (var d in nodes) { if (stars[nodes[d]._id] == 1) { r.push(d); } }
  6141. } else {
  6142. // Device name search
  6143. try {
  6144. var rs = x.split(/\s+/).join('|'), rx = new RegExp(rs); // In some cases (like +), this can throw an exception.
  6145. for (var d in nodes) {
  6146. if (features2 & 0x00008000) { // Both server and client names must match
  6147. if(features2 & 0x10000000){
  6148. if (rx.test(nodes[d].name.toLowerCase()) || rx.test(meshes[nodes[d].meshid].name.toLowerCase()) || ((nodes[d].rnamel != null) && rx.test(nodes[d].rnamel.toLowerCase()))) { r.push(d); }
  6149. }else {
  6150. if (rx.test(nodes[d].name.toLowerCase()) || ((nodes[d].rnamel != null) && rx.test(nodes[d].rnamel.toLowerCase()))) { r.push(d); }
  6151. }
  6152. } else {
  6153. if(features2 & 0x10000000){
  6154. if (showRealNames) {
  6155. if (nodes[d].rnamel != null && rx.test(nodes[d].rnamel.toLowerCase()) || rx.test(meshes[nodes[d].meshid].name.toLowerCase()) ) { r.push(d); }
  6156. } else {
  6157. if (rx.test(nodes[d].name.toLowerCase()) || rx.test(meshes[nodes[d].meshid].name.toLowerCase()) ) { r.push(d); }
  6158. }
  6159. }else {
  6160. if (showRealNames) {
  6161. if (nodes[d].rnamel != null && rx.test(nodes[d].rnamel.toLowerCase())) { r.push(d); }
  6162. } else {
  6163. if (rx.test(nodes[d].name.toLowerCase())) { r.push(d); }
  6164. }
  6165. }
  6166. }
  6167. }
  6168. } catch (ex) { for (var d in nodes) { r.push(d); } }
  6169. }
  6170. return r;
  6171. }
  6172. function onSearchInputChanged() {
  6173. var x = Q('SearchInput').value.toLowerCase().trim(); putstore('_search', Q('SearchInput').value);
  6174. QS('SearchInput')['background-color'] = QS('KvmSearchInput')['background-color'] = (x == '')?null:'#FDFFBE';
  6175. QV('SearchInputClearButton', (x != ''));
  6176. QV('KvmSearchInputClearButton', (x != ''));
  6177. var r = parseSearchOrInput(x);
  6178. for (var d in nodes) { nodes[d].v = (r.indexOf(d) >= 0) }
  6179. // Check filter dropdown
  6180. var devFilter = Q('DevFilterSelect').value;
  6181. if (devFilter == 1) { for (var d in nodes) { if (((nodes[d].conn == null) || (nodes[d].conn == 0)) && (nodes[d].mtype != 3)) { nodes[d].v = false; } } } // Online, also include Local devices and relayed devices
  6182. if (devFilter == 2) { for (var d in nodes) { var n = nodes[d]; if ((n.sessions == null) || ((n.sessions.kvm == null) && (n.sessions.terminal == null) && (n.sessions.files == null) && (n.sessions.tcp == null) && (n.sessions.udp == null))) { n.v = false; } } } // Sessions
  6183. if (devFilter == 3) { for (var d in nodes) { if (stars[nodes[d]._id] != 1) { nodes[d].v = false; } } } // Starred
  6184. if (devFilter == 4) { for (var d in nodes) { if (nodes[d].intelamt == null) { nodes[d].v = false; } } } // Intel AMT
  6185. if (devFilter == 5) { for (var d in nodes) { if ((nodes[d].conn != null) && (nodes[d].conn != 0)) { nodes[d].v = false; } } } // Offline
  6186. if (devFilter == 6) { for (var d in nodes) { var n = nodes[d]; if ((n.sessions == null) || (n.sessions.help == null)) { n.v = false; } } } // Sessions
  6187. if (devFilter == 7) { for (var d in nodes) { var n = nodes[d]; if ((n.tags == null) || (n.tags.length == 0)) { n.v = false; } } } // Tagged
  6188. if (devFilter == 8) { for (var d in nodes) { var n = nodes[d]; if ((n.tags != null) && (n.tags.length > 0)) { n.v = false; } } } // Untagged
  6189. }
  6190. var contextelement = null;
  6191. function handleContextMenu(event) {
  6192. // When called, we look for elements with "cmenu=xxx" and show the right context menu for that element.
  6193. hideContextMenu();
  6194. if (xxdialogMode) return;
  6195. var scrollLeft = (window.pageXOffset !== null) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
  6196. var scrollTop = (window.pageYOffset !== null) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
  6197. var elem = document.elementFromPoint(event.pageX - scrollLeft, event.pageY - scrollTop);
  6198. while (elem && elem != null && elem.attributes.cmenu == null) { elem = elem.parentElement; } // Go up until element with context menu or root is reached.
  6199. if (elem == null) return true; // No "cmenu=xxx" found at the element that was clicked.
  6200. var cmenu = elem.attributes.cmenu.value;
  6201. switch (cmenu) {
  6202. case 'filesConnectButton': {
  6203. // Files connect button context menu
  6204. if ((currentNode == null) || (currentNode.agent == null)) return true;
  6205. contextelement = elem;
  6206. showContextMenuDiv(document.getElementById('filesShellContextMenu'), event.pageX, event.pageY);
  6207. break;
  6208. }
  6209. case 'termConnectButton': {
  6210. // Terminal connect button context menu
  6211. if ((currentNode == null) || (currentNode.agent == null) || (currentNode.mtype == 3)) return true;
  6212. // If the server has a specific terminal shell mode, don't show connection options
  6213. if (serverinfo.linuxshell && (currentNode.agent.id > 4)) return;
  6214. contextelement = elem;
  6215. var contextmenudiv = document.getElementById('termShellContextMenu'); // Windows options (Power Shell)
  6216. if (currentNode.agent.id > 4 && !isWindowsNode(currentNode)) { contextmenudiv = document.getElementById('termShellContextMenuLinux'); } // Non-Windows options
  6217. if (currentNode.agent.id == 34) { contextmenudiv = document.getElementById('termShellContextMenu2'); } // Windows MeshCentral Assistant
  6218. showContextMenuDiv(contextmenudiv, event.pageX, event.pageY);
  6219. break;
  6220. }
  6221. case 'deskConnectButton': {
  6222. // Desktop connect button context menu
  6223. if ((currentNode == null) || (currentNode.agent == null)) return true;
  6224. contextelement = elem;
  6225. showContextMenuDiv(document.getElementById('deskConnectContextMenu'), event.pageX, event.pageY);
  6226. break;
  6227. }
  6228. case 'deskDisconnectButton': {
  6229. // Desktop disconnect button context menu
  6230. if ((currentNode == null) || (currentNode.agent == null)) return true;
  6231. contextelement = elem;
  6232. showContextMenuDiv(document.getElementById('deskDisconnectContextMenu'), event.pageX, event.pageY);
  6233. break;
  6234. }
  6235. case 'devsContentMenu': {
  6236. // Device content menu
  6237. contextelement = elem;
  6238. var contextmenudiv = document.getElementById('contextMenu');
  6239. showContextMenuDiv(contextmenudiv, event.pageX, event.pageY);
  6240. // Get the node and set the menu options
  6241. var nodeid;
  6242. if (Q('viewselect').value == 1) {
  6243. nodeid = contextelement.children[0].children[0].children[1].children[0].attributes.onmouseup.value;
  6244. } else {
  6245. nodeid = contextelement.children[1].attributes.onmouseup.value;
  6246. }
  6247. var node = getNodeFromId(nodeid.substring(12, nodeid.length - 18));
  6248. var rights = GetNodeRights(node);
  6249. var consoleRights = ((rights & 16) != 0);
  6250. // Check if we have desktop, terminal and file access
  6251. var desktopAccess = ((rights == 0xFFFFFFFF) || ((rights & 65536) == 0));
  6252. var terminalAccess = ((rights == 0xFFFFFFFF) || ((rights & 512) == 0));
  6253. var fileAccess = ((rights == 0xFFFFFFFF) || ((rights & 1024) == 0));
  6254. QV('cxdesktop', ((node.conn & 1) != 0) && (node.mtype != 4) && ((node.mtype == 1) || (node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 1) != 0) || (node.intelamt && (node.intelamt.state == 2))) && ((rights & 8) || (rights & 256)) && desktopAccess);
  6255. QV('cxterminal', ((node.conn & 1) != 0) && (node.mtype != 4) && ((node.mtype == 1) || (node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 2) != 0) || (node.intelamt && (node.intelamt.state == 2))) && (rights & 8) && terminalAccess);
  6256. QV('cxfiles', (node.mtype != 4) && ((node.mtype == 2) && ((node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 4) != 0))) && (rights & 8) && fileAccess);
  6257. QV('cxevents', (node.intelamt != null) && ((node.intelamt.state == 2) || (node.conn & 2)) && (rights & 8));
  6258. QV('cxdetails', node.mtype < 3);
  6259. QV('cxwebvnc', ((((node.conn & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((rights & 8) != 0) && ((features & 0x20000000) == 0)));
  6260. QV('cxwebrdp', ((((node.conn & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((rights & 8) != 0) && ((features & 0x40000000) == 0)));
  6261. QV('cxwebssh', ((features2 & 0x200) && (((node.conn & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((rights & 8) != 0)));
  6262. QV('cxconsole', (consoleRights && (node.mtype == 2) && ((node.agent == null) || (node.agent.caps == null) || ((node.agent.caps & 8) != 0))) && (rights & 8));
  6263. QV('cxrdp', false); // always have the RDP hidden
  6264. if ((((node.conn & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((rights & 8) != 0)) {
  6265. if ((node.agent.id > 0) && (node.agent.id < 5)) {
  6266. if (navigator.platform.toLowerCase() == 'win32') {
  6267. if ((serverinfo.devicemeshrouterlinks == null) || (serverinfo.devicemeshrouterlinks.rdp != false)) {
  6268. QV('cxrdp', true);
  6269. }
  6270. }
  6271. }
  6272. }
  6273. QV('cxmgroupsplit', true);
  6274. QV('cxstar', true);
  6275. break;
  6276. }
  6277. default: {
  6278. // Basic context menu
  6279. if ((contextmenudiv = document.getElementById(cmenu)) == null) return true;
  6280. contextelement = elem;
  6281. showContextMenuDiv(contextmenudiv, event.pageX, event.pageY);
  6282. break;
  6283. }
  6284. }
  6285. return haltEvent(event);
  6286. }
  6287. function showContextMenuDiv(element, x, y) {
  6288. var clientRect = document.documentElement.getBoundingClientRect();
  6289. var docHeight = clientRect.height;
  6290. var docWidth = clientRect.width;
  6291. element.style.left = element.style.right = element.style.top = element.style.bottom = null;
  6292. if (x > (docWidth / 2)) { element.style.right = (docWidth - event.pageX) + 'px'; } else { element.style.left = event.pageX + 'px'; }
  6293. if (y > (docHeight / 2)) { element.style.bottom = (docHeight - event.pageY) + 'px'; } else { element.style.top = event.pageY + 'px'; }
  6294. element.style.display = 'block';
  6295. }
  6296. function cmaction(action, event) {
  6297. var nodeid;
  6298. if (Q('viewselect').value == 1) {
  6299. nodeid = contextelement.children[0].children[0].children[1].children[0].attributes.onmouseup.value;
  6300. } else {
  6301. nodeid = contextelement.children[1].attributes.onmouseup.value;
  6302. }
  6303. nodeid = nodeid.substring(12, nodeid.length - 18);
  6304. if (action == 9) { Q('viewselect').value = 3; Q('viewselect').onchange(); Q('autoConnectDesktopCheckbox').checked = true; Q('autoConnectDesktopCheckbox').onclick(); } // Multi-Desktop
  6305. if ((action > 0) && (action < 9)) {
  6306. var panel = [0, 10, 12, 11, 13, 16, 17, 15, 19][action]; // (invalid), General, Desktop, Terminal, Files, Events, Console, Plugin
  6307. if (event && (event.shiftKey == true)) {
  6308. // Open the device in a different tab
  6309. safeNewWindow(window.location.origin + '?node=' + nodeid.split('/')[2] + '&viewmode=' + panel + '&hide=16' + ((urlargs.key)?('&key=' + urlargs.key):''), 'meshcentral:' + nodeid);
  6310. } else {
  6311. // Go to the right panel
  6312. gotoDevice(nodeid, panel);
  6313. // If possible, connect...
  6314. var mesh = meshes[currentNode.meshid];
  6315. if ((currentNode.conn & 1) && (mesh.mtype == 2)) {
  6316. if ((panel == 11) && (desktop == null) && (currentNode.agent.caps & 1)) { connectDesktop(null, 3); } // Desktop
  6317. if ((panel == 12) && (terminal == null) && (currentNode.agent.caps & 2)) { connectTerminal(null, 1); } // Terminal
  6318. if ((panel == 13) && (files == null)) { connectFiles(null); } // files
  6319. }
  6320. }
  6321. }
  6322. if (action == 10) {
  6323. // Toggle star
  6324. var elements = document.getElementsByClassName('DeviceCheckbox'), selectedDevices = [];
  6325. for (var i = 0; i < elements.length; i++) { if (elements[i].checked === true) { selectedDevices.push(elements[i].defaultValue.substring(6)); } }
  6326. if (selectedDevices.length == 0) { selectedDevices.push(nodeid); }
  6327. for (var i in selectedDevices) { if (stars[selectedDevices[i]] != null) { delete stars[selectedDevices[i]]; delete selectedDevices[i]; } }
  6328. var starcount = Object.keys(stars).length;
  6329. for (var i in selectedDevices) { if ((starcount < 200) && (stars[selectedDevices[i]] == null)) { stars[selectedDevices[i]] = 1; starcount++; } }
  6330. putstore('stars', JSON.stringify(stars));
  6331. updateDeviceViewDevice(nodeid);
  6332. if (Q('DevFilterSelect').value == 3) { mainUpdate(1); }
  6333. }
  6334. else if (action == 11) { p10mstsc(nodeid); } // Web-RDP
  6335. else if (action == 12) { p10rfb(nodeid); } // Web-VNC
  6336. else if (action == 13) { p10ssh(nodeid); } // Web-SSH
  6337. else if (action == 14) { p10MCRouter(nodeid,3); } // RDP
  6338. }
  6339. function cmmeshaction(action) {
  6340. var meshid = contextelement.attributes.onclick.value.substring(10, contextelement.attributes.onclick.value.length - 2);
  6341. var elements = document.getElementsByClassName('DeviceCheckbox');
  6342. if ((action == 1) || (action == 2)) {
  6343. // Change the visible check boxes
  6344. for (var i = 0; i < elements.length; i++) {
  6345. if ((elements[i].attributes) && (elements[i].attributes['class']['value'].split(' ')[0] == meshid)) { elements[i].checked = (action == 1); }
  6346. }
  6347. // Update the list of checked nodes
  6348. if (action == 1) {
  6349. for (var i in nodes) { if ((nodes[i].meshid == meshid) && (nodes[i].v == true)) { checkedNodeids[nodes[i]._id] = 1; } }
  6350. } else if (action == 2) {
  6351. for (var i in checkedNodeids) {
  6352. var n = getNodeFromId(i);
  6353. if ((n != null) && (n.meshid == meshid) && (n.v == true)) { delete checkedNodeids[i]; }
  6354. }
  6355. }
  6356. }
  6357. //if (action == 3) { window.location = "multidesktop.aspx?mesh=" + meshid + "&auto=1"; }
  6358. p1updateInfo();
  6359. }
  6360. function cmconnectfilesaction() {
  6361. connectFiles(null, 1, 0x0020);
  6362. }
  6363. function cmtermaction(action, consent) {
  6364. if (action < 100) {
  6365. connectTerminal(null, 1, { protocol: action, consent: consent });
  6366. } else if (action == 100) {
  6367. connectTerminal(null, 1, { protocol: 1, requireLogin: true, consent: consent });
  6368. }
  6369. }
  6370. function cmdeskaction(action) {
  6371. if (action == 1) { connectDesktop(null, 3, null, 0x0008 + 0x0040); } // Consent Prompt + Privacy bar
  6372. if (action == 2) { connectDesktop(null, 3, null, 0x0008); } // Consent Prompt
  6373. if (action == 3) { connectDesktop(null, 3, null, 0x0040); } // Privacy bar
  6374. if (action == 10) { if (desktop != null) { desktop.sendCtrlMsg('{"ctrlChannel":"102938","type":"autolock","value":true}'); connectDesktop(); } } // Disconnect and lock
  6375. if (action == 11) { if (desktop != null) { desktop.sendCtrlMsg('{"ctrlChannel":"102938","type":"autolock","value":false}'); connectDesktop(); } } // Disconnect without lock
  6376. }
  6377. function cmaltportaction(action) {
  6378. if (xxdialogMode) return;
  6379. var x = "RDP remote connection port:" + '<br /><br /><input type=text placeholder="3389" inputmode="numeric" pattern="[0-9]*" onkeypress="return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)" maxlength=5 id=d10rdpport type=text>';
  6380. setDialogMode(2, "RDP Connection", 3, function() {
  6381. // Save the new RDP port to the server
  6382. var rdpport = ((Q('d10rdpport').value.length > 0) ? parseInt(Q('d10rdpport').value) : 3389);
  6383. meshserver.send({ action: 'changedevice', nodeid: currentNode._id, rdpport: rdpport });
  6384. //if (currentNode != null) { p10MCRouter(currentNode._id, 3, rdpport); }
  6385. }, x, currentNode);
  6386. Q('d10rdpport').focus();
  6387. if (currentNode.rdpport != null) { Q('d10rdpport').value = currentNode.rdpport; }
  6388. }
  6389. function cmsshportaction(action) {
  6390. if (xxdialogMode) return;
  6391. 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>';
  6392. setDialogMode(2, "SSH Connection", 3, function() {
  6393. // Save the new SSH port to the server
  6394. var sshport = ((Q('d10sshport').value.length > 0) ? parseInt(Q('d10sshport').value) : 22);
  6395. meshserver.send({ action: 'changedevice', nodeid: currentNode._id, sshport: sshport });
  6396. //if (currentNode != null) { p10MCRouter(currentNode._id, 3, sshport); }
  6397. }, x, currentNode);
  6398. Q('d10sshport').focus();
  6399. if (currentNode.sshport != null) { Q('d10sshport').value = currentNode.sshport; }
  6400. }
  6401. function cmrfbportaction(action) {
  6402. if (xxdialogMode) return;
  6403. var x = "VNC remote connection port:" + '<br /><br /><input type=text placeholder="5900" inputmode="numeric" pattern="[0-9]*" onkeypress="return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)" maxlength=5 id=d10rfbport type=text>';
  6404. setDialogMode(2, "VNC Connection", 3, function() {
  6405. // Save the new RFB port to the server
  6406. var rfbport = ((Q('d10rfbport').value.length > 0) ? parseInt(Q('d10rfbport').value) : 3389);
  6407. meshserver.send({ action: 'changedevice', nodeid: currentNode._id, rfbport: rfbport });
  6408. //if (currentNode != null) { p10rfb(currentNode._id, rfbport); }
  6409. }, x, currentNode);
  6410. Q('d10rfbport').focus();
  6411. if (currentNode.rfbport != null) { Q('d10rfbport').value = currentNode.rfbport; }
  6412. }
  6413. function cmhttpportaction(action) {
  6414. if (xxdialogMode) return;
  6415. var x = "HTTP remote connection port:" + '<br /><br /><input type=text placeholder="80" inputmode="numeric" pattern="[0-9]*" onkeypress="return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)" maxlength=5 id=d10httpport type=text>';
  6416. setDialogMode(2, "HTTP Connection", 3, function() {
  6417. // Save the new HTTP port to the server
  6418. var httpport = ((Q('d10httpport').value.length > 0) ? parseInt(Q('d10httpport').value) : 80);
  6419. meshserver.send({ action: 'changedevice', nodeid: currentNode._id, httpport: httpport });
  6420. //if (currentNode != null) { p10rfb(currentNode._id, httpport); }
  6421. }, x, currentNode);
  6422. Q('d10httpport').focus();
  6423. if (currentNode.httpport != null) { Q('d10httpport').value = currentNode.httpport; }
  6424. }
  6425. function cmhttpsportaction(action) {
  6426. if (xxdialogMode) return;
  6427. var x = "HTTPS remote connection port:" + '<br /><br /><input type=text placeholder="443" inputmode="numeric" pattern="[0-9]*" onkeypress="return (event.keyCode == 8) || (event.charCode >= 48 && event.charCode <= 57)" maxlength=5 id=d10httpsport type=text>';
  6428. setDialogMode(2, "HTTPS Connection", 3, function() {
  6429. // Save the new HTTP port to the server
  6430. var httpsport = ((Q('d10httpsport').value.length > 0) ? parseInt(Q('d10httpsport').value) : 443);
  6431. meshserver.send({ action: 'changedevice', nodeid: currentNode._id, httpsport: httpsport });
  6432. //if (currentNode != null) { p10rfb(currentNode._id, httpsport); }
  6433. }, x, currentNode);
  6434. Q('d10httpsport').focus();
  6435. if (currentNode.httpsport != null) { Q('d10httpsport').value = currentNode.httpsport; }
  6436. }
  6437. function cmfilesaction(action) {
  6438. if (xxdialogMode) return;
  6439. var filetreexx = p13sort_files(p13filetree.dir);
  6440. var file = filetreexx[parseInt(contextelement.attributes.fileindex.nodeValue)];
  6441. if (action == 1) { // Rename the file
  6442. setDialogMode(2, "Rename", 3, p13renamefileEx, '<input type=text id=p13renameinput maxlength=64 onkeyup=p13fileNameCheck(event) style=width:100% value="' + file.n + '" />', { action: 'rename', path: p13filetreelocation.join('/'), oldname: file.n});
  6443. focusTextBox('p13renameinput');
  6444. p13fileNameCheck();
  6445. } else if (action == 2) { // Edit the file
  6446. if (file.s <= 204800) {
  6447. p13downloadfile(encodeURIComponentEx(p13filetreelocation.join('/') + '/' + file.n), encodeURIComponentEx(file.n), file.s, 'viewer');
  6448. } else { messagebox("File Editor", "Only files less than 200k can be edited."); }
  6449. } else if (action == 3) { // Delete the file
  6450. setDialogMode(2, "Delete", 3, p13deletefileCm, "Delete item?", file);
  6451. }
  6452. }
  6453. function cmexpandaction(action) {
  6454. if (action == 1) {
  6455. CollapsedGroups = {};
  6456. } else {
  6457. if (sort == 0) { CollapsedGroups = {}; for (var i in meshes) { CollapsedGroups[i] = true; } for (var i in nodes) { if (meshes[nodes[i].meshid] == null) { CollapsedGroups[nodes[i].meshid] = true; } } }
  6458. if (sort == 1) { CollapsedGroups = {}; for (var i = 0; i < 10; i++) { CollapsedGroups['pwr:' + i] = true; } }
  6459. if (sort == 3) { CollapsedGroups = {}; for (var i in nodes) { var node = nodes[i]; if (node.tags) { for (var j in node.tags) { CollapsedGroups['tag:' + encodeURIComponentEx(node.tags[j])] = true; } } } }
  6460. if (sort == 4) {
  6461. CollapsedGroups = {};
  6462. for (var i in nodes) {
  6463. var node = nodes[i];
  6464. var mesh2 = meshes[node.meshid];
  6465. if (mesh2) {
  6466. CollapsedGroups['tag:' + encodeURIComponentEx(mesh2.name)] = true;
  6467. if (node.tags) { for (var j in node.tags) { CollapsedGroups['tag:' + encodeURIComponentEx(mesh2.name + ' - ' + node.tags[j])] = true; } }
  6468. } else {
  6469. if (node.tags) { for (var j in node.tags) { CollapsedGroups['tag:' + encodeURIComponentEx('**INDV*~*DEVS** - ' + node.tags[j])] = true; } }
  6470. }
  6471. }
  6472. }
  6473. }
  6474. putstore('_collapse', JSON.stringify(CollapsedGroups));
  6475. updateCollapseAllButton();
  6476. mainUpdate(4);
  6477. }
  6478. function cmdeskplayeraction(action) {
  6479. if (xxdialogMode) return;
  6480. safeNewWindow(window.location.origin + '{{{domainurl}}}player.htm' + ((urlargs.key)?('?key=' + urlargs.key):''), 'meshcentral-deskplayer');
  6481. }
  6482. function cmdeskshortcutaction(action) {
  6483. if (xxdialogMode) return;
  6484. deskCustomizeKeys();
  6485. }
  6486. function cmdeskpreconfigtypeaction(action) {
  6487. if (xxdialogMode) return;
  6488. if (action == -1) { deskCustomizeStrings(); }
  6489. else if (action < 1000) { showDeskTypeEx(serverinfo.preConfiguredRemoteInput[action].value); } // Type server pre-configured input string
  6490. else { showDeskTypeEx(deskKeyboardStrings[action - 1000].v); } // Type user pre-configured input string
  6491. }
  6492. function cmdeskpreconfigscriptaction(action) {
  6493. meshserver.send({ action: 'runcommands', nodeids: [ currentNode._id ], presetcmd: action });
  6494. }
  6495. function p13deletefileCm(b, file) {
  6496. files.sendText({ action: 'rm', reqid: 1, path: p13filetreelocation.join('/'), delfiles: [ file.n ], rec: false });
  6497. p13folderup(999);
  6498. }
  6499. /*
  6500. function pluginTabClose() {
  6501. var pluginTab = contextelement;
  6502. var pname = pluginTab.getAttribute('x-data-plugin-sname');
  6503. var pdiv = Q('plugin-'+pname);
  6504. pdiv.parentNode.removeChild(pdiv);
  6505. pluginTab.parentNode.removeChild(pluginTab);
  6506. QV('p42', true);
  6507. goPlugin(-1);
  6508. }
  6509. */
  6510. function hideContextMenu() {
  6511. QV('contextMenu', false);
  6512. QV('meshContextMenu', false);
  6513. QV('filesShellContextMenu', false);
  6514. QV('termShellContextMenu', false);
  6515. QV('termShellContextMenu2', false);
  6516. QV('termShellContextMenuLinux', false);
  6517. QV('deskConnectContextMenu', false);
  6518. QV('deskDisconnectContextMenu', false);
  6519. QV('altPortContextMenu', false);
  6520. QV('rfbPortContextMenu', false);
  6521. QV('sshPortContextMenu', false);
  6522. QV('httpPortContextMenu', false);
  6523. QV('httpsPortContextMenu', false);
  6524. QV('filesContextMenu', false);
  6525. QV('deskPlayerContextMenu', false);
  6526. QV('deskKeyShortcutContextMenu', false);
  6527. QV('deskPreConfigShortcutContextMenu', false);
  6528. QV('deskPreConfigScriptContextMenu', false);
  6529. QV('expandAllContextMenu', false);
  6530. //QV('pluginTabContextMenu', false);
  6531. contextelement = null;
  6532. }
  6533. //
  6534. // DEVICES MAP
  6535. //
  6536. // Maps code starts from here. Initialize all the variables
  6537. var xxmap = {
  6538. map: null,
  6539. contextmenu: null,
  6540. activeInteractions: [], // Save Modified features in this list
  6541. showindex: 0,
  6542. markersSource: null, // Initialize a Source Vector
  6543. markersLayer: null,
  6544. mapLayer: null, // Create a tile and use OSM source
  6545. mapView: null, // Sets the initial view
  6546. }
  6547. // Add a feature for every Node and change style if connection status changes
  6548. function updateMapMarkers(selectedMesh) {
  6549. if ((features & 0x00008000) == 0) return;
  6550. if ((xxmap != null) && (xxmap.map == null)) { try { loadmap(); } catch (ex) { console.error('loadmap() exception', ex); } }
  6551. if (xxmap == null) return;
  6552. var boundingBox = null;
  6553. for (var i in nodes) {
  6554. try {
  6555. var loc = map_parseNodeLoc(nodes[i]), feature = xxmap.markersSource.getFeatureById(nodes[i]._id);
  6556. if ((loc != null) && ((nodes[i].meshid == selectedMesh) || (selectedMesh == null))) { // Draw markers for devices with locations
  6557. var lat = loc[0], lon = loc[1], type = loc[2];
  6558. if (boundingBox == null) { boundingBox = [ lat, lon, lat, lon, 0 ]; } else { if (lat < boundingBox[0]) { boundingBox[0] = lat; } if (lon < boundingBox[1]) { boundingBox[1] = lon; } if (lat > boundingBox[2]) { boundingBox[2] = lat; } if (lon > boundingBox[3]) { boundingBox[3] = lon; } }
  6559. if (feature == null) { addFeature(nodes[i]); boundingBox[4] = 1; } else { updateFeature(nodes[i], feature); feature.setStyle(markerStyle(nodes[i], loc[2])); } // Update Feature
  6560. } else {
  6561. if (feature) { xxmap.markersSource.removeFeature(feature); }
  6562. }
  6563. } catch (ex) { console.error('updateMapMarkers() exception', ex, JSON.stringify(nodes[i])); }
  6564. }
  6565. return boundingBox;
  6566. }
  6567. var map_cm_popup, map_cm_editMarker, map_cm_clearMarker, map_cm_saveMarker, map_cm_nodemenu_items, contextmenu_items;
  6568. if (features & 0x00008000) {
  6569. // Show node details on hovering over a feature
  6570. map_cm_popup = new ol.Overlay({ element: Q('xmap-info-window'), positioning: 'bottom-center', stopEvent: false });
  6571. // Edit Marker item
  6572. map_cm_editMarker = { text: "Modify node location", callback: function (obj) { modifyMarkerloc(obj.data); } };
  6573. // Clear Marker item
  6574. map_cm_clearMarker = {
  6575. text: "Remove node location", callback: function (obj) {
  6576. meshserver.send({ action: 'changedevice', nodeid: obj.data.a, userloc: [] }); // Clear the user position marker
  6577. }
  6578. };
  6579. // Save Marker item
  6580. map_cm_saveMarker = { text: "Save node location", callback: function (obj) { saveMarkerloc(obj.data); } };
  6581. // Build a context menu for a feature
  6582. map_cm_nodemenu_items = [
  6583. { text: "General information", callback: function (obj) { if (obj.data != null) { gotoDevice(obj.data, 10); } } },
  6584. { text: "Desktop", callback: function (obj) { if (obj.data != null) { gotoDevice(obj.data, 11); } } },
  6585. { text: "Terminal", callback: function (obj) { if (obj.data != null) { gotoDevice(obj.data, 12); } } },
  6586. { text: "Intel&reg; AMT", callback: function (obj) { if (obj.data != null) { gotoDevice(obj.data, 14); } } },
  6587. '-',
  6588. { text: "Zoom-in to extent", callback: function (obj) { var coords = obj.data.getGeometry().getCoordinates(); zoomToLocation(coords, 19); } },
  6589. { text: "Zoom-out to extent", callback: function (obj) { var coords = obj.data.getGeometry().getCoordinates(); zoomToLocation(coords, 2); } }
  6590. ];
  6591. // Context menu for clicks other than on feature
  6592. contextmenu_items = [
  6593. { text: "Refresh", callback: function () { refreshMap(true, true); } },
  6594. { text: "Zoom to fit extent", callback: function () { zoomToFitExtent(); } },
  6595. { text: "Center map here", callback: function (obj) { xxmap.mapView.animate({ center: obj.coordinate }); } },
  6596. { text: "Place node here", callback: function (obj) { placeNode(obj.coordinate); } }
  6597. ];
  6598. }
  6599. function stringToIntHash(str) {
  6600. var hash = 0, i;
  6601. for (i = 0; i < str.length; i++) { hash = ((hash << 5) - hash) + str.charCodeAt(i); hash |= 0; }
  6602. return hash;
  6603. };
  6604. // Get the lat/lon from a node
  6605. function map_parseNodeLoc(node) {
  6606. var loc = null, t = 0;
  6607. if (node.iploc) { loc = node.iploc; t = 1; }
  6608. if (node.wifiloc) { loc = node.wifiloc; t = 2; }
  6609. if (node.gpsloc) { loc = node.gpsloc; t = 3; }
  6610. if (node.userloc) { loc = node.userloc; t = 4; }
  6611. if ((loc == null) || (typeof loc != 'string')) return null;
  6612. loc = loc.split(',');
  6613. if (t == 1) {
  6614. // If this is IP location, randomize the position a little.
  6615. return [ parseFloat(loc[0]) + (stringToIntHash(node._id.substring(0, 20)) / 100000000000), parseFloat(loc[1]) + (stringToIntHash(node._id.substring(20)) / 100000000000), t ];
  6616. } else {
  6617. // Return the real position
  6618. return [ parseFloat(loc[0]), parseFloat(loc[1]), t ];
  6619. }
  6620. }
  6621. // Load the entire map
  6622. function loadmap() {
  6623. if ((features & 0x00008000) == 0) return;
  6624. if (xxmap == null) return;
  6625. if ((features & 0x8000) == 0) { xxmap = null; return; } // Geolocation not supported
  6626. QV('viewselectmapoption', true);
  6627. QV('devViewButton4', true);
  6628. try {
  6629. // Initialize a Source Vector
  6630. xxmap.markersSource = new ol.source.Vector();
  6631. xxmap.markersLayer = new ol.layer.Vector({
  6632. source: xxmap.markersSource
  6633. });
  6634. // Create a tile and use OSM source
  6635. xxmap.mapLayer = new ol.layer.Tile({ source: new ol.source.OSM() });
  6636. xxmap.mapView = new ol.View({ // Set the initial view
  6637. center: ol.proj.transform([0, 0], 'EPSG:4326', 'EPSG:3857'),
  6638. zoom: 2,
  6639. minZoom: 2,
  6640. maxZoom: 20,
  6641. extent: ol.proj.transformExtent([-100000, -69.55, 100000, 69.55], 'EPSG:4326', 'EPSG:3857')
  6642. });
  6643. xxmap.map = new ol.Map({
  6644. target: 'xdevicesmap',
  6645. layers: [xxmap.mapLayer, xxmap.markersLayer],
  6646. view: xxmap.mapView
  6647. });
  6648. xxmap.map.addOverlay(map_cm_popup);
  6649. // Goto information tab if a user clicks on a feature
  6650. xxmap.map.on('click', function(evt) {
  6651. var feature = xxmap.map.forEachFeatureAtPixel(evt.pixel, function(feat, layer) { return feat; });
  6652. if (feature) {
  6653. var nodeid = feature.getId();
  6654. if (nodeid != null) { gotoDevice(nodeid, 10); } // Goto general info tab
  6655. else { // For pointer
  6656. var nodeFeatgoto = getCorrespondingFeature(feature); gotoDevice(nodeFeatgoto.getId(), 10);
  6657. }
  6658. }
  6659. });
  6660. // On hover feature show the name of the node. Also add pointer style
  6661. xxmap.map.on('pointermove', function(evt) {
  6662. var feature = xxmap.map.forEachFeatureAtPixel(evt.pixel, function(feat, layer) { return feat; });
  6663. if (feature) {
  6664. xxmap.map.getTargetElement().style.cursor = 'pointer';
  6665. var coord = feature.getGeometry().getCoordinates();
  6666. // map_cm_popup.setPosition(evt.coordinate);
  6667. map_cm_popup.setPosition(coord);
  6668. var featid = feature.getId();
  6669. if (featid) {
  6670. QH('xmap-info-window', feature.get('name'));
  6671. } else {
  6672. var nodeFeat = getCorrespondingFeature(feature); // Return the node feature associated to pointer.
  6673. QH('xmap-info-window', nodeFeat.get('name'));
  6674. }
  6675. } else {
  6676. xxmap.map.getTargetElement().style.cursor = '';
  6677. QH('xmap-info-window', '');
  6678. }
  6679. });
  6680. // Initialize context menu for openlayers
  6681. var contextmenu = new ContextMenu({
  6682. width: 160,
  6683. defaultItems: false, // defaultItems are Zoom In/Zoom Out
  6684. items: contextmenu_items
  6685. });
  6686. // On right click open the context menu
  6687. contextmenu.on('open', function (evt) {
  6688. var feature = xxmap.map.forEachFeatureAtPixel(evt.pixel, function(ft, l){ return ft; });
  6689. xxmap.contextmenu.clear(); //Clear the context menu
  6690. if (feature) {
  6691. var featId = feature.getId();
  6692. if (featId) { addContextMenuItems(feature); } // Node feature will have an id
  6693. else { // If the feature is a pointer, Get its corresponding Node feature
  6694. var nodeFeature = getCorrespondingFeature(feature); //return the node feature associated to pointer.
  6695. if (nodeFeature) { addContextMenuItems(nodeFeature); }
  6696. else{ xxmap.contextmenu.extend(contextmenu_items); }
  6697. }
  6698. }
  6699. else { xxmap.contextmenu.extend(contextmenu_items); }
  6700. });
  6701. if (xxmap.contextmenu == null) { xxmap.contextmenu = contextmenu; }
  6702. xxmap.map.addControl(xxmap.contextmenu);
  6703. //addMeshOptions(); // Adds Mesh names to mesh dropdown
  6704. } catch (ex) {
  6705. console.log(ex);
  6706. QV('viewselectmapoption', false);
  6707. QV('devViewButton4', false);
  6708. xxmap = null;
  6709. }
  6710. }
  6711. // Add feature on to Map for a Node
  6712. function addFeature(node, lat, lon) {
  6713. var existingfeature = getModifiedFeature(node._id); // Check if Corresponding feature was Modified ( Modifed feature are in active interactions list)
  6714. if (existingfeature) { xxmap.markersSource.addFeature(existingfeature); } // Add that existing feature
  6715. else { // Add new feature for this node
  6716. if (!lat && !lon) { var loc = map_parseNodeLoc(node); lat = loc[0]; lon = loc[1]; }
  6717. // Fix the longiture and send an event to patch the db to correct coordinate format. It will cause second unnecessary updateFeature on this node to the map.
  6718. if (lon > 180) { lon = 180 - lon; meshserver.send({ action: 'changedevice', nodeid: node._id, userloc: [ lat, lon ] }); }
  6719. if ((lat < 90) && (lat > -90) && (lon < 180) && (lon > -180)) { // Check valid lat/lon
  6720. var feature = new ol.Feature({ geometry: new ol.geom.Point(ol.proj.transform([lon, lat], 'EPSG:4326','EPSG:3857')), name: node.name, status: node.conn, lat: lat, lon: lon });
  6721. feature.setId(node._id); // Set id for the device as nodeid
  6722. feature.setStyle(markerStyle(node));
  6723. xxmap.markersSource.addFeature(feature); // Add the feature to Marker Source
  6724. }
  6725. }
  6726. }
  6727. // Removing any feature from map
  6728. function removeFeature(node) {
  6729. var feature = xxmap.markersSource.getFeatureById(node._id);
  6730. if (feature) { xxmap.markersSource.removeFeature(feature); }
  6731. }
  6732. // Update feature
  6733. function updateFeature(node, feature) {
  6734. if (node.conn != feature.get('status') ) { // Update status if changed
  6735. feature.set('status',node.conn)
  6736. feature.setStyle(markerStyle(node));
  6737. }
  6738. // Since this is IP address location, add some fixed randomness to the location. Avoid pin pile-up.
  6739. var loc = map_parseNodeLoc(node);
  6740. if (loc != null) {
  6741. var lat = loc[0], lon = loc[1];
  6742. if ((lat != feature.get('lat')) || (lon != feature.get('lon'))) { // Update lat and lon if changed
  6743. feature.set('lat', lat); feature.set('lon', lon);
  6744. var modifiedCoordinates = ol.proj.transform([parseFloat(lon), parseFloat(lat)], 'EPSG:4326', 'EPSG:3857');
  6745. feature.getGeometry().setCoordinates(modifiedCoordinates);
  6746. }
  6747. }
  6748. if (node.name != feature.get('name') ) { feature.set('name', node.name); } // Update name
  6749. }
  6750. // Enable dragging of a marker after edit option is clicked in context menu
  6751. function modifyMarkerloc(ft){
  6752. var featid = ft.getId();
  6753. if (featid) {
  6754. ft.setStyle(markerStyle(getNodeFromId(ft.a), 4)); // Switch to a user marker
  6755. if ( !getActiveInteractions(ft)) {
  6756. var dragInteration = new ol.interaction.Modify({
  6757. features: new ol.Collection([ft]),
  6758. pixelTolerance: 10
  6759. });
  6760. xxmap.activeInteractions.push({ featureid: featid, feature:ft, interaction: dragInteration }); // Also keep track of Interactions
  6761. xxmap.map.addInteraction(dragInteration);
  6762. }
  6763. }
  6764. }
  6765. // This will be called when save location option is clicked in context menu
  6766. function saveMarkerloc(ft){
  6767. var featid = ft.getId()
  6768. if (featid) {
  6769. var actInteraction = getActiveInteractions(ft);
  6770. if (actInteraction) { // Check if the interaction exists
  6771. xxmap.map.removeInteraction(actInteraction); //Clear Interaction for that node
  6772. removeInteraction(featid);
  6773. var coord = ft.getGeometry().getCoordinates();
  6774. var v = ol.proj.transform(coord, 'EPSG:3857', 'EPSG:4326');
  6775. if (v[0] > 180) { v[0] = 180 - v[0]; }
  6776. var vx = [ v[1], v[0] ]; // Flip the coordinates around, lat/long
  6777. meshserver.send({ action: 'changedevice', nodeid: featid, userloc: vx }); // Send them to server to save changes
  6778. }
  6779. }
  6780. }
  6781. // Style the Markers
  6782. function markerStyle(node, type) {
  6783. if (type == null) {
  6784. type = 0;
  6785. if (node.iploc) { type = 1; }
  6786. if (node.wifiloc) { type = 2; }
  6787. if (node.gpsloc) { type = 3; }
  6788. if (node.userloc) { type = 4; }
  6789. }
  6790. var types = ['', '-ip','-wifi','-gps','-user'];
  6791. var color = connStateColor(node);
  6792. var style = new ol.style.Style({
  6793. image: new ol.style.Icon({ color: color, anchor: [0.5, 1], src: 'images/mapmarker' + types[type] + '.png' })
  6794. //stroke: new ol.style.Stroke({ color: '#000', width: 20 })
  6795. //text: new ol.style.Text({ text: 'bob!', textAlign: 'right', offsetX: -10, fill: new ol.style.Fill({ color: '#000' }), stroke: new ol.style.Stroke({ color: '#fff', width: 2 }) })
  6796. });
  6797. //deviceMark.setStyle(new ol.style.Style({
  6798. // text: new ol.style.Text({
  6799. // //font: '12px helvetica,sans-serif',
  6800. // text: currentNode.name,
  6801. // textAlign: 'right',
  6802. // offsetX: -10,
  6803. // fill: new ol.style.Fill({ color: '#000' }),
  6804. // stroke: new ol.style.Stroke({ color: '#fff', width: 2 })
  6805. // }),
  6806. // image: new ol.style.Icon(({ color: [113, 140, 0], src: 'images/dot.png' })) }));
  6807. return [ style ];
  6808. }
  6809. // TODO: Add more connection status types. Currently we only change color if connection status changes
  6810. function connStateColor(nodeConn){
  6811. if (nodeConn.conn == 1 || nodeConn.conn == 3 || nodeConn.conn == 5) { return '#00ffdd'; } // Green for connected devices
  6812. return '#C70039'; // Red if the Agent is not connected
  6813. }
  6814. // Add save/edit option to context menu
  6815. function addContextMenuItems(feature) {
  6816. if (getActiveInteractions(feature)) { // If this feature is modified then display save option in contextmenu
  6817. map_cm_saveMarker.data = feature;
  6818. xxmap.contextmenu.push(map_cm_saveMarker);
  6819. } else {
  6820. map_cm_editMarker.data = feature;
  6821. xxmap.contextmenu.push(map_cm_editMarker);
  6822. var node = getNodeFromId(feature.a);
  6823. if (node.userloc) {
  6824. map_cm_clearMarker.data = feature;
  6825. xxmap.contextmenu.push(map_cm_clearMarker);
  6826. }
  6827. }
  6828. map_cm_nodemenu_items.forEach(function (item){
  6829. if (item.text == "Zoom-in to extent" || item.text == "Zoom-out to extent") { item.data = feature; }
  6830. else { if (item != '-') { item.data = feature.getId(); } }
  6831. });
  6832. xxmap.contextmenu.extend(map_cm_nodemenu_items);
  6833. }
  6834. // Return a active Interaction if it exists in activeInteractions list
  6835. function getActiveInteractions(feature) {
  6836. var featid = feature.getId();
  6837. for (var i = 0; i < xxmap.activeInteractions.length; i++) {
  6838. if (xxmap.activeInteractions[i].featureid == featid) { return xxmap.activeInteractions[i].interaction; }
  6839. }
  6840. return false;
  6841. }
  6842. // Return Modified feature based on Id
  6843. function getModifiedFeature(featid) {
  6844. if (featid) {
  6845. for (var i = 0; i < xxmap.activeInteractions.length; i++) {
  6846. if (xxmap.activeInteractions[i].featureid == featid) { return xxmap.activeInteractions[i].feature; }
  6847. }
  6848. }
  6849. return null;
  6850. }
  6851. // Remove Interaction
  6852. function removeInteraction(ftid) {
  6853. var index = -1;
  6854. for (var i = 0; i < xxmap.activeInteractions.length; i++) {
  6855. if (xxmap.activeInteractions[i].featureid === ftid) { index = i; break; }
  6856. }
  6857. if (index >= 0) { xxmap.activeInteractions.splice(index, 1); }
  6858. }
  6859. // Check if pointer coordinates are equal to features and return node feature
  6860. function getCorrespondingFeature(pointerFeat) {
  6861. var pointerCoord = pointerFeat.getGeometry().getCoordinates();
  6862. for (var i = 0; i < xxmap.activeInteractions.length ; i++) {
  6863. var modifiedFeatures = xxmap.activeInteractions[i].feature;
  6864. var fearCoord = modifiedFeatures.getGeometry().getCoordinates();
  6865. if (fearCoord[0].toFixed(5) == pointerCoord[0].toFixed(5) && fearCoord[1].toFixed(5) == pointerCoord[1].toFixed(5) ) { return modifiedFeatures; }
  6866. }
  6867. return null;
  6868. }
  6869. // Refresh the map and clear list
  6870. function refreshMap(reset, rebound){
  6871. if (reset) {
  6872. xxmap.map.setTarget(null);
  6873. xxmap.map = null;
  6874. xxmap.markersSource = null;
  6875. xxmap.mapView = null;
  6876. xxmap.mapLayer = null;
  6877. xxmap.activeInteractions = []; // Clear Active Interaction list
  6878. }
  6879. //clearMeshOptions();
  6880. //onSelectMeshChange();
  6881. var box = updateMapMarkers();
  6882. if ((box != null) && (rebound || (box[4] == 1))) {
  6883. var clat = (box[0] + box[2]) / 2;
  6884. var clon = (box[1] + box[3]) / 2;
  6885. var cscale = Math.max(Math.abs(box[0] - box[2]), Math.abs(box[1] - box[3]));
  6886. var view = xxmap.map.getView();
  6887. view.setCenter(ol.proj.transform([clon, clat], 'EPSG:4326', 'EPSG:3857'));
  6888. var i = 360, j = -2;
  6889. while (i > cscale) { j++; i = i / 2; }
  6890. view.setZoom(j);
  6891. }
  6892. }
  6893. // Called When Place a node option is clicked from context menu
  6894. function placeNode(coords) {
  6895. if (xxdialogMode) return;
  6896. var x = '<div style=margin-bottom:6px><label for=selectnode-search>' + "Search" + '</label>&nbsp&nbsp<input type=text placeholder="' + "Device name" + '" id="selectnode-search" onchange=onPlaceNodeInputChange() onkeyup=onPlaceNodeInputChange() autocomplete=off style=width:120px></div><div id=placenode style="height:254px;overflow-y:auto;width:100%;margin:12px 1px 4px 1px;"><div id=noNodesMapPlace style=text-align:center;width:100%;display:none>' + "No devices found." + '</div>';
  6897. for (var i in nodes) {
  6898. x += '<div class=noselect id=' + nodes[i]._id + '-rowid onclick=selectNodeToPlace(event,\''+ nodes[i]._id +'\') style=background-color:lightgray;margin-bottom:4px;border-radius:2px><input name=PlaceMapDeviceCheckbox id=' + nodes[i]._id + '-checkid type=checkbox style=width:16px;display:inline />';
  6899. x += '<div class=j' + nodes[i].icon + ' style=width:16px;height:16px;margin-top:2px;margin-right:4px;display:inline-block></div><div style=width:16px;display:inline>' + nodes[i].name + '</div></div>';
  6900. }
  6901. setDialogMode(2, "Select a node to place", 3, placeNodeEx, x + '</div>', coords);
  6902. onPlaceNodeInputChange();
  6903. }
  6904. function placeNodeEx(button, coords) {
  6905. var elements = document.getElementsByName('PlaceMapDeviceCheckbox');
  6906. for (var i in elements) {
  6907. if (elements[i].checked) {
  6908. var node = getNodeFromId(elements[i].id.substring(0, elements[i].id.length - 8));
  6909. if (node) {
  6910. var feature = xxmap.markersSource.getFeatureById(i);
  6911. var v = ol.proj.transform(coords, 'EPSG:3857', 'EPSG:4326');
  6912. var vx = [ v[1], v[0] ]; // Flip the coordinates around, lat/long
  6913. if (feature) {
  6914. feature.getGeometry().setCoordinates(coords);
  6915. var activeInteraction = getActiveInteractions(feature);
  6916. if (activeInteraction) {
  6917. saveMarkerloc(feature);
  6918. } else { // If this feature is not saved after its location is changed, then send updated coords to server.
  6919. meshserver.send({ action: 'changedevice', nodeid: node._id, userloc: vx }); // Send them to server to save changes
  6920. }
  6921. } else {
  6922. meshserver.send({ action: 'changedevice', nodeid: node._id, userloc: vx }); // This Node is not yet added to maps.
  6923. }
  6924. }
  6925. }
  6926. }
  6927. }
  6928. // Called when the user changes the search box
  6929. function onPlaceNodeInputChange() {
  6930. updatePlaceNodeTable(Q('selectnode-search').value.trim().toLowerCase());
  6931. }
  6932. // Update the list of devices in the "place on map" table
  6933. function updatePlaceNodeTable(inputSearch) {
  6934. var elements = document.getElementsByName('PlaceMapDeviceCheckbox'), count = 0;
  6935. for (var i in nodes) {
  6936. var visible = ((nodes[i].namel.indexOf(inputSearch) >= 0 || inputSearch == '') || (nodes[i].rnamel != null && nodes[i].rnamel.indexOf(inputSearch) >= 0));
  6937. if (visible) { count++; }
  6938. QV(nodes[i]._id + '-rowid', visible);
  6939. }
  6940. QV('noNodesMapPlace', count == 0);
  6941. //console.log(selected);
  6942. //for (var i in nodes) {
  6943. // if ((nodes[i].name.toLowerCase().indexOf(inputSearch) >= 0 || inputSearch == '') || (nodes[i].rnamel != null && nodes[i].rnamel.toLowerCase().indexOf(inputSearch) >= 0)) {
  6944. // console.log(selected.indexOf(nodes[i]._id));
  6945. // x += '<div class=noselect id=' + nodes[i]._id + '-rowid onclick=selectNodeToPlace(event,\''+ nodes[i]._id +'\') style=background-color:lightgray;margin-bottom:4px;border-radius:2px><input name=PlaceMapDeviceCheckbox id=' + nodes[i]._id + '-checkid type=checkbox style=width:16px;display:inline ' + ((selected.indexOf(nodes[i]._id) >= 0)?'checked':'') + ' />';
  6946. // x += '<div class=j' + nodes[i].icon + ' style=width:16px;height:16px;margin-top:2px;margin-right:4px;display:inline-block></div><div style=width:16px;display:inline>' + nodes[i].name + '</div></div>';
  6947. // }
  6948. //}
  6949. //if (x == '') { x = '<div style=text-align:center;width:100%>No devices found.</div>'; }
  6950. //QH('placenode', '');
  6951. }
  6952. // Called when a user clicks on a device to toggle selection for placement on map.
  6953. function selectNodeToPlace(e, id) {
  6954. // Toggle checkbox if needed
  6955. if (e.target.name != 'PlaceMapDeviceCheckbox') { var inputElement = Q(id + '-checkid'); inputElement.checked = !inputElement.checked; }
  6956. // Check button state
  6957. var elements = document.getElementsByName('PlaceMapDeviceCheckbox'), checkcount = 0;
  6958. for (var i in elements) { if (elements[i].checked) checkcount++; }
  6959. QE('idx_dlgOkButton', checkcount > 0);
  6960. }
  6961. // Add option for available meshes in mesh Dropdown
  6962. function addMeshOptions(addMeshid, meshName) {
  6963. //var meshOptions = Q('select-mesh');
  6964. //if (addMeshid && meshName) {
  6965. // var option = document.createElement('option');
  6966. // option.value =addMeshid;
  6967. // option.text = meshName;
  6968. // meshOptions.add(option); // Add specific option
  6969. //}
  6970. //else {
  6971. // for (var i in meshes) { // Add all options
  6972. // var option = document.createElement('option');
  6973. // option.value = i;
  6974. // option.text = meshes[i].name;
  6975. // meshOptions.add(option);
  6976. // }
  6977. //}
  6978. }
  6979. // Remove/Modify options in Mesh dropdown (if modMeshname is defined then Modify else Remove)
  6980. function meshOptionRmvMod(delMeshid, modMeshname){
  6981. //var meshOptions = Q('select-mesh');
  6982. //if (delMeshid) {
  6983. // var index=-1;
  6984. // for (var i = 1; i < meshOptions.options.length; i++) {
  6985. // if (meshOptions[i].value === delMeshid) { index=i; }
  6986. // }
  6987. // if (index > 0) {
  6988. // if (modMeshname) {
  6989. // meshOptions[index].innerHTML=modMeshname; // If Mesh name is Modified
  6990. // }
  6991. // else { meshOptions.remove(index); }
  6992. // }
  6993. //}
  6994. }
  6995. //Check if there is any mesh created
  6996. function meshExists() {
  6997. for (var i in meshes) { if (meshes[i]) { return true; } }
  6998. return false;
  6999. }
  7000. // Reset Mesh dropdown option to 'All' when a current view mesh is deleted.
  7001. function setMeshView(emeshid) {
  7002. var selectMeshElement=Q('select-mesh');
  7003. var selectedIndex = selectMeshElement.selectedIndex;
  7004. if (selectMeshElement[selectedIndex].value == emeshid) { selectMeshElement[0].selected = true; onSelectMeshChange(); }
  7005. }
  7006. // Clear all mesh options except 'All'
  7007. function clearMeshOptions() {
  7008. //var meshOptions=Q('select-mesh');
  7009. //for(var i = meshOptions.options.length - 1 ; i > 0 ; i--) { meshOptions.remove(i); }
  7010. }
  7011. // Make a http get call
  7012. function getSearchLocation() {
  7013. try {
  7014. var searchdata = Q('mapSearchLocation').value.trim();
  7015. if (searchdata.length > 0) {
  7016. var xmlhttp = new XMLHttpRequest(); // Compatible with Chrome, Opera, Safari, IE7+, Firefox.
  7017. xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { formatSearchData(xmlhttp.responseText); } }
  7018. xmlhttp.open('GET', 'https://nominatim.openstreetmap.org/search?q=' + searchdata + '&format=json', true); // Get request
  7019. xmlhttp.send();
  7020. }
  7021. } catch (e) {}
  7022. }
  7023. // Format data recieved from nominatim API and display it on content window
  7024. function formatSearchData(data) {
  7025. try {
  7026. QH('xmapSearchResults','');
  7027. var dataInfo = JSON.parse(data), count = 0, x = '<div class="xmapItem">';
  7028. for (var i = 0; i < dataInfo.length; i++) {
  7029. if (dataInfo[i].display_name && dataInfo[i].boundingbox[0] && dataInfo[i].boundingbox[1] && dataInfo[i].boundingbox[2] && dataInfo[i].boundingbox[3]) {
  7030. count++;
  7031. var itemclass = (i % 2 == 0)?'xmapItemSel1':'xmapItemSel1';
  7032. x += '<div class="' + itemclass + '" onclick=mapGotoSelectedLocation(this)><div>' + dataInfo[i].display_name + '</div><div style=display:none>' + dataInfo[i].boundingbox[0] + '!#!' + dataInfo[i].boundingbox[1] + '!#!' + dataInfo[i].boundingbox[2] + '!#!' + dataInfo[i].boundingbox[3] + '</div></div>';
  7033. }
  7034. }
  7035. x += '</div>';
  7036. if (count == 1) {
  7037. // If only one result is returned then zoom to that location
  7038. var extent = [ parseFloat(dataInfo[0].boundingbox[2]), parseFloat(dataInfo[0].boundingbox[0]), parseFloat(dataInfo[0].boundingbox[3]), parseFloat(dataInfo[0].boundingbox[1]) ];
  7039. zoomToExtent(extent);
  7040. } else {
  7041. if (count == 0) { x = '<div style=width:200px>' + "No location found." + '<div>'; }
  7042. QV('xmapSearchResultsDlg', true);
  7043. }
  7044. QH('xmapSearchResults', x);
  7045. }
  7046. catch (e) {}
  7047. }
  7048. // Zoom into the bounding box
  7049. function mapGotoSelectedLocation(obj) {
  7050. var objchildren = obj.children;
  7051. var boundingBox = objchildren[1].innerHTML.split('!#!');
  7052. var extent = [parseFloat(boundingBox[2]), parseFloat(boundingBox[0]), parseFloat(boundingBox[3]), parseFloat(boundingBox[1])];
  7053. //Q('search-location').value = objchildren[0].innerHTML;
  7054. zoomToExtent(extent);
  7055. mapCloseSearchWindow();
  7056. }
  7057. // Close the search window
  7058. function mapCloseSearchWindow() {
  7059. QH('xmapSearchResults', '');
  7060. QV('xmapSearchResultsDlg', false);
  7061. }
  7062. // Zoom to specific cordinates
  7063. function zoomToLocation(coordinates, zoomVal) {
  7064. var view = xxmap.map.getView();
  7065. view.setCenter(coordinates);
  7066. view.setZoom(zoomVal);
  7067. }
  7068. function zoomToFitExtent() {
  7069. var features = xxmap.markersSource.getFeatures();
  7070. if (features.length > 0) {
  7071. var extent = xxmap.markersSource.getExtent();
  7072. xxmap.map.getView().fit(extent, xxmap.map.getSize());
  7073. }
  7074. }
  7075. function zoomToExtent(extent){
  7076. var boundingExtent = ol.proj.transformExtent(extent, ol.proj.get('EPSG:4326'), ol.proj.get('EPSG:3857'));
  7077. xxmap.map.getView().fit(boundingExtent, xxmap.map.getSize());
  7078. }
  7079. //
  7080. // MY DEVICE
  7081. //
  7082. function refreshDevice(nodeid) {
  7083. if (!currentNode || currentNode._id != nodeid) return;
  7084. gotoDevice(nodeid, xxcurrentView, true);
  7085. }
  7086. var currentNode;
  7087. var powerTimelineNode = null;
  7088. var powerTimelineReq = null;
  7089. var powerTimelineUpdate = null;
  7090. var powerTimeline = null;
  7091. var deviceSharesNode = null;
  7092. var deviceSharesReq = null;
  7093. var deviceShares = null;
  7094. function getCurrentNode() { return currentNode; };
  7095. function gotoDevice(nodeid, panel, refresh, event) {
  7096. if(event && ((event.which == 3) || (event.button == 2))) return; // ignore right click events here as handled elsewhere
  7097. // Remind the user to verify the email address
  7098. if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access this feature until a email address is verified. This is required for password recovery. Go to the \"My Account\" tab to change and verify an email address."); return; }
  7099. // Remind the user to add two factor authentication
  7100. if ((features & 0x00040000) && (count2factoraAuths() == 0)) { 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\" tab and look at the \"Account Security\" section."); return; }
  7101. if (event && ((event.shiftKey == true) || (event.which == 2) || (event.button == 1))) {
  7102. // Open the device in a different tab
  7103. safeNewWindow(window.location.origin + '?node=' + nodeid.split('/')[2] + '&viewmode=10&hide=16' + ((urlargs.key)?('&key=' + urlargs.key):''), 'meshcentral:' + nodeid);
  7104. return;
  7105. }
  7106. //disconnectAllKvmFunction();
  7107. var node = getNodeFromId(nodeid);
  7108. if (node == null) return;
  7109. if ((currentNode == null) || (currentNode._id != node._id) || ((node.conn & 1) == 0)) { deviceDetailsStatsClear(); } // Hide the list cpu/memory graph
  7110. var mesh = meshes[node.meshid];
  7111. var meshrights = GetNodeRights(node);
  7112. var deviceSwitch = ((currentNode == null) || (currentNode._id != nodeid));
  7113. if (!currentNode || currentNode._id != node._id || refresh == true) {
  7114. currentNode = node;
  7115. // Pre defined scripts
  7116. QV('deskPreConfigScriptContextMenu1', (currentNode.mtype == 2) && isWindowsNode(currentNode)); // Windows devices
  7117. QV('deskPreConfigScriptContextMenu2', (currentNode.mtype == 2) && !isWindowsNode(currentNode)); // Other devices
  7118. // Device Notification
  7119. QV('p10deviceNotify', (currentNode.sessions != null) && ((currentNode.sessions.kvm != null) || (currentNode.sessions.terminal != null) || (currentNode.sessions.files != null) || (currentNode.sessions.tcp != null) || (currentNode.sessions.udp != null)));
  7120. QV('p10deviceStar', stars[currentNode._id] == 1);
  7121. QV('p10deviceHelp', (currentNode.sessions != null) && (currentNode.sessions.help != null))
  7122. if ((currentNode.sessions != null) && (currentNode.sessions.msg != null)) { QV('p10deviceMsg', true); QH('p10deviceMsg', Object.keys(currentNode.sessions.msg).length); } else { QV('p10deviceMsg', false); }
  7123. // Device Battery
  7124. QV('p10deviceBattery', false);
  7125. if ((currentNode.sessions != null) && (currentNode.sessions.battery != null)) {
  7126. var bat = currentNode.sessions.battery;
  7127. var statestr = '';
  7128. if (bat.state == 'ac') { statestr = "Device is plugged-in"; }
  7129. if (bat.state == 'dc') { statestr = "Device is battery powered"; }
  7130. var levelstr = '', levelnum = -1;
  7131. if ((typeof bat.level == 'number') && (bat.level >= 0) && (bat.level <= 100)) {
  7132. levelstr = bat.level + '%';
  7133. levelnum = (Math.floor((bat.level + 10) / 25) + 1);
  7134. if (levelnum > 5) { lvl = 5; }
  7135. if (bat.state == 'ac') { if (bat.level == 100) { levelnum = 11; } else { levelnum += 5; } }
  7136. }
  7137. if (levelnum > 0) {
  7138. Q('p10deviceBattery').title = (statestr != null)?(statestr + ', ' + levelstr):levelstr;
  7139. QV('p10deviceBattery', true);
  7140. Q('p10deviceBattery').className = 'deviceBatteryLarge deviceBatteryLarge' + levelnum;
  7141. }
  7142. } else {
  7143. QV('p10deviceBattery', false);
  7144. }
  7145. // Add node name
  7146. var nname = EscapeHtml(node.name), nnameEx;
  7147. if (nname.length == 0) { nname = '<i>' + "None" + '</i>'; }
  7148. if (((meshrights & 4) != 0) && ((!mesh.flags) || ((mesh.flags & 2) == 0 || (mesh.flags & 16)))) { nname = '<span tabindex=0 title="' + "Click here to edit the server-side device name" + '" onclick=showEditNodeValueDialog(0) onkeyup="if (event.key == \'Enter\') showEditNodeValueDialog(0)" style=cursor:pointer>' + nname + ' <img class=hoverButton src="images/link5.png" /></span>'; }
  7149. nnameEx = nname;
  7150. if (mesh) { nname += '<span style=color:#AAA;font-size:small> - ' + EscapeHtml(mesh.name) + '</span>'; }
  7151. QH('p10deviceName', nname);
  7152. QH('p11deviceName', nname);
  7153. QH('p12deviceName', nname);
  7154. QH('p13deviceName', nname);
  7155. QH('p14deviceName', nname);
  7156. QH('p15deviceName', "Console - " + nname);
  7157. QH('p16deviceName', nname);
  7158. QH('p17deviceName', nname);
  7159. QH('p19deviceName', nname);
  7160. // Node attributes
  7161. var x = '<table style=width:100%>';
  7162. // If title bar is hidden, display the device name here
  7163. if ((args.hide & 8) != 0) { x += '<br />' + addDeviceAttribute("Name", nnameEx); }
  7164. // Attribute: Mesh
  7165. if (mesh) { x += addDeviceAttribute('<span title="' + "The name of the device group this computer belong to." + '">' + "Group" + '</span>', '<a href=# title="' + "The name of the device group this computer belong to" + '" onclick=gotoMesh("' + node.meshid + '") style=cursor:pointer>' + EscapeHtml(meshes[node.meshid].name) + '</a>'); }
  7166. // Attribute: Name
  7167. if ((node.rname != null) && (node.name != node.rname)) { x += addDeviceAttribute('<span title="' + "The name of this computer as set in the operating system" + '">' + "OS Name" + '</span>', '<span title="' + "The name of this computer as set in the operating system" + '">' + EscapeHtml(node.rname) + '</span>'); }
  7168. // Attribute: Host
  7169. if ((((features & 1) == 0) && (node.mtype != 4)) || (node.mtype == 3)) { // If not WAN-only, local hostname is in use
  7170. x += addDeviceAttribute("Hostname", addLinkConditional('<span onclick=showEditNodeValueDialog(1) style=cursor:pointer>' + (node.host?EscapeHtml(node.host):('<i>' + "None" + '</i>')) + '</span>', 'showEditNodeValueDialog(1)', meshrights & 4));
  7171. }
  7172. // Attribute: Description
  7173. var description = node.desc?EscapeHtml(node.desc):('<i>' + "None" + '</i>');
  7174. if ((meshrights & 4) != 0) {
  7175. x += addDeviceAttribute("Description", '<span onclick=showEditNodeValueDialog(2) style=cursor:pointer>' + description + ' <img class=hoverButton src="images/link5.png" /></span>');
  7176. } else {
  7177. x += addDeviceAttribute("Description", description);
  7178. }
  7179. // IP-KVM / PDU information
  7180. if (node.mtype == 4) {
  7181. if (node.portnum != null) { x += addDeviceAttribute("Port Number", node.portnum); }
  7182. if (node.porttype != null) { x += addDeviceAttribute("Port Type", node.porttype); }
  7183. }
  7184. // Attribute: Mesh Agent
  7185. if ((node.agent != null) && (node.agent.id != null) && (node.mtype == 3)) {
  7186. if (node.agent.id == 4) { x += addDeviceAttribute("Device Type", "Windows"); }
  7187. if (node.agent.id == 6) { x += addDeviceAttribute("Device Type", "Linux"); }
  7188. if (node.agent.id == 29) { x += addDeviceAttribute("Device Type", "macOS"); }
  7189. } else if ((node.agent != null) && (node.agent.id != null) && (node.agent.ver != null)) {
  7190. var str = '';
  7191. if (node.agent.id <= agentsStr.length) { str = agentsStr[node.agent.id]; } else { str = agentsStr[0]; }
  7192. if (node.agent.ver != 0) { str += ' v' + node.agent.ver; }
  7193. if (node.agent.id == 14) { str = node.agent.core; }
  7194. if ((node.agent.root === false) && ((node.conn & 1) != 0)) { str += ', ' + "Restricted"; }
  7195. x += addDeviceAttribute("Mesh Agent", str);
  7196. }
  7197. // Attribute: Intel AMT
  7198. if (node.intelamt != null) {
  7199. var str = '';
  7200. var provisioningStates = { 0: nobreak("Not Activated (Pre)"), 1: nobreak("Not Activated (In)"), 2: nobreak("Activated") };
  7201. if (node.intelamt.ver != null && node.intelamt.state == null) { str += '<i>' + "Unknown State" + '</i>, v' + EscapeHtml(node.intelamt.ver); }
  7202. else if ((node.intelamt.ver == null) && (node.intelamt.state == 2)) { str += '<i>' + "Activated" + '</i>'; }
  7203. else if ((node.intelamt.ver == null) || (node.intelamt.state == null)) { str += '<i>' + "Unknown Version & State" + '</i>'; }
  7204. else {
  7205. str += provisioningStates[node.intelamt.state];
  7206. if ((node.intelamt.state == 2) && node.intelamt.flags) { if (node.intelamt.flags & 2) { str += ' <span title="' + "Intel&reg; AMT is activated in Client Control Mode" + '">' + "CCM" + '</span>'; } else if (node.intelamt.flags & 4) { str += ' <span title="' + "Intel&reg; AMT is activated in Admin Control Mode" + '">' + "ACM" + '</span>'; } }
  7207. str += (', v' + EscapeHtml(node.intelamt.ver));
  7208. }
  7209. // If Intel AMT is activated, show additional options
  7210. if (node.intelamt.state == 2) {
  7211. if (node.intelamt.tls == 1) { str += ', <span title="' + "Intel&reg; AMT is setup with TLS network security" + '">' + "TLS" + '</span>'; }
  7212. var editUserCredentialsIcon = false;
  7213. if (node.intelamt.user == null || node.intelamt.user == '') { // If credentials are not set, allow setting them.
  7214. if ((meshrights & 4) != 0) {
  7215. str += ', <i style=color:#FF0000;cursor:pointer title="' + "Edit Intel&reg; AMT credentials" + '" onclick=editDeviceAmtSettings("' + node._id + '")>' + "No Credentials" + '</i>';
  7216. editUserCredentialsIcon = true;
  7217. } else {
  7218. str += ', <i style=color:#FF0000>' + "No Credentials" + '</i>';
  7219. }
  7220. } else if (((features2 & 1) != 0) && (node.intelamt.warn != null)) { // If AMT manager is running and warned of invalid credentials, allow setting them.
  7221. var warn = null;
  7222. if ((node.intelamt.warn & 1) != 0) { warn = "Invalid Credentials"; }
  7223. if ((node.intelamt.warn & 8) != 0) { warn = "Trying Credentials"; }
  7224. if (warn != null) {
  7225. if ((meshrights & 4) != 0) {
  7226. str += ', <i style=color:#FF0000;cursor:pointer title="' + "Edit Intel&reg; AMT credentials" + '" onclick=editDeviceAmtSettings("' + node._id + '")>' + warn + '</i>';
  7227. editUserCredentialsIcon = true;
  7228. } else {
  7229. str += ', <i style=color:#FF0000>' + warn + '</i>';
  7230. }
  7231. }
  7232. }
  7233. // If the AMT manager is not running, always allow Intel AMT credentials to be edited.
  7234. if (((meshrights & 4) != 0) && ((features2 & 1) == 0)) { editUserCredentialsIcon = true; }
  7235. str += ' ';
  7236. if (editUserCredentialsIcon) {
  7237. str += '<img src=images/link4.png height=10 width=10 title="' + "Edit Intel&reg; AMT credentials" + '" style=cursor:pointer onclick=editDeviceAmtSettings("' + node._id + '")>';
  7238. }
  7239. }
  7240. var meName = '<span title="' + "Intel&reg; Manageability Engine" + '">' + "Intel&reg; ME" + '<span>';
  7241. if (typeof node.intelamt.sku == 'number') {
  7242. if ((node.intelamt.sku & 8) != 0) { meName = '<span title="' + "Intel&reg; Active Management Technology" + '">' + "Intel&reg; AMT" + '<span>'; }
  7243. else if ((node.intelamt.sku & 16) != 0) { meName = '<span title="' + "Intel&reg; Standard Manageability" + '">' + "Intel&reg; SM" + '<span>'; }
  7244. }
  7245. x += addDeviceAttribute(meName, str);
  7246. }
  7247. if ((node.agent != null) && (node.agent.tag != null)) {
  7248. // Attribute: Mesh Agent Tag
  7249. var tag = EscapeHtml(node.agent.tag);
  7250. if (tag.startsWith('mailto:')) { tag = '<a href="' + EscapeHtml(tag) + '">' + EscapeHtml(tag.substring(7)) + '</a>'; }
  7251. x += addDeviceAttribute("Agent Tag", tag);
  7252. } else if ((node.intelamt != null) && (node.intelamt.tag != null)) {
  7253. // Attribute: Intel AMT Tag
  7254. var tag = EscapeHtml(node.intelamt.tag);
  7255. if (tag.startsWith('mailto:')) { tag = '<a href="' + EscapeHtml(tag) + '">' + EscapeHtml(tag.substring(7)) + '</a>'; }
  7256. x += addDeviceAttribute("Intel&reg; AMT Tag", tag);
  7257. }
  7258. // Attribute: Intel AMT
  7259. //if (node.intelamt && node.intelamt.user) { x += addDeviceAttribute('Intel&reg; AMT', node.intelamt.user); }
  7260. // Operating system description
  7261. if (node.osdesc) { x += addDeviceAttribute("Operating System", node.osdesc); }
  7262. // Windows Security Central
  7263. if (node.wsc) {
  7264. var y = [];
  7265. 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>'); } }
  7266. 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>'); } }
  7267. 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>'); } }
  7268. x += addDeviceAttribute("Windows Security", y.join(', '));
  7269. }
  7270. // Defender for Windows Server
  7271. if(node.defender) {
  7272. var y = [];
  7273. 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>'); } }
  7274. 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>'); } }
  7275. if (node.defender.AntivirusSignatureVersion != null) { y.push("SignatureVersion" + ' - <span style=color:green>' + EscapeHtml(node.defender.AntivirusSignatureVersion) + '</span>'); }
  7276. if (y.length > 0) x += addDeviceAttribute("Windows Defender", y.join(', '));
  7277. }
  7278. // Antivirus
  7279. if (node.av && node.av.length > 0) {
  7280. var y = [];
  7281. for (var i in node.av) {
  7282. if (node.av[i].product) {
  7283. var avx = EscapeHtml(node.av[i].product);
  7284. if (node.av[i].enabled !== true) { avx += ' - <span style=color:red>' + "Disabled" + '</span>'; }
  7285. if (node.av[i].updated !== true) { avx += ' - <span style=color:red>' + "Out of date" + '</span>'; }
  7286. if ((node.av[i].enabled == true) && (node.av[i].updated == true)) { avx += ' - <span style=color:green>' + "OK" + '</span>'; }
  7287. y.push(avx);
  7288. }
  7289. }
  7290. x += addDeviceAttribute("Antivirus", y.join('<br />'));
  7291. }
  7292. // Active Users
  7293. if (node.users && node.users.length > 0) {
  7294. var u = node.users.map(function(user, index) {
  7295. var label = (node.upnusers && node.upnusers[index] != null) ? EscapeHtml(node.upnusers[index]) : null;
  7296. var locked = (node.lusers && node.lusers.indexOf(user) >= 0);
  7297. var escapedUser = EscapeHtml(user);
  7298. if (locked) { return addKeyLinkConditional(escapedUser, (label ? (label + ' - ' + "Locked") : "Locked"), true); }
  7299. else if (label) { return '<span style=cursor:default title=\''+ label + '\'>' + escapedUser + '</span>'; }
  7300. else { return escapedUser; }
  7301. }).join(', ');
  7302. x += addDeviceAttribute((node.users.length > 1 ? "Active Users" : "Active User"), u);
  7303. }
  7304. // Display device user consent
  7305. if ((node.agent != null) && (node.agent.id != 14) && (node.mtype != 3)) {
  7306. var meshFeatures = [];
  7307. var consent = 0;
  7308. if (node.consent) { consent = node.consent; }
  7309. if (serverinfo.consent) { consent |= serverinfo.consent; }
  7310. if ((consent & 0x0040) && (consent & 0x0008)) { meshFeatures.push("Desktop Prompt+Toolbar"); } else if (consent & 0x0040) { meshFeatures.push("Desktop Toolbar"); } else if (consent & 0x0008) { meshFeatures.push("Desktop Prompt"); } else { if (consent & 0x0001) { meshFeatures.push("Desktop Notify"); } }
  7311. if (consent & 0x0010) { meshFeatures.push("Terminal Prompt"); } else { if (consent & 0x0002) { meshFeatures.push("Terminal Notify"); } }
  7312. if (consent & 0x0020) { meshFeatures.push("Files Prompt"); } else { if (consent & 0x0004) { meshFeatures.push("Files Notify"); } }
  7313. if (consent == 7) { meshFeatures = ["Always Notify"]; }
  7314. if ((consent & 56) == 56) { meshFeatures = ["Always Prompt"]; }
  7315. meshFeatures = meshFeatures.join(', ');
  7316. if (meshFeatures == '') { meshFeatures = '<i>' + "None" + '</i>'; }
  7317. x += addDeviceAttribute("User Consent", addLinkConditional(meshFeatures, 'p20editmeshconsent(3)', meshrights & 1));
  7318. }
  7319. var devNotifyStr = [];
  7320. if ((userinfo.notify != null) && (userinfo.notify[node._id] != null)) {
  7321. var devNotify = userinfo.notify[node._id];
  7322. if (devNotify & 2) { devNotifyStr.push("Connect"); }
  7323. if (devNotify & 4) { devNotifyStr.push("Disconnect"); }
  7324. if ((node.intelamt != null) && (devNotify & 8)) { devNotifyStr.push("Intel&reg; AMT"); }
  7325. if ((features2 & 0x00004000) && (userinfo.emailVerified)) {
  7326. var xx = 0;
  7327. if (devNotify & 16) { xx++; } if (devNotify & 32) { xx++; } if (devNotify & 64) { xx++; }
  7328. if (xx > 0) { devNotifyStr.push(format("Email ({0})", xx)); }
  7329. }
  7330. if ((userinfo.msghandle != null) && ((features2 & 0x02000000) != 0)) {
  7331. var xx = 0;
  7332. if (devNotify & 128) { xx++; } if (devNotify & 256) { xx++; } if (devNotify & 512) { xx++; }
  7333. if (xx > 0) { devNotifyStr.push(format("Messaging ({0})", xx)); }
  7334. }
  7335. }
  7336. devNotifyStr = devNotifyStr.join(', ');
  7337. if (devNotifyStr == '') { devNotifyStr = '<i>' + "None" + '</i>'; }
  7338. x += addDeviceAttribute("Notifications", addLink(devNotifyStr, 'p20editDeviceNotify()'));
  7339. // Attribute: Connectivity (Only show this if more than just the agent is connected).
  7340. var connectivity = node.conn;
  7341. if (connectivity && (connectivity > 1)) {
  7342. var cstate = [];
  7343. if ((node.conn & 1) != 0) cstate.push('<span title="' + "Mesh agent is connected and ready for use." + '">' + "Mesh Agent" + '</span>');
  7344. if ((node.conn & 2) != 0) cstate.push('<span title="' + "Intel&reg; AMT CIRA is connected and ready for use." + '">' + "Intel&reg; AMT CIRA" + '</span>');
  7345. else if ((node.conn & 4) != 0) cstate.push('<span title="' + "Intel&reg; AMT is routable and ready for use." + '">' + "Intel&reg; AMT" + '</span>');
  7346. if ((node.conn & 8) != 0) cstate.push('<span title="' + "Mesh agent is reachable using another agent as relay." + '">' + "Mesh Relay" + '</span>');
  7347. if ((node.conn & 16) != 0) { cstate.push('<span title="' + "MQTT connection to the device is active." + '">' + "MQTT" + '</span>'); }
  7348. x += addDeviceAttribute("Connectivity", cstate.join(', '));
  7349. }
  7350. // Node tags
  7351. var groupingTags = '<i>' + "None" + '</i>';
  7352. if (node.tags != null) { groupingTags = ''; for (var i in node.tags) { groupingTags += '<span class=tagSpan>' + EscapeHtml(node.tags[i]) + '</span> '; } }
  7353. if ((meshrights & 4) != 0) {
  7354. x += addDeviceAttribute("Tags", '<span onclick=showEditNodeValueDialog(3) style=line-height:26px;cursor:pointer>' + groupingTags + ' <img class=hoverButton src="images/link5.png" width=10 height=10 /></span>');
  7355. } else {
  7356. x += addDeviceAttribute("Tags", '<span style=line-height:26px>' + groupingTags + '</span>');
  7357. }
  7358. // SSH & RDP Credentials
  7359. if ((node.ssh != null) || (node.rdp != null)) {
  7360. var y = [];
  7361. if ((meshrights & 4) != 0) {
  7362. 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>'); }
  7363. 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>'); }
  7364. } else {
  7365. if (node.ssh != null) { y.push(((node.ssh == 1)?"SSH-User+Pass":((node.ssh == 2)?"SSH-User+Key+Pass":"SSH-User+Key"))); }
  7366. if (node.rdp != null) { y.push("RDP"); }
  7367. }
  7368. x += addDeviceAttribute("Credentials", y.join(', '));
  7369. }
  7370. // Relay for
  7371. var relayFor = [];
  7372. for (var i in meshes) { if (meshes[i].relayid == node._id) { relayFor.push('<a href=# onclick=gotoMesh("' + meshes[i]._id + '") style=cursor:pointer>' + EscapeHtml(meshes[i].name) + '</a>'); } }
  7373. if (relayFor.length > 0) { x += addDeviceAttribute('<span title="' + "Device groups this device is a relay for" + '">' + "Relay for" + '</span>', relayFor.join(", ")); }
  7374. x += '</table><br />';
  7375. // Show action button, only show if we have permissions 4, 8, 64
  7376. if (((meshrights & (4 + 8 + 64 + 262144)) != 0) && (node.mtype < 3) && ((node.agent == null) || (node.agent.id != 34))) { x += '<input type=button value="' + "Actions" + '" title="' + "Perform power actions on the device" + '" onclick=deviceActionFunction() />'; }
  7377. x += '<input type=button value="' + "Notes" + '" title="' + "View notes about this device" + '" onclick=showNotes(' + ((meshrights & 128) == 0) + ',"' + encodeURIComponentEx(node._id) + '") />';
  7378. x += '<input type=button value="' + "Log Event" + '" title="' + "Write an event for this device" + '" onclick=writeDeviceEvent("' + encodeURIComponentEx(node._id) + '") />';
  7379. if ((node.mtype == 2) && (connectivity & 1) && ((meshrights & 131072) != 0)) { x += '<input type=button cmenu=deskPreConfigScriptContextMenu value="' + "Run" + '" title="' + "Run commands on this device" + '" onclick=runDeviceCmd("' + encodeURIComponentEx(node._id) + '") />'; }
  7380. if (node.mtype != 4) {
  7381. if ((meshrights & 8) && (connectivity & 1) && ((meshrights & 16384) != 0) || ((node.pmt == 1) && ((features2 & 2) != 0))) { x += '<input type=button value="' + "Message" + '" title="' + "Display a text message on the remote device" + '" onclick=deviceMessageFunction() />'; }
  7382. //if ((connectivity & 1) && (meshrights & 8) && (node.agent.id < 5)) { x += '<input type=button value=Toast title="' + "Display a text message of the remote device" + '" onclick=deviceToastFunction() />'; }
  7383. if ((meshrights & 8) && (connectivity & 1) && ((meshrights & 16384) != 0) || ((node.pmt == 1) && ((features2 & 2) != 0))) { x += '<input type=button value="' + "Chat" + '" title="' + "Open chat window to this computer" + '" onclick=deviceChat(event) />'; }
  7384. if ((serverinfo != null) && (serverinfo.altmessenging != null) && (meshrights & 8) && (connectivity & 1)) {
  7385. for (var i in serverinfo.altmessenging) {
  7386. var am = serverinfo.altmessenging[i];
  7387. if ((am.type == null) || (am.type == 'device')) {
  7388. x += '<input type=button value="' + EscapeHtml(serverinfo.altmessenging[i].name) + '" onclick=altDeviceChat(event,' + i + ') />';
  7389. }
  7390. }
  7391. }
  7392. if ((serverinfo.guestdevicesharing !== false) && (node.agent != null) && (node.agent.caps & 3) && (connectivity & 1) && ((meshrights & 0x80008) == 0x80008) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 0x1000) == 0))) {
  7393. x += '<input type=button value="' + "Share" + '" title="' + "Create a link to share this device with a guest" + '" onclick=showShareDevice() />';
  7394. QV('DeskGuestShareButton', true);
  7395. } else {
  7396. QV('DeskGuestShareButton', false);
  7397. }
  7398. }
  7399. if ((node.mtype == 4) && (connectivity & 1)) {
  7400. if (node.porttype == 'PDU') {
  7401. if (node.pwr == 1) {
  7402. if (meshrights & 0x40000) { x += '<input type=button value="' + "Turn off" + '" title="' + "Turn off" + '" onclick=setIpPduState(0) />'; }
  7403. } else if (node.pwr == 8) {
  7404. if (meshrights & 0x40) { x += '<input type=button value="' + "Turn on" + '" title="' + "Turn on" + '" onclick=setIpPduState(1) />'; }
  7405. }
  7406. } else {
  7407. if (meshrights & 8) { x += '<input type=button value="' + "Remote Control" + '" title="' + "Remote Control" + '" onclick=openIpKvmRemoteControl("' + encodeURIComponentEx(node._id) + '") />'; }
  7408. }
  7409. }
  7410. // Custom UI
  7411. if ((customui != null) && (customui.devicebuttons != null)) {
  7412. for (var i in customui.devicebuttons) {
  7413. if ((customui.devicebuttons[i].showif == null) || ((mesh != null) && (customui.devicebuttons[i].showif == mesh._id)) || (customui.devicebuttons[i].showif == currentNode._id)) {
  7414. x += '<input id="cui:' + i + '" type="button" value="' + customui.devicebuttons[i].name + '" onclick=customUIAction(event,"devicebuttons") />';
  7415. }
  7416. }
  7417. }
  7418. QH('p10html', x);
  7419. // Show node last 7 days timeline
  7420. mainUpdate(256);
  7421. // Check if we have terminal and file access
  7422. var desktopAccess = ((meshrights == 0xFFFFFFFF) || ((meshrights & 65536) == 0));
  7423. var terminalAccess = ((meshrights == 0xFFFFFFFF) || ((meshrights & 512) == 0));
  7424. var fileAccess = ((meshrights == 0xFFFFFFFF) || ((meshrights & 1024) == 0));
  7425. var amtAccess = ((meshrights == 0xFFFFFFFF) || ((meshrights & 2048) == 0));
  7426. // Show bottom buttons
  7427. x = '<div class="p10html3right">';
  7428. if (((meshrights & 1) != 0) && (node.mtype != 4)) { // MESHRIGHT_EDITMESH
  7429. // TODO: Show change group only if there is another mesh of the same type.
  7430. x += '&nbsp;<a href=# onclick=p10showChangeGroupDialog(["' + node._id + '"]) title="' + "Move this device to a different device group" + '">' + "Change Group" + '</a>';
  7431. }
  7432. if ((meshrights & 0x8000) != 0) { // MESHRIGHT_UNINSTALL
  7433. x += '&nbsp;<a href=# onclick=p10showDeleteNodeDialog("' + node._id + '") title="' + "Remove this device" + '">' + "Delete Device" + '</a>';
  7434. }
  7435. x += '</div><div class="p10html3left">';
  7436. if ((node.agent) && (node.mtype != 3) && ((meshrights & 1048576) != 0)) x += '<a href=# onclick=p10showNodeNetInfoDialog("' + node._id + '") title="' + "Show device network interface information" + '">' + "Interfaces" + '</a>&nbsp;';
  7437. if ((features & 0x00008000) && (xxmap != null)) x += '<a href=# onclick=p10showNodeLocationDialog("' + node._id + '") title="' + "Show device locations information" + '">' + "Location" + '</a>&nbsp;';
  7438. if ((node.agent == null) || ((node.agent.id != 14) && (node.agent.id != 34))) {
  7439. if ((userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.siteadmin & 128) == 0)) { // Check if we should view tools
  7440. if ((terminalAccess) && ((meshrights & 8) != 0) && (node.agent != null) && (node.agent.id != 14)) x += '<a href=# onclick=p10showMeshCmdDialog(1,"' + node._id + '") title="' + "Traffic router used to connect to a device thru this server" + '.">' + "MeshCmd" + '</a>&nbsp;';
  7441. }
  7442. if ((args.xterm === 0) && (node.agent) && ((node.agent.caps & 2) != 0) && ((meshrights & 8) != 0) && ((meshrights == 0xFFFFFFFF) || ((meshrights & 512) == 0))) { x += '<a href=# onclick=p10openxterm(event,"' + node._id + '") title="' + "Open XTerm terminal" + '">' + "XTerm" + '</a>&nbsp;'; }
  7443. // RDP link, show this link only of the remote machine is Windows.
  7444. if ((((connectivity & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((meshrights & 8) != 0)) {
  7445. if (webRelayPort != 0) {
  7446. x += '<a href=# cmenu=httpPortContextMenu onclick=p10WebRouter("' + node._id + '",1,' + (node.httpport ? node.httpport : 80) + ')>' + "HTTP" + ((node.httpport && (node.httpport != 80)) ? '/' + node.httpport : '') + '</a>&nbsp;';
  7447. x += '<a href=# cmenu=httpsPortContextMenu onclick=p10WebRouter("' + node._id + '",2,' + (node.httpsport ? node.httpsport : 443) + ')>' + "HTTPS" + ((node.httpsport && (node.httpsport != 443)) ? '/' + node.httpsport : '') + '</a>&nbsp;';
  7448. }
  7449. if ((node.agent.id > 0) && (node.agent.id < 5)) {
  7450. if (navigator.platform.toLowerCase() == 'win32') {
  7451. if ((serverinfo.devicemeshrouterlinks == null) || (serverinfo.devicemeshrouterlinks.rdp != false)) {
  7452. x += '<a href=# cmenu=altPortContextMenu id=rdpMCRouterLink onclick=p10MCRouter("' + node._id + '",3) title="' + "Requires installation of MeshCentral Router" + '.">' + "RDP" + ((node.rdpport && (node.rdpport != 3389)) ? '/' + node.rdpport : '') + '</a>&nbsp;';
  7453. }
  7454. }
  7455. }
  7456. if (node.agent.id > 4) {
  7457. if ((navigator.platform.toLowerCase() == 'win32') || (navigator.platform.toLowerCase() == 'macintel')) {
  7458. if ((serverinfo.devicemeshrouterlinks == null) || (serverinfo.devicemeshrouterlinks.ssh != false)) {
  7459. x += '<a href=# cmenu=sshPortContextMenu onclick=p10MCRouter("' + node._id + '",4,' + (node.sshport ? node.sshport : 22) + ') title="' + "Requires installation of MeshCentral Router." + '">' + "SSH" + ((node.sshport && (node.sshport != 22)) ? '/' + node.sshport : '') + '</a>&nbsp;';
  7460. }
  7461. }
  7462. if (navigator.platform.toLowerCase() == 'win32') {
  7463. if ((serverinfo.devicemeshrouterlinks == null) || (serverinfo.devicemeshrouterlinks.scp != false)) {
  7464. x += '<a href=# cmenu=sshPortContextMenu onclick=p10MCRouter("' + node._id + '",5,' + (node.sshport ? node.sshport : 22) + ') title="' + "Requires installation of MeshCentral Router." + '">' + "SCP" + ((node.sshport && (node.sshport != 22)) ? '/' + node.sshport : '') + '</a>&nbsp;';
  7465. }
  7466. }
  7467. }
  7468. if ((navigator.platform.toLowerCase() == 'win32') || (navigator.platform.toLowerCase() == 'macintel')) {
  7469. if ((serverinfo.devicemeshrouterlinks != null) && (Array.isArray(serverinfo.devicemeshrouterlinks.extralinks))) {
  7470. for (var i in serverinfo.devicemeshrouterlinks.extralinks) {
  7471. var r = serverinfo.devicemeshrouterlinks.extralinks[i], p = '\"' + r.protocol + '\"';;
  7472. if (doesDeviceMatchFilterTags(node, r.filter)) {
  7473. if (typeof r.protocol == 'number') { p = r.protocol; } else if (r.protocol == 'http') { p = 1; } else if (r.protocol == 'https') { p = 2; } else if (r.protocol == 'rdp') { p = 3; } else if (r.protocol == 'ssh') { p = 4; } else if (r.protocol == 'scp') { p = 5; } else if (r.protocol == 'mcrdesktop') { p = 6; } else if (r.protocol == 'mcrfiles') { p = 7; }
  7474. if (((p == 6) || (p == 7)) && (node.mtype != 2)) continue; // If this is not an agent device, don't show MeshCentral Router desktop/file links.
  7475. x += '<a href=# onclick=p10MCRouter("' + node._id + '",' + p + ',' + r.port + (r.ip?(',\"' + r.ip + '\"'):',null') + ',' + (r.localport?r.localport:0) + ') title="' + "Requires installation of MeshCentral Router." + '">' + r.name + '</a>&nbsp;';
  7476. }
  7477. }
  7478. }
  7479. }
  7480. }
  7481. // noVNC link
  7482. if ((((connectivity & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((meshrights & 8) != 0) && ((features & 0x20000000) == 0)) {
  7483. x += '<a href=# cmenu=rfbPortContextMenu id=rfbLink onclick=p10rfb("' + node._id + '") title="' + "Launch web-based VNC session to this device" + '.">' + "Web-VNC" + '</a>&nbsp;';
  7484. }
  7485. // MSTSC.js link
  7486. if ((((connectivity & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((meshrights & 8) != 0) && ((features & 0x40000000) == 0)) {
  7487. x += '<a href=# cmenu=altPortContextMenu id=mstscLink onclick=p10mstsc("' + node._id + '") title="' + "Launch web-based RDP session to this device" + '.">' + "Web-RDP" + '</a>&nbsp;';
  7488. }
  7489. // SSH link
  7490. if ((features2 & 0x200) && (((connectivity & 1) != 0) || (node.mtype == 3)) && (node.agent) && ((meshrights & 8) != 0)) {
  7491. x += '<a href=# cmenu=sshPortContextMenu id=sshLink onclick=p10ssh("' + node._id + '") title="' + "Launch web-based SSH session to this device" + '.">' + "Web-SSH" + '</a>&nbsp;';
  7492. }
  7493. // MQTT options
  7494. if ((meshrights == 0xFFFFFFFF) && (features & 0x00400000)) { x += '<a href=# onclick=p10showMqttLoginDialog("' + node._id + '") title="' + "Get MQTT login credentials for this device." + '">' + "MQTT Login" + '</a>&nbsp;'; }
  7495. }
  7496. x += '</div><br>'
  7497. if (node.mtype == 3) {
  7498. // If this is a local device, there is no power timeline so display the links below the user rights.
  7499. QH('p10html3', '');
  7500. QH('p10html5', x);
  7501. } else {
  7502. // This is a normal device, display the links below the power timeline.
  7503. QH('p10html3', x);
  7504. QH('p10html5', '');
  7505. }
  7506. // Set the node power state
  7507. var powerstate = PowerStateStr(node.state);
  7508. //if (node.state == 0) { powerstate = 'Unknown State'; }
  7509. var agentPrivilages = '';
  7510. if ((node.agent != null) && (node.agent.root === false)) { agentPrivilages = ', <span title="' + "Agent is running on remote device with reduced privilages." + '">' + "Restricted" + '</span>'; }
  7511. if ((connectivity & 1) != 0) {
  7512. if (powerstate.length > 0) { powerstate += '<br/>'; }
  7513. if (node.mtype == 4) {
  7514. if (node.porttype == 'PDU') {
  7515. powerstate += '<span style=font-size:12px title="' + "Switch port connected" + '">' + "Switch port connected" + '</span>' + agentPrivilages;
  7516. } else {
  7517. powerstate += '<span style=font-size:12px title="' + "IP-KVM port connected" + '">' + "IP-KVM port connected" + '</span>' + agentPrivilages;
  7518. }
  7519. } else {
  7520. powerstate += '<span style=font-size:12px title="' + "Agent connected" + '">' + "Agent connected" + '</span>' + agentPrivilages;
  7521. }
  7522. }
  7523. if ((connectivity & 2) != 0) { if (powerstate.length > 0) { powerstate += '<br/>'; } powerstate += '<span style=font-size:12px title="' + "Intel&reg; AMT connected" + '">' + "Intel&reg; AMT connected" + '</span>'; }
  7524. else if ((connectivity & 4) != 0) { if (powerstate.length > 0) { powerstate += '<br/>'; } powerstate += '<span style=font-size:12px title="' + "Intel&reg; AMT detected" + '">' + "Intel&reg; AMT detected" + '</span>'; }
  7525. if ((connectivity & 16) != 0) { if (powerstate.length > 0) { powerstate += '<br/>'; } powerstate += '<span style=font-size:12px title="' + "MQTT connected" + '">' + "MQTT channel connected" + '</span>'; }
  7526. if ((powerstate == '') && node.lastconnect) { powerstate = '<span style=font-size:12px>' + "Last seen:" + '<br />' + printDateTime(new Date(node.lastconnect)) + '</span>'; }
  7527. else {
  7528. if (node.porttype == 'PDU') { powerstate += ('<br/>' + powerStateStrings[node.pwr]); }
  7529. else if ((node.pwr > 1) && (node.pwr != 7)) { powerstate += ('<br/>' + powerStateStrings[node.pwr]); }
  7530. }
  7531. QH('MainComputerState', powerstate);
  7532. // Set the node icon
  7533. Q('MainComputerImage').setAttribute('src', 'images/icons256-' + node.icon + '-1.png');
  7534. Q('MainComputerImage').className = ((((!node.conn) || (node.conn == 0)) && (node.mtype != 3))?'gray':'');
  7535. // If we are looking at a local non-windows device, enable terminal capability.
  7536. if ((node.mtype == 3) && (node.agent != null) && (node.agent.id > 4) && (features2 & 0x00000200)) { node.agent.caps = 6; } // 1 = Terminal, 2 = Desktop, 4 = files
  7537. // Setup/Refresh the desktop tab
  7538. if (terminalAccess) { setupTerminal(); }
  7539. if (fileAccess) { setupFiles(); }
  7540. var consoleRights = ((meshrights & 16) != 0);
  7541. if (consoleRights) { setupConsole(); } else { if (panel == 15) { panel = 10; } }
  7542. // Show or hide the tabs
  7543. // mesh.mtype: 1 = Intel AMT only, 2 = Mesh Agent, 3 = Local Device
  7544. // node.agent.caps (bitmask): 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console
  7545. QV('MainDevDesktop', desktopAccess && ((((node.agent == null) && (node.intelamt != null) && ((typeof node.intelamt.sku !== 'number') || ((node.intelamt.sku & 8) != 0)))
  7546. || ((node.agent != null) && ((node.agent.caps == null) || ((node.agent.caps & 1) != 0) || (node.intelamt && (node.intelamt.state == 2)))) || ((node.mtype == 3) && ((node.agent.id == 3) || (node.agent.id == 4))))
  7547. && ((meshrights & 8) || (meshrights & 256)))
  7548. );
  7549. QV('MainDevTerminal', (((node.agent == null) && (node.intelamt != null)) || ((node.agent) && (node.agent.caps == null)) || ((node.agent) && ((node.agent.caps & 2) != 0)) || (node.intelamt && (node.intelamt.state == 2))) && (meshrights & 8) && terminalAccess);
  7550. QV('MainDevFiles', (node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 4) != 0) && (meshrights & 8) && fileAccess);
  7551. QV('MainDevInfo', (node.mtype < 3) && ((meshrights & 1048576) != 0));
  7552. QV('MainDevAmt', (node.intelamt != null) && ((node.intelamt.state == 2) || (node.conn & 2)) && (meshrights & 8) && amtAccess);
  7553. QV('MainDevConsole', (consoleRights && ((node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 8) != 0))) && (meshrights & 8));
  7554. QV('MainDevPlugins', false);
  7555. QV('p15uploadCore', (node.agent != null) && (node.agent.caps != null) && ((node.agent.caps & 16) != 0));
  7556. QH('p15coreName', ((node.agent != null) && (node.agent.core != null))?node.agent.core:'');
  7557. // Set the Intel AMT / Intel SM tab name
  7558. if ((node.intelamt != null) && (typeof node.intelamt.sku == 'number') && ((node.intelamt.sku & 16) != 0)) {
  7559. QH('MainDevAmt', "Intel&reg;SM");
  7560. QH('p14deviceNamePrefix', "Intel&reg; SM");
  7561. } else {
  7562. QH('MainDevAmt', "Intel&reg;AMT");
  7563. QH('p14deviceNamePrefix', "Intel&reg; AMT");
  7564. }
  7565. // Setup/Refresh Intel AMT tab
  7566. var amtFrameNode = Q('p14iframe').contentWindow.getCurrentMeshNode();
  7567. if ((amtFrameNode != null) && (amtFrameNode._id != currentNode._id)) { Q('p14iframe').contentWindow.disconnect(); }
  7568. var online = ((node.conn & 6) != 0); // If CIRA (2) or AMT (4) connected, enable Commander
  7569. Q('p14iframe').contentWindow.setConnectionState(online);
  7570. Q('p14iframe').contentWindow.setFrameHeight('650px');
  7571. Q('p14iframe').contentWindow.setAuthCallback(updateAmtCredentials);
  7572. // Request the power timeline
  7573. if ((powerTimelineNode != currentNode._id) && (powerTimelineReq != currentNode._id)) {
  7574. QH('p10html2', '');
  7575. powerTimelineReq = currentNode._id;
  7576. meshserver.send({ action: 'powertimeline', nodeid: currentNode._id });
  7577. meshserver.send({ action: 'lastconnect', nodeid: currentNode._id });
  7578. meshserver.send({ action: 'getsysinfo', nodeid: currentNode._id });
  7579. meshserver.send({ action: 'getnetworkinfo', nodeid: currentNode._id });
  7580. meshserver.send({ action: 'getNotes', id: currentNode._id });
  7581. QH('p17info2', '');
  7582. }
  7583. // Request device sharing
  7584. if ((deviceSharesNode != currentNode._id) && (deviceSharesReq != currentNode._id)) {
  7585. deviceSharesReq = currentNode._id;
  7586. meshserver.send({ action: 'deviceShares', nodeid: currentNode._id });
  7587. }
  7588. // Reset the desktop tools
  7589. QV('DeskTools', false);
  7590. showDeskToolsProcesses();
  7591. // Ask for device events
  7592. refreshDeviceEvents();
  7593. // Update the web page title
  7594. if ((currentNode) && (xxcurrentView >= 10) && (xxcurrentView < 20)) {
  7595. document.title = currentNode.name + (mesh?(' - ' + mesh.name):'') + ' - ' + decodeURIComponent('{{{extitle}}}');
  7596. } else {
  7597. document.title = decodeURIComponent('{{{extitle}}}');
  7598. }
  7599. // Clear user consent status if present
  7600. if (deviceSwitch) {
  7601. p11clearConsoleMsg();
  7602. p12clearConsoleMsg();
  7603. p13clearConsoleMsg();
  7604. }
  7605. // Device refresh plugin handler
  7606. if (pluginHandler != null) {
  7607. QH('p19headers', ''); QH('p19pages', '');
  7608. pluginHandler.callHook('onDeviceRefreshEnd', nodeid, panel, refresh, event);
  7609. var lastTab = getstore('_curPluginPage', null);
  7610. if (lastTab != null && Q('p19ph-' + lastTab) != null) pluginHandler.callPluginPage(lastTab, Q('p19ph-' + lastTab));
  7611. }
  7612. // Show user device permissions
  7613. x = '';
  7614. if (meshrights & 7) {
  7615. x += '<a href=# onclick="return p20showAddMeshUserDialog(5)" style=cursor:pointer;margin-right:10px><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add User" + '</a>';
  7616. if (usergroups != null) {
  7617. var userGroupCount = 0, newUserGroup = false;
  7618. for (var i in usergroups) {
  7619. if ((usergroups[i].membershipType != null) || (usergroups[i]._id.split('/')[1] != nodeid.split('/')[1])) continue;
  7620. userGroupCount++; if ((currentNode.links == null) || (currentNode.links[i] == null)) { newUserGroup = true; }
  7621. }
  7622. if ((userGroupCount > 0) && (newUserGroup)) { x += '<a href=# onclick="return p20showAddMeshUserDialog(6)" style=cursor:pointer;margin-right:10px><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add User Group" + '</a>'; }
  7623. }
  7624. }
  7625. 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><th scope=col style=text-align:left></th></tr>';
  7626. var count = 1;
  7627. if (currentNode.links != null) {
  7628. // Sort the list of users to display
  7629. var useridlist = [];
  7630. for (var i in currentNode.links) { if (i.startsWith('user/') || i.startsWith('ugrp/')) { useridlist.push(i); } }
  7631. useridlist.sort();
  7632. for (var i in useridlist) {
  7633. var trash = '', rights = '', userid = useridlist[i], srights = currentNode.links[userid].rights, username = EscapeHtml(userid.split('/')[2]), rights = makeUserDeviceRightsString(srights), ugroup = false;
  7634. if (currentNode.links[userid].name != null) { username = EscapeHtml(currentNode.links[userid].name); }
  7635. if (userid == userinfo._id) { username = EscapeHtml(userinfo.name); }
  7636. if ((users != null) && (users[userid] != null)) { username = EscapeHtml(users[userid].name); }
  7637. if ((usergroups != null) && (usergroups[userid] != null)) { username = EscapeHtml(usergroups[userid].name); ugroup = true; }
  7638. if ((meshrights & 2) != 0) {
  7639. if (ugroup) {
  7640. trash = '<a href=# onclick=\'return p30removeUserFromNode(event,"' + encodeURIComponentEx(userid) + '")\' title="' + "Remove user group rights to this device group" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>';
  7641. rights = '<span style=cursor:pointer onclick=p20showAddMeshUserDialog(6,"' + encodeURIComponentEx(userid) + '")>' + rights + ' <img class=hoverButton style=cursor:pointer src=images/link5.png></span>';
  7642. } else {
  7643. trash = '<a href=# onclick=\'return p30removeUserFromNode(event,"' + encodeURIComponentEx(userid) + '")\' title="' + "Remove user rights to this device group" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>';
  7644. rights = '<span style=cursor:pointer onclick=p20showAddMeshUserDialog(5,"' + encodeURIComponentEx(userid) + '")>' + rights + ' <img class=hoverButton style=cursor:pointer src=images/link5.png></span>';
  7645. }
  7646. }
  7647. if (users != null) {
  7648. if (ugroup) {
  7649. username = '<a href=# onclick=\'gotoUserGroup("' + encodeURIComponentEx(userid) + '");haltEvent(event);\'>' + username + '</a>';
  7650. } else {
  7651. username = '<a href=# onclick=\'gotoUser("' + encodeURIComponentEx(userid) + '");haltEvent(event);\'>' + username + '</a>';
  7652. }
  7653. }
  7654. x += '<tr ' + (((++count % 2) == 0) ? 'style=background-color:#DDD' : '') + '><td style=width:30%><div title="' + (ugroup?"User Group":"User") + '" class=m' + (ugroup?4:2) + '></div><div>&nbsp;' + username + '<div></div></div></td><td style=width:70%><div style=float:right>' + trash + '</div><div>' + rights + '</div></td></tr>';
  7655. }
  7656. }
  7657. if (count == 1) { x += '<tr><td><div style=padding:6px>&nbsp;<i>' + "No users with special device permissions" + '</i><div></div></div></td><td></td></tr>'; }
  7658. x += '</tbody></table>';
  7659. // Show device shares
  7660. if ((deviceShares != null) && (deviceSharesNode == currentNode._id) && (deviceShares.length > 0)) {
  7661. x += '<br /><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>' + "Active Device Sharing" + '</th><th scope=col style=text-align:left></th></tr>';
  7662. count = 1;
  7663. for (var i = 0; i < deviceShares.length; i++) {
  7664. var dshare = deviceShares[i], trash = '';
  7665. if (dshare.url != null) { trash += '<a href="' + dshare.url + '" rel="noreferrer noopener" target=_blank title="' + "Device Sharing Link" + '" style=cursor:pointer><img src=images/link2.png border=0 height=10 width=10></a> '; }
  7666. trash += '<a href=# onclick=\'return p30removeDeviceSharing(event,"' + encodeURIComponentEx(currentNode._id) + '","' + encodeURIComponentEx(dshare.publicid) + '","' + encodeURIComponentEx(dshare.guestName) + '")\' title="' + "Remove device sharing" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>';
  7667. var type = ''; if (dshare.p <= 7) { type = ['', "Terminal", "Desktop", "Desktop + Terminal", "Files", "Terminal + Files", "Desktop + Files", "Desktop + Terminal + Files"][dshare.p]; } else if (dshare.p == 8) { type = "HTTP/" + dshare.port; } else if (dshare.p == 16) { type = "HTTPS/" + dshare.port; }
  7668. var details = type;
  7669. if ((dshare.startTime != null) && (dshare.expireTime != null)) { details = format("{0}, {1} to {2}", type, printFlexDateTime(new Date(dshare.startTime)), printFlexDateTime(new Date(dshare.expireTime))); }
  7670. if ((dshare.startTime != null) && (dshare.duration != null)) {
  7671. if (dshare.duration < 2) {
  7672. details = format("{0}, {1} for {2} minute", type, printFlexDateTime(new Date(dshare.startTime)), dshare.duration);
  7673. } else {
  7674. details = format("{0}, {1} for {2} minutes", type, printFlexDateTime(new Date(dshare.startTime)), dshare.duration);
  7675. }
  7676. }
  7677. if (dshare.recurring == 1) { details += ", Reccuring daily"; }
  7678. if (dshare.recurring == 2) { details += ", Reccuring weekly"; }
  7679. if (((dshare.p & 2) != 0) && (dshare.viewOnly === true)) { details += ", View only desktop"; }
  7680. if (dshare.consent != null) {
  7681. if (dshare.consent == 0) { details += ", No Consent"; } else {
  7682. if ((dshare.consent & 0x0038) != 0) { details += ", Prompt for consent"; }
  7683. if ((dshare.consent & 0x0040) != 0) { details += ", Toolbar"; }
  7684. }
  7685. }
  7686. var guestName = EscapeHtml(dshare.guestName);
  7687. if (dshare.publicid.startsWith('AS:node/')) { guestName = '<i>' + "Agent Self-Share" + '</i>'; }
  7688. x += '<tr ' + (((++count % 2) == 0) ? 'style=background-color:#DDD' : '') + '><td style=width:30%><div class=m' + 2 + '></div><div>&nbsp;' + guestName + '<div></div></div></td><td style=width:70%><div style=float:right>' + trash + '</div><div>' + details + '</div></td></tr>';
  7689. }
  7690. x += '</tbody></table>';
  7691. }
  7692. QH('p10html4', x);
  7693. // Change the URL
  7694. var urlviewmode = '';
  7695. if (((features & 0x10000000) == 0) && (xxcurrentView >= 10) && (xxcurrentView <= 19) && (currentNode != null)) {
  7696. urlviewmode = '?viewmode=' + xxcurrentView + '&gotonode=' + currentNode._id.split('/')[2];
  7697. for (var i in urlargs) { urlviewmode += ('&' + i + '=' + urlargs[i]); }
  7698. try { window.history.replaceState({}, document.title, window.location.pathname + urlviewmode); } catch (ex) { }
  7699. }
  7700. // Clear the desktop session selector
  7701. QV('p11DeskSessionSelector', false);
  7702. QH('p11DeskSessionSelector', '');
  7703. }
  7704. setupDesktop(); // Always refresh the desktop, even if we are on the same device, we need to do some canvas switching.
  7705. if (!panel) panel = 10;
  7706. go(panel);
  7707. }
  7708. function setIpPduState(op) {
  7709. if (op == 0) {
  7710. setDialogMode(2, "Power Operation", 3, function() { meshserver.send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: 2 }); }, "Perform power off?"); // Turn off
  7711. } else {
  7712. setDialogMode(2, "Power Operation", 3, function() { meshserver.send({ action: 'wakedevices', nodeids: [ currentNode._id ] }); }, "Perform power on?"); // Turn on
  7713. }
  7714. }
  7715. function p20editDeviceNotify() {
  7716. if (xxdialogMode) return false;
  7717. var devNotify = 0, fx = ((features2 & 0x00004000) && (userinfo.emailVerified))?1:0;
  7718. if (userinfo.notify && userinfo.notify[currentNode._id]) { devNotify = userinfo.notify[currentNode._id]; }
  7719. var x = '<div style="border-bottom: 1px solid #888;margin-bottom:3px">' + "Web Page Notifications" + '</div>';
  7720. x += '<div><label><input id=p20notifyIntelDeviceConnect type=checkbox />' + "Device connections" + '</label></div>';
  7721. x += '<div><label><input id=p20notifyIntelDeviceDisconnect type=checkbox />' + "Device disconnections" + '</label></div>';
  7722. if (currentNode.intelamt != null) { fx += 2; x += '<div><label><input id=p20notifyIntelAmtKvmActions type=checkbox />' + "Intel&reg; AMT desktop and serial events" + '</label></div>'; }
  7723. if (fx & 1) {
  7724. x += '<br /><div style="border-bottom: 1px solid #888;margin-bottom:3px">' + "Email Notifications" + '</div>';
  7725. x += '<div><label><input id=p20enotifyIntelDeviceConnect type=checkbox />' + "Device connections" + '</label></div>';
  7726. x += '<div><label><input id=p20enotifyIntelDeviceDisconnect type=checkbox />' + "Device disconnections" + '</label></div>';
  7727. x += '<div><label><input id=p20enotifyIntelDeviceHelp type=checkbox />' + "Help requests" + '</label></div>';
  7728. }
  7729. if ((userinfo.msghandle != null) && ((features2 & 0x02000000) != 0)) {
  7730. x += '<br /><div style="border-bottom: 1px solid #888;margin-bottom:3px">' + "Messaging Notifications" + '</div>';
  7731. x += '<div><label><input id=p20emsgDeviceConnect type=checkbox />' + "Device connections" + '</label></div>';
  7732. x += '<div><label><input id=p20emsgDeviceDisconnect type=checkbox />' + "Device disconnections" + '</label></div>';
  7733. x += '<div><label><input id=p20emsgDeviceHelp type=checkbox />' + "Help requests" + '</label></div>';
  7734. }
  7735. setDialogMode(2, "Notification Settings", 3, p20editDeviceNotifyEx, x, fx);
  7736. Q('p20notifyIntelDeviceConnect').checked = (devNotify & 2);
  7737. Q('p20notifyIntelDeviceDisconnect').checked = (devNotify & 4);
  7738. if (fx & 2) { Q('p20notifyIntelAmtKvmActions').checked = (devNotify & 8); }
  7739. if (fx & 1) {
  7740. Q('p20enotifyIntelDeviceConnect').checked = (devNotify & 16);
  7741. Q('p20enotifyIntelDeviceDisconnect').checked = (devNotify & 32);
  7742. Q('p20enotifyIntelDeviceHelp').checked = (devNotify & 64);
  7743. }
  7744. if ((userinfo.msghandle != null) && ((features2 & 0x02000000) != 0)) {
  7745. Q('p20emsgDeviceConnect').checked = (devNotify & 128);
  7746. Q('p20emsgDeviceDisconnect').checked = (devNotify & 256);
  7747. Q('p20emsgDeviceHelp').checked = (devNotify & 512);
  7748. }
  7749. return false;
  7750. }
  7751. function p20editDeviceNotifyEx(b, fx) {
  7752. var devNotify = 0;
  7753. devNotify += Q('p20notifyIntelDeviceConnect').checked ? 2 : 0;
  7754. devNotify += Q('p20notifyIntelDeviceDisconnect').checked ? 4 : 0;
  7755. if (fx & 2) { devNotify += Q('p20notifyIntelAmtKvmActions').checked ? 8 : 0; }
  7756. if (fx & 1) {
  7757. devNotify += Q('p20enotifyIntelDeviceConnect').checked ? 16 : 0;
  7758. devNotify += Q('p20enotifyIntelDeviceDisconnect').checked ? 32 : 0;
  7759. devNotify += Q('p20enotifyIntelDeviceHelp').checked ? 64 : 0;
  7760. }
  7761. if ((userinfo.msghandle != null) && ((features2 & 0x02000000) != 0)) {
  7762. devNotify += Q('p20emsgDeviceConnect').checked ? 128 : 0;
  7763. devNotify += Q('p20emsgDeviceDisconnect').checked ? 256 : 0;
  7764. devNotify += Q('p20emsgDeviceHelp').checked ? 512 : 0;
  7765. }
  7766. meshserver.send({ action: 'changeusernotify', nodeid: currentNode._id, notify: devNotify });
  7767. }
  7768. function makeUserDeviceRightsString(rights) {
  7769. if (rights == 57592) { return "Full Device Rights"; }
  7770. var str = [];
  7771. if (rights & 8) {
  7772. var str1 = [];
  7773. if (rights & 256) str1.push("No Input");
  7774. if (rights & 512) str1.push("No Terminal");
  7775. if (rights & 1024) str1.push("No Files");
  7776. if (rights & 2048) str1.push("No AMT");
  7777. if (rights & 4096) str1.push("Limited Input");
  7778. if (rights & 65536) str1.push("No Desktop");
  7779. if ((rights & 524288) && (serverinfo.guestdevicesharing !== false)) str1.push("Guest Share");
  7780. if (str1.length > 0) { str.push('Control (' + str1.join(', ') + ')'); } else { str.push("Control"); }
  7781. }
  7782. if (rights & 16) str.push("Console");
  7783. if (rights & 32) str.push("Server Files");
  7784. if (rights & 64) str.push("Wake");
  7785. if (rights & 128) str.push("Notes");
  7786. if (rights & 8192) str.push("Limit Events");
  7787. if (rights & 16384) str.push("Chat");
  7788. if (rights & 32768) str.push("Uninstall");
  7789. if (rights & 131072) str.push("Commands");
  7790. if (rights & 262144) str.push("Reset/Off");
  7791. if (rights & 524288) str.push("Sharing");
  7792. if (rights & 1048576) str.push("Details");
  7793. if (rights & 2097152) str.push("Relay");
  7794. if (str.length == 0) return "No Rights";
  7795. return str.join(', ');
  7796. }
  7797. function makeDeviceGroupRightsString(rights) {
  7798. if (rights == 0xFFFFFFFF) { return "Full Rights"; }
  7799. var str = [];
  7800. if (rights & 1) str.push("Edit Group");
  7801. if (rights & 2) str.push("Manage Users");
  7802. if (rights & 4) str.push("Manage Devices");
  7803. if (rights & 8) {
  7804. var str1 = [];
  7805. if (rights & 256) str1.push("No Input");
  7806. if (rights & 512) str1.push("No Terminal");
  7807. if (rights & 1024) str1.push("No Files");
  7808. if (rights & 2048) str1.push("No AMT");
  7809. if (rights & 4096) str1.push("Limited Input");
  7810. if (rights & 65536) str1.push("No Desktop");
  7811. if ((rights & 524288) && (serverinfo.guestdevicesharing !== false)) str1.push("Guest Share");
  7812. if (str1.length > 0) { str.push('Control (' + str1.join(', ') + ')'); } else { str.push("Control"); }
  7813. }
  7814. if (rights & 16) str.push("Console");
  7815. if (rights & 32) str.push("Server Files");
  7816. if (rights & 64) str.push("Wake");
  7817. if (rights & 128) str.push("Notes");
  7818. if (rights & 8192) str.push("Limit Events");
  7819. if (rights & 16384) str.push("Chat");
  7820. if (rights & 32768) str.push("Uninstall");
  7821. if (rights & 131072) str.push("Commands");
  7822. if (rights & 262144) str.push("Reset/Off");
  7823. if (rights & 524288) str.push("Sharing");
  7824. if (rights & 1048576) str.push("Details");
  7825. if (rights & 2097152) str.push("Relay");
  7826. if (str.length == 0) return "No Rights";
  7827. return str.join(', ');
  7828. }
  7829. // Run commands on current device
  7830. function runDeviceCmd(nodeid) { if (xxdialogMode) return; d2runCommandDialog({ nodeids: [ nodeid ? decodeURIComponent(nodeid) : currentNode._id ] }); }
  7831. function writeDeviceEvent(nodeid) {
  7832. if (xxdialogMode) return;
  7833. setDialogMode(2, "Add Device Event", 3, writeDeviceEventEx, '<textarea id=d2devEvent style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll></textarea><span style=font-size:10px>' + "This will add an entry to this device's event log." + '<span>', nodeid);
  7834. Q('d2devEvent').focus();
  7835. }
  7836. function writeDeviceEventEx(buttons, tag) { meshserver.send({ action: 'setDeviceEvent', nodeid: decodeURIComponent(tag), msg: encodeURIComponentEx(Q('d2devEvent').value) }); }
  7837. function showNotes(readonly, noteid) {
  7838. if (xxdialogMode) return;
  7839. if (noteid == null) { noteid = encodeURIComponentEx('p'+userinfo._id); }
  7840. var x = '<textarea id=d2devNotes ro=' + readonly + ' noteid=' + noteid + ' readonly style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll></textarea>';
  7841. 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>'; }
  7842. if (showNotesPanel === 'true') { x += ' <span style=font-size:10px><a target=_blank href=\'https://www.markdownguide.org/cheat-sheet/\'>' + "Markdown syntax supported" + '</a></span>'; }
  7843. setDialogMode(2, "Notes", 3, showNotesEx, x, noteid);
  7844. meshserver.send({ action: 'getNotes', id: decodeURIComponent(noteid) });
  7845. }
  7846. function showNotesEx(buttons, tag) {
  7847. Q('notesPanelArea').innerHTML = (marked && DOMPurify) ? DOMPurify.sanitize(marked.parse(Q('d2devNotes').value, { breaks: true }), { USE_PROFILES: { html: true } }) : Q('d2devNotes').value;
  7848. meshserver.send({ action: 'setNotes', id: decodeURIComponent(tag), notes: encodeURIComponentEx(Q('d2devNotes').value) });
  7849. if ((showNotesPanel === 'true') && Q('d2devNotes').value != '') { QV('notesPanel',true); }else{ QV('notesPanel', false); }
  7850. }
  7851. function openIpKvmRemoteControl(nodeid) {
  7852. if (xxdialogMode) return;
  7853. var nid = decodeURIComponent(nodeid).split('/')[2];
  7854. safeNewWindow('/ipkvm.ashx/' + nid + '/', 'ipkvm:' + nid);
  7855. }
  7856. function deviceChat(e) {
  7857. if (xxdialogMode) return;
  7858. var url = '/messenger?id=meshmessenger/' + encodeURIComponentEx(currentNode._id) + '/' + encodeURIComponentEx(userinfo._id) + '&title=' + currentNode.name;
  7859. if (serverinfo.domainsuffix != '') { url = '/' + serverinfo.domainsuffix + url; }
  7860. if ((authCookie != null) && (authCookie != '')) { url += '&auth=' + authCookie; }
  7861. if ((currentNode.pmt == 1) && ((features2 & 2) != 0)) { url += '&pmt=1'; } // Push messaging is possible for this device
  7862. if (e && (e.shiftKey == true)) {
  7863. safeNewWindow(url, 'meshmessenger:' + currentNode._id);
  7864. } else {
  7865. 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');
  7866. }
  7867. meshserver.send({ action: 'meshmessenger', nodeid: decodeURIComponent(currentNode._id) });
  7868. }
  7869. function altDeviceChat(e, i) {
  7870. if (xxdialogMode) return;
  7871. var url = serverinfo.altmessenging[i].url.split('{0}').join(currentNode._id.split('/')[2]).split('{1}').join(currentNode._id.split('/')[2]).split('{2}').join(currentNode._id.split('/')[2]).split('{3}').join(currentNode._id.split('/')[2]);
  7872. var userid1 = encodeURIComponentEx(userinfo._id.split('/')[2]); // userid
  7873. var userid2 = encodeURIComponentEx(userinfo._id.split('/').join('-')); // user-domain-userid
  7874. var userid3 = userid1, userid4 = userid2;
  7875. if (userinfo.realname != null) {
  7876. userid3 = encodeURIComponentEx(userinfo.realname.split(' ').join('')); // real name with no empty spaces
  7877. userid4 = encodeURIComponentEx(userinfo.realname.split(' ').join('-')); // real name with - instead of spaces
  7878. }
  7879. url = url.split('{4}').join(userid1).split('{5}').join(userid2).split('{6}').join(userid3).split('{7}').join(userid4);
  7880. var localurl = url;
  7881. if (typeof serverinfo.altmessenging[i].localurl == 'string') {
  7882. localurl = serverinfo.altmessenging[i].localurl.split('{0}').join(currentNode._id.split('/')[2]).split('{1}').join(currentNode._id.split('/')[2]).split('{2}').join(currentNode._id.split('/')[2]).split('{3}').join(currentNode._id.split('/')[2]);
  7883. localurl = localurl.split('{4}').join(userid1).split('{5}').join(userid2).split('{6}').join(userid3).split('{7}').join(userid4);
  7884. }
  7885. if (url != '') { meshserver.send({ action: 'msg', type: 'openUrl', nodeid: currentNode._id, url: url }); }
  7886. if (localurl != '') { safeNewWindow(localurl, 'altmessenger:' + currentNode._id, 'directories=no,titlebar=no,toolbar=no,location=no,status=no,menubar=no,scrollbars=no,resizable=no,width=400,height=560'); }
  7887. }
  7888. function deviceToggleBackground() {
  7889. if (xxdialogMode) return;
  7890. meshserver.send({ action: 'msg', type: 'deskBackground', nodeid: currentNode._id, op: 1 }); // Toggle desktop background image
  7891. }
  7892. function deviceUrlFunction() {
  7893. if (xxdialogMode) return;
  7894. 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>');
  7895. Q('d2devurl').focus();
  7896. deviceUrlFunctionValidate();
  7897. }
  7898. function deviceUrlFunctionValidate() {
  7899. var x = Q('d2devurl').value.toLowerCase();
  7900. QE('idx_dlgOkButton', ((x.startsWith('http://') && (x.length > 7)) || (x.startsWith('https://') && (x.length > 8))));
  7901. }
  7902. function deviceUrlFunctionEx() {
  7903. meshserver.send({ action: 'msg', type: 'openUrl', nodeid: currentNode._id, url: Q('d2devurl').value });
  7904. }
  7905. function deviceMessageFunction() {
  7906. if (xxdialogMode) return;
  7907. var x = '<div style=margin-bottom:4px>' + "Display a message box on the remote device." + '</div>';
  7908. x += '<textarea id=d2devMessage style=background-color:#fcf3cf;width:100%;height:80px;resize:none;overflow-y:scroll;box-sizing:border-box;margin-bottom:4px></textarea>';
  7909. x += '<select style=width:100% id=d2devTimeout>';
  7910. x += '<option value=2 selected>' + "Show for 2 Minutes (Default)" + '</option>';
  7911. x += '<option value=10>' + "Show for 10 minutes" + '</option>';
  7912. x += '<option value=30>' + "Show for 30 minutes" + '</option>';
  7913. x += '<option value=60>' + "Show for 60 minutes" + '</option>';
  7914. x += '<option value=0>' + "Show message until dismissed by user" + '</option>';
  7915. x += '</select>';
  7916. setDialogMode(2, "Device Message", 3, deviceMessageFunctionEx, x);
  7917. Q('d2devMessage').focus();
  7918. }
  7919. function deviceMessageFunctionEx() {
  7920. var title = decodeURIComponent('{{{extitle}}}'), timeout = parseFloat(Q('d2devTimeout').value);
  7921. if (title == '') { title = "MeshCentral"; }
  7922. if(isNaN(timeout)){ timeout = 2; }
  7923. if (currentNode.pmt == 1) {
  7924. meshserver.send({ action: 'pushmessage', nodeid: currentNode._id, title: title, msg: Q('d2devMessage').value });
  7925. } else {
  7926. meshserver.send({ action: 'msg', type: 'messagebox', nodeid: currentNode._id, title: title, msg: Q('d2devMessage').value, timeout: (timeout * 60000) });
  7927. }
  7928. }
  7929. function deskClipboardInFunction() {
  7930. if (desktop == null || desktop.State != 3) return;
  7931. if (desktop.m.getClipboard) {
  7932. var text = desktop.m.getClipboard();
  7933. if ((text != null) && (navigator.clipboard != null)) { navigator.clipboard.writeText(text).then(function() { }).catch(function(err) { console.log(err); }) } // Put remote clipboard data into our clipboard
  7934. } else {
  7935. meshserver.send({ action: 'msg', type: 'getclip', nodeid: currentNode._id, tag: 2 });
  7936. }
  7937. }
  7938. // Called to lock or unlock remote desktop user input
  7939. function deskInputLockFunction(value) {
  7940. if (xxdialogMode || desktop == null || desktop.State != 3) return;
  7941. setDialogMode(2, "Remote Input Lock", 3, function() { try { desktop.m.SendRemoteInputLock(value); } catch (ex) {} }, (value == 1)?"Lock remote user's mouse and keyboard?":"Unlock remote user's mouse and keyboard?");
  7942. }
  7943. function deskClipboardOutFunction() {
  7944. if ((navigator.clipboard != null) && (navigator.clipboard.readText != null)) {
  7945. try {
  7946. navigator.clipboard.readText().then(function(text) {
  7947. if (desktop.m.setClipboard) { desktop.m.setClipboard(text); } else { meshserver.send({ action: 'msg', type: 'setclip', nodeid: currentNode._id, data: text }); }
  7948. }).catch(function(err) { console.log(err); });
  7949. } catch (ex) { console.log(ex); }
  7950. }
  7951. return true;
  7952. }
  7953. function deskRefreshFunction() {
  7954. if (desktop == null || desktop.State != 3) return;
  7955. desktop.m.SendRefresh();
  7956. }
  7957. function deviceToastFunction() {
  7958. if (xxdialogMode) return;
  7959. var x = '<select id=d2deviceop style=width:100%;margin-bottom:4px><option value=2>' + "Toast Notification" + '</option><option value=1>' + "Message Box" + '</option><option value=3>' + "Alert Box" + '</option></select>';
  7960. x += '<input id=dp2notifyTitle maxlength=256 placeholder="' + "Title" + '" style=width:100%;box-sizing:border-box;margin-bottom:4px />';
  7961. x += '<textarea id=d2notifyMsg style=background-color:#fcf3cf;width:100%;height:140px;resize:none;overflow-y:scroll;box-sizing:border-box;margin-bottom:4px></textarea>';
  7962. x += '<select style=width:100% id=d2notifyTimeout>';
  7963. x += '<option disabled value="">Only Applicable to Message Box Notifications</option>';
  7964. x += '<option value=2 selected>' + "Show for 2 Minutes (Default)" + '</option>';
  7965. x += '<option value=10>' + "Show for 10 minutes" + '</option>';
  7966. x += '<option value=30>' + "Show for 30 minutes" + '</option>';
  7967. x += '<option value=60>' + "Show for 60 minutes" + '</option>';
  7968. x += '<option value=0>' + "Show message until dismissed by user" + '</option>';
  7969. x += '</select>';
  7970. setDialogMode(2, "Device Notification", 3, deviceToastFunctionEx, x);
  7971. Q('d2notifyMsg').focus();
  7972. }
  7973. function deviceToastFunctionEx() {
  7974. var op = Q('d2deviceop').value, title = Q('dp2notifyTitle').value, msg = Q('d2notifyMsg').value, timeout = parseFloat(Q('d2notifyTimeout').value);
  7975. if (msg.length == 0) return;
  7976. if (title == '') { title = decodeURIComponent('{{{extitle}}}'); }
  7977. if (title == '') { title = "MeshCentral"; }
  7978. if(isNaN(timeout)){ timeout = 2; }
  7979. if (op == 1) { // MessageBox
  7980. meshserver.send({ action: 'msg', type: 'messagebox', nodeid: currentNode._id, title: title, msg: msg, timeout: (timeout * 60000) });
  7981. } else if (op == 2) { // Toast
  7982. meshserver.send({ action: 'toast', nodeids: [ currentNode._id ], title: title, msg: msg });
  7983. } else if (op == 3) { // Old Style MessageBox
  7984. meshserver.send({ action: 'msg', type: 'alertbox', nodeid: currentNode._id, title: title, msg: msg });
  7985. }
  7986. }
  7987. // Lock desktop
  7988. function deviceLockFunction() {
  7989. if ((xxdialogMode == null) && (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?"); }
  7990. }
  7991. function showShareDevice() {
  7992. if (xxdialogMode) return;
  7993. var rights = GetNodeRights(currentNode);
  7994. var y = '', x = "Creates a link that allows a guest without an account to remote control this device for a limited time." + '<br /><br />';
  7995. x += addHtmlValue("Guest Name", '<input id=d2inviteName style=width:250px maxlength=128 type=text onkeyup=showShareDeviceValidate() />');
  7996. var deskFull = '<option value=2>' + "Desktop" + '</option>';
  7997. if ((rights != 0xFFFFFFFF) && ((rights & 0x100) != 0)) { deskFull = ''; }
  7998. var fullTerm = '<option value=1>' + "Terminal" + '</option>';
  7999. if ((rights != 0xFFFFFFFF) && ((rights & 0x200) != 0)) { fullTerm = ''; }
  8000. var fullFiles = '<option value=4>' + "Files" + '</option>';
  8001. if ((rights != 0xFFFFFFFF) && ((rights & 0x400) != 0)) { fullFiles = ''; }
  8002. var deskFiles = '<option value=5>' + "Desktop + Files" + '</option>';
  8003. if ((rights != 0xFFFFFFFF) && ((rights & 0x500) != 0)) { deskFiles = ''; }
  8004. var termFiles = '<option value=6>' + "Terminal + Files" + '</option>';
  8005. if ((rights != 0xFFFFFFFF) && ((rights & 0x600) != 0)) { termFiles = ''; }
  8006. var allFeatures = '<option value=7>' + "Desktop + Terminal + Files" + '</option>';
  8007. if ((rights != 0xFFFFFFFF) && ((rights & 0x700) != 0)) { allFeatures = ''; }
  8008. var httpFeature = '';
  8009. if (webRelayPort != 0) {
  8010. httpFeature = '<option value=8>' + "HTTP" + '</option><option value=9>' + "HTTPS" + '</option>';
  8011. if ((rights != 0xFFFFFFFF) && ((rights & 8) != 0)) { httpFeature = ''; }
  8012. }
  8013. var y = '', z = '';
  8014. if ((currentNode.agent.caps & 1) == 1) { y += (deskFull + '<option value=3>' + "Desktop, View only" + '</option>'); } // Agent is desktop capable
  8015. if ((currentNode.agent.caps & 2) == 2) { y += fullTerm; } // Agent is terminal capable
  8016. if ((currentNode.agent.caps & 4) == 4) { y += fullFiles; } // Agent is files capable
  8017. if ((currentNode.agent.caps & 5) == 5) { y += deskFiles; } // Agent is desktop + files capable
  8018. if ((currentNode.agent.caps & 6) == 6) { y += termFiles; } // Agent is terminal + files capable
  8019. if ((currentNode.agent.caps & 7) == 7) { y += allFeatures; } // Agent is desktop + terminal + files capable
  8020. y += httpFeature;
  8021. x += addHtmlValue("Type", '<select id=d2shareType style=float:right;width:250px onchange=showShareDeviceValidate()>' + y + '</select>');
  8022. var options = { 1 : "1 minute", 5 : "5 minutes", 10 : "10 minutes", 15 : "15 minutes", 30 : "30 minutes", 45 : "45 minutes", 60 : "60 minutes", 120 : "2 hours", 240 : "4 hours", 480 : "8 hours", 720 : "12 hours", 960 : "16 hours", 1440 : "24 hours", 2880 : "2 days", 5760 : "4 days", 0 : "Unlimited" }
  8023. y = '';
  8024. for (var i in options) {
  8025. if ((serverinfo.guestdevicesharingmaxtime == null) || ((i > 0) && (i <= serverinfo.guestdevicesharingmaxtime))) {
  8026. y += '<option value=' + i + '>' + options[i] + '</option>';
  8027. }
  8028. if ((i > 0) && (i <= 720) && ((serverinfo.guestdevicesharingmaxtime == null) || ((i > 0) && (i <= serverinfo.guestdevicesharingmaxtime)))) {
  8029. z += '<option value=' + i + '>' + options[i] + '</option>';
  8030. }
  8031. }
  8032. x += addHtmlValue("Validity", '<select id=d2timeRange style=float:right;width:250px onchange=showShareDeviceValidate()><option value=0>' + "Starting now" + '</option><option value=1>' + "Time range" + '</option><option value=2>' + "Recurring daily" + '</option><option value=3>' + "Recurring weekly" + '</option></select>');
  8033. x += '<div id=d2modenow>';
  8034. x += addHtmlValue("Expire Time", '<select id=d2inviteExpire style=float:right;width:250px>' + y + '</select>');
  8035. x += '</div><div id=d2moderange style=display:none>';
  8036. x += addHtmlValue("Time Range Start", '<input id=d2timeRangeStartSelector style=float:right;width:250px class=flatpickr type="text" placeholder="' + "Select Date & Time..." + '" data-id="altinput">');
  8037. x += addHtmlValue("Time Range End", '<input id=d2timeRangeEndSelector style=float:right;width:250px class=flatpickr type="text" placeholder="' + "Select Date & Time..." + '" data-id="altinput">');
  8038. x += '</div><div id=d2moderecurring style=display:none>';
  8039. x += addHtmlValue("Start Time", '<input id=d2timeStartSelector style=float:right;width:250px class=flatpickr type="text" placeholder="' + "Select Date & Time..." + '" data-id="altinput">');
  8040. x += addHtmlValue("Duration", '<select id=d2inviteDuration style=float:right;width:250px>' + z + '</select>');
  8041. x += '</div>';
  8042. if (currentNode.agent.caps & 1) { x += '<div id=d2userConsentSelector>' + addHtmlValue("User Consent", '<select id=d2userConsent style=float:right;width:250px><option value=0>' + "Notify Only" + '<option value=1>' + "Prompt for consent" + '</option><option value=2>' + "No Consent" + '</option></select>') + '</div>'; }
  8043. x += '<div id=d2httpPortSelector>' + addHtmlValue("Port", '<input id=d2httpPort style=float:right;width:250px value=80 onkeyup=showShareDeviceValidate()></input>') + '</div>';
  8044. x += '<div id=d2httpsPortSelector>' + addHtmlValue("Port", '<input id=d2httpsPort style=float:right;width:250px value=443 onkeyup=showShareDeviceValidate()></input>') + '</div>';
  8045. setDialogMode(2, "Share Device", 3, showShareDeviceEx, x);
  8046. showShareDeviceValidate();
  8047. var tomorrow = new Date();
  8048. tomorrow.setDate(tomorrow.getDate() + 1);
  8049. var rangeStartTime = flatpickr('#d2timeRangeStartSelector', {
  8050. enableTime: true, minDate: new Date(), defaultDate: new Date(), minuteIncrement: 1, plugins: [new confirmDatePlugin({})],
  8051. onChange: function(selectedDates, dateStr, instance) {
  8052. rangeEndTime.set('minDate', dateStr);
  8053. if (rangeEndTime.selectedDates[0] && rangeEndTime.selectedDates[0] < selectedDates[0]) {
  8054. rangeEndTime.clear();
  8055. }
  8056. }
  8057. });
  8058. var rangeEndTime = flatpickr('#d2timeRangeEndSelector', { enableTime: true, minDate: new Date(), defaultDate: tomorrow, minuteIncrement: 1, plugins: [new confirmDatePlugin({})] });
  8059. var startTime = flatpickr('#d2timeStartSelector', { enableTime: true, minDate: new Date(), defaultDate: new Date(), minuteIncrement: 1, plugins: [new confirmDatePlugin({})] });
  8060. xxdialogTag = { rangeStart: rangeStartTime, rangeEnd: rangeEndTime, start: startTime };
  8061. }
  8062. function showShareDeviceValidate() {
  8063. if (currentNode.agent.caps & 1) { QV('d2userConsentSelector', Q('d2shareType').value < 8); }
  8064. QV('d2httpPortSelector', Q('d2shareType').value == 8);
  8065. QV('d2httpsPortSelector', Q('d2shareType').value == 9);
  8066. QV('d2modenow', Q('d2timeRange').value == 0);
  8067. QV('d2moderange', Q('d2timeRange').value == 1);
  8068. QV('d2moderecurring', Q('d2timeRange').value >= 2);
  8069. var ok = true;
  8070. if (Q('d2shareType').value == 8) { var port = parseInt(Q('d2httpPort').value); if ((Q('d2httpPort').value != port) || (port < 1) || (port > 65535)) { ok = false; } }
  8071. if (Q('d2shareType').value == 9) { var port = parseInt(Q('d2httpsPort').value); if ((Q('d2httpsPort').value != port) || (port < 1) || (port > 65535)) { ok = false; } }
  8072. if (Q('d2inviteName').value.trim().length == 0) { ok = false; }
  8073. QE('idx_dlgOkButton', ok);
  8074. }
  8075. function showShareDeviceEx(b, tag) {
  8076. var consent = 0, p = parseInt(Q('d2shareType').value), viewOnly = false, q = 0;
  8077. if (p == 8) { meshserver.send({ action: 'createDeviceShareLink', nodeid: currentNode._id, guestname: Q('d2inviteName').value.trim(), p: 8, expire: parseInt(Q('d2inviteExpire').value), port: parseInt(Q('d2httpPort').value), consent: 0 }); return; }
  8078. if (p == 9) { meshserver.send({ action: 'createDeviceShareLink', nodeid: currentNode._id, guestname: Q('d2inviteName').value.trim(), p: 16, expire: parseInt(Q('d2inviteExpire').value), port: parseInt(Q('d2httpsPort').value), consent: 0 }); return; }
  8079. if (p == 3) { viewOnly = true; }
  8080. var q = [0, 1, 2, 2, 4, 6, 5, 7][p]; // Protocol flags: 1 = Terminal, 2 = Desktop, 4 = Files, 8 = HTTP, 16 = HTTPS.
  8081. if (q & 1) {
  8082. consent |= 0x0002; // Terminal notify
  8083. if ((currentNode.agent.caps & 1) && (Q('d2userConsent').value == 1)) { consent |= 0x0010; } // Terminal prompt for user consent
  8084. if ((currentNode.agent.caps & 1) && (Q('d2userConsent').value == 2)) { consent = 0; } // Terminal with no user consent
  8085. }
  8086. if (q & 2) {
  8087. consent |= 0x0041; // Desktop connection toolbar + Desktop notify
  8088. if ((currentNode.agent.caps & 1) && (Q('d2userConsent').value == 1)) { consent |= 0x0008; } // Desktop prompt for user consent
  8089. if ((currentNode.agent.caps & 1) && (Q('d2userConsent').value == 2)) { consent = 0; } // Desktop with no user consent
  8090. }
  8091. if (q & 4) {
  8092. consent |= 0x0004; // Files notify
  8093. if ((currentNode.agent.caps & 1) && (Q('d2userConsent').value == 1)) { consent |= 0x0020; } // Files prompt for user consent
  8094. if ((currentNode.agent.caps & 1) && (Q('d2userConsent').value == 2)) { consent = 0; } // Files prompt for user consent
  8095. }
  8096. if (Q('d2timeRange').value == 0) {
  8097. meshserver.send({ action: 'createDeviceShareLink', nodeid: currentNode._id, guestname: Q('d2inviteName').value.trim(), p: q, expire: parseInt(Q('d2inviteExpire').value), consent: consent, viewOnly: viewOnly });
  8098. } else if (Q('d2timeRange').value == 1) {
  8099. meshserver.send({ action: 'createDeviceShareLink', nodeid: currentNode._id, guestname: Q('d2inviteName').value.trim(), p: q, start: Math.floor(tag.rangeStart.selectedDates[0].getTime() / 1000), end: Math.floor(tag.rangeEnd.selectedDates[0].getTime() / 1000), consent: consent, viewOnly: viewOnly });
  8100. } else {
  8101. meshserver.send({ action: 'createDeviceShareLink', nodeid: currentNode._id, guestname: Q('d2inviteName').value.trim(), p: q, start: Math.floor(tag.start.selectedDates[0].getTime() / 1000), expire: parseInt(Q('d2inviteDuration').value), recurring: Q('d2timeRange').value - 1, consent: consent, viewOnly: viewOnly });
  8102. }
  8103. }
  8104. function deviceActionFunction() {
  8105. if (xxdialogMode) return;
  8106. var rights = GetNodeRights(currentNode), count = 0;
  8107. var x = "Select an operation to perform on this device." + '<br /><br />';
  8108. var y = '<select id=d2deviceop style=float:right;width:250px onchange=deviceActionFunctionValidate()>';
  8109. var z = '';
  8110. if ((currentNode.agent != null) && (currentNode.agent.id == 14)) {
  8111. if (((currentNode.conn & 1) != 0) && ((rights & 8) != 0)) {
  8112. count++;
  8113. y += '<option value=400>' + "Flash" + '</option>';
  8114. y += '<option value=401>' + "Vibrate" + '</option>';
  8115. z += '<div id=d2devicetimediv>' + addHtmlValue("Time", '<select id=d2devicetime style=float:right;width:250px><option value=1000>' + "1 second" + '</option><option value=5000>' + "5 seconds" + '</option><option value=10000>' + "10 seconds" + '</option></select>') + '</div>';
  8116. }
  8117. } else {
  8118. if ((rights & 64) != 0) { count++; y += '<option value=100>' + "Wake-up" + '</option>'; } // Wake-up permission
  8119. if (((currentNode.conn & 1) != 0) && ((rights & 131072) != 0)) { count++; y += '<option value=106>' + "Run Commands" + '</option>'; } // Remote command permission
  8120. 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>'; }
  8121. if ((currentNode.conn & 16) != 0) { count++; y += '<option value=103>' + "Send MQTT Message" + '</option>'; }
  8122. if ((currentNode.intelamt != null) && (currentNode.intelamt.state == 2) && ((currentNode.conn & 6) != 0) && ((rights & 262144) != 0)) {
  8123. count++;
  8124. y += '<option value=310>' + "Intel&reg; AMT Reset" + '</option>';
  8125. y += '<option value=308>' + "Intel&reg; AMT Power off" + '</option>';
  8126. if ((xxcurrentView == 11) || (xxcurrentView == 12)) { // Only show these options on terminal or desktop tabs
  8127. y += '<option value=312>' + "Intel&reg; AMT Reset to BIOS" + '</option>';
  8128. y += '<option value=311>' + "Intel&reg; AMT Power on to BIOS" + '</option>';
  8129. y += '<option value=315>' + "Intel&reg; AMT Power on to PXE" + '</option>';
  8130. y += '<option value=316>' + "Intel&reg; AMT Reset to PXE" + '</option>';
  8131. }
  8132. }
  8133. if ((currentNode.intelamt != null) && (currentNode.intelamt.state == 2) && ((currentNode.conn & 6) != 0) && ((rights & 64) != 0)) {
  8134. count++;
  8135. y += '<option value=302>' + "Intel&reg; AMT Power on" + '</option>';
  8136. }
  8137. if (((xxcurrentView == 11) || (xxcurrentView == 12)) && (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
  8138. if (((currentNode.conn & 1) != 0) && ((rights & 32768) != 0)) { count++; y += '<option value=104>' + "Uninstall Agent" + '</option>'; }
  8139. }
  8140. y += '</select>';
  8141. x += addHtmlValue("Operation", y);
  8142. if (count == 0) { x = "No actions currently available for this device."; }
  8143. setDialogMode(2, "Device Action", (count == 0)?2:3, deviceActionFunctionEx, x + z);
  8144. if (count > 0) { deviceActionFunctionValidate(); }
  8145. }
  8146. function deviceActionFunctionValidate() {
  8147. var op = Q('d2deviceop').value;
  8148. try { QV('d2devicetimediv', (op == 400) || (op == 401)); } catch (ex) {}
  8149. }
  8150. function deviceActionFunctionEx() {
  8151. var op = Q('d2deviceop').value;
  8152. if (op == 100) {
  8153. // Device wake
  8154. meshserver.send({ action: 'wakedevices', nodeids: [currentNode._id] });
  8155. } else if (op == 103) {
  8156. // Send MQTT Message
  8157. p10showSendMqttMsgDialog([currentNode._id]);
  8158. } else if (op == 104) {
  8159. // Uninstall agent
  8160. p10showSendUninstallAgentDialog([currentNode._id]);
  8161. } else if (op == 106) {
  8162. // Run commands
  8163. var wintype = false, linuxtype = false;
  8164. if (currentNode.agent) { if (isWindowsNode(currentNode)) { wintype = true; } else { linuxtype = true; } }
  8165. if ((wintype == true) || (linuxtype == true)) {
  8166. var x = "Run commands on selected devices." + '<br />';
  8167. if (wintype == true) {
  8168. x += '<select id=d2cmdtype style=width:100%;margin-bottom:4px;margin-top:4px>';
  8169. x += '<option value=1>' + "Windows Command Prompt" + '</option><option value=2>' + "Windows PowerShell" + '</option>';
  8170. if (linuxtype == true) { x += '<option value=3>' + "Linux/BSD/macOS Command Shell" + '</option>'; }
  8171. x += '</select>';
  8172. }
  8173. 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>';
  8174. x += '<textarea id=d2runcmd style=background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:scroll></textarea>';
  8175. setDialogMode(2, "Run Commands", 3, deviceRunCmdsFunctionEx, x);
  8176. Q('d2runcmd').focus();
  8177. //QE('idx_dlgOkButton', true);
  8178. }
  8179. } else if (op == 107) {
  8180. // Intel AMT One Click Recovery (OCR)
  8181. Q('d3localmodeform').action = 'oneclickrecovery.ashx';
  8182. Q('d3auth').value = authCookie;
  8183. Q('d3filter').value = '.efi';
  8184. Q('d3attrib').value = currentNode._id;
  8185. setDialogMode(3, "Intel&reg; AMT One Click Recovery", 3, deviceActionOneClickRecovery);
  8186. d3init();
  8187. } else if (op == 302) { // Intel AMT power on
  8188. 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?");
  8189. } else if (op == 308) { // Intel AMT power off
  8190. 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>");
  8191. } else if (op == 310) { // Intel AMT reset
  8192. setDialogMode(2, "Intel&reg; AMT Power Operation", 3, function() { meshserver.send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: parseInt(op) }); }, "Perform Intel&reg; AMT reset?");
  8193. } else if (op == 311) { // Intel AMT power on to BIOS
  8194. setDialogMode(2, "Intel&reg; AMT Power Operation", 3, function() { meshserver.send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: parseInt(op) + ((xxcurrentView == 12) ? 2 : 0) }); }, "Perform Intel&reg; AMT power on to BIOS?");
  8195. } else if (op == 312) { // Intel AMT reset to BIOS
  8196. setDialogMode(2, "Intel&reg; AMT Power Operation", 3, function() { meshserver.send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: parseInt(op) + ((xxcurrentView == 12) ? 2 : 0) }); }, "Perform Intel&reg; AMT reset to BIOS?");
  8197. } else if (op == 315) { // Intel AMT power to PXE
  8198. setDialogMode(2, "Intel&reg; AMT Power Operation", 3, function() { meshserver.send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: parseInt(op) + ((xxcurrentView == 12) ? 2 : 0) }); }, "Perform Intel&reg; AMT power on to PXE?");
  8199. } else if (op == 316) { // Intel AMT reset to PXE
  8200. setDialogMode(2, "Intel&reg; AMT Power Operation", 3, function() { meshserver.send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: parseInt(op) + ((xxcurrentView == 12) ? 2 : 0) }); }, "Perform Intel&reg; AMT reset to PXE?");
  8201. } else if ((op == 400) || (op == 401)) {
  8202. // Flash / vibrate
  8203. meshserver.send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: parseInt(op), time: parseInt(Q('d2devicetime').value) });
  8204. } else {
  8205. // Power operation
  8206. meshserver.send({ action: 'poweraction', nodeids: [ currentNode._id ], actiontype: parseInt(op) });
  8207. }
  8208. }
  8209. function deviceActionOneClickRecovery() {
  8210. var mode = Q('d3uploadMode').value;
  8211. if (mode == 1) {
  8212. // Boot using local disk image file
  8213. Q('d3submit').click();
  8214. } else {
  8215. // Boot using server image file
  8216. var files = d3getFileSel();
  8217. if (files.length == 1) { meshserver.send({ action: 'oneclickrecovery', nodeids: [ Q('d3attrib').value ], type: 'diskimage', path: d3filetreelocation.join('/') + '/' + files[0] }); }
  8218. }
  8219. }
  8220. function deviceRunCmdsFunctionEx() {
  8221. var type = 3;
  8222. try { type = parseInt(Q('d2cmdtype').value); } catch (ex) { }
  8223. meshserver.send({ action: 'runcommands', nodeids: [ currentNode._id ], type: type, cmds: Q('d2runcmd').value, runAsUser: parseInt(Q('d2cmduser').value) });
  8224. }
  8225. // Called when MeshCommander needs new credentials or updated credentials.
  8226. function updateAmtCredentials(forceDialog) {
  8227. var node = getNodeFromId(currentNode._id);
  8228. if ((forceDialog == true) || (node.intelamt.user == null) || (node.intelamt.user == '')) {
  8229. editDeviceAmtSettings(currentNode._id, updateAmtCredentialsEx);
  8230. } else {
  8231. Q('p14iframe').contentWindow.connectButtonfunctionEx();
  8232. }
  8233. }
  8234. function updateAmtCredentialsEx(button, tag) {
  8235. Q('p14iframe').contentWindow.connectButtonfunctionEx();
  8236. }
  8237. // Look to see if we need to update the device timeline
  8238. function updateDeviceTimeline() {
  8239. if ((meshserver.State != 2) || (powerTimelineNode == null) || (powerTimelineUpdate == null) || (currentNode == null) || (currentNode.mtype == 3)) return;
  8240. if ((powerTimelineNode == powerTimelineReq) && (currentNode._id == powerTimelineNode) && (powerTimelineUpdate < Date.now())) {
  8241. powerTimelineUpdate = null;
  8242. meshserver.send({ action: 'powertimeline', nodeid: currentNode._id });
  8243. meshserver.send({ action: 'lastconnect', nodeid: currentNode._id });
  8244. }
  8245. }
  8246. // Draw device power bars. The bars are 766px wide.
  8247. function drawDeviceTimeline() {
  8248. if ((currentNode == null) || (xxcurrentView < 10) || (xxcurrentView > 19) || (currentNode.mtype == 3) || (hidePowerTimeline === 'true')) return;
  8249. var timeline = null, now = Date.now();
  8250. if (currentNode._id == powerTimelineNode) { timeline = powerTimeline; }
  8251. // Calculate when the timeline starts
  8252. var d = new Date();
  8253. d.setHours(0, 0, 0, 0);
  8254. d = new Date(d.getTime() - (1000 * 60 * 60 * 24 * 6));
  8255. var timelineStart = d.getTime();
  8256. // De-compact the timeline
  8257. var timeline2 = [];
  8258. if (timeline != null && timeline.length > 1) {
  8259. timeline2.push([ 0, timeline[1], timeline[0] ]); // Start, End, Power
  8260. var ct = timeline[1];
  8261. for (var i = 2; i < timeline.length; i += 2) {
  8262. var power = timeline[i], dt = now;
  8263. if (timeline.length > (i + 1)) { dt = timeline[i + 1]; }
  8264. timeline2.push([ ct, ct + dt, power ]); // Start, End, Power
  8265. ct = ct + dt;
  8266. }
  8267. }
  8268. // Draw the timeline
  8269. var x = '', count = 1, date = new Date();
  8270. var totalWidth = Q('column_l').offsetWidth - (160 + 9 + 9 + 14); // Compute the total width of the power bar
  8271. date.setHours(0, 0, 0, 0);
  8272. for (var i = 0; i < 7; i++) {
  8273. var datavalue = '', start = date.getTime(), end = start + (1000 * 60 * 60 * 24);
  8274. for (var j in timeline2) {
  8275. var block = timeline2[j];
  8276. if (isTimeBlockInside(start, end, block[0], block[1]) == true) {
  8277. var ts = Math.max(start, block[0]);
  8278. var te = Math.min(Math.min(end, block[1]), now);
  8279. var width = Math.round(((te - ts) * totalWidth) / 86400000);
  8280. if (width > 0) {
  8281. var title = format("{0} from {1} to {2}.", powerStateStrings2[block[2]], printTime(new Date(ts)), printTime(new Date(te)));
  8282. datavalue += '<div class="pwState ' + powerColor(block[2]) + '" title="' + title + '" style="width:' + width + 'px;"></div>';
  8283. }
  8284. }
  8285. }
  8286. x += '<tr class=' + (((count % 2) == 0)?'altBack':'') + '><td><div>&nbsp;' + printDate(date) + '<div></div></div></td><td><div>' + datavalue + '</div></td></tr>';
  8287. ++count;
  8288. date = new Date(date.getFullYear(), date.getMonth(), date.getDate() - 1);
  8289. }
  8290. // Add the language and timezone of the browser to the server so the server can localize the time correctly.
  8291. var tz = '';
  8292. try { tz = '&tz=' + encodeURIComponentEx(Intl.DateTimeFormat().resolvedOptions().timeZone); } catch (ex) {}
  8293. QH('p10html2', '<table cellpadding=2 cellspacing=0><thead><tr style=><th scope=col style=text-align:center;width:150px>' + "Day" + '</th><th scope=col style=text-align:center><a onclick=downloadFile("devicepowerevents.ashx?id=' + currentNode._id + '&tf=' + new Date().getTimezoneOffset() + '&l=' + encodeURIComponentEx(getLang()) + tz + (urlargs.key?('&key=' + urlargs.key):'') + '",null,true)><img title="' + "Download power events" + '" src="images/link4.png" /></a>' + "7 Day Power State" + '</th></tr></thead><tbody>' + x + '</tbody></table>');
  8294. }
  8295. // Return a color for the given power state
  8296. function powerColor(x) { if (x < powerColorTable.length) { return powerColorTable[x]; } return 'pwsYellow'; }
  8297. // Return true if the time block is visible within the start/end period
  8298. function isTimeBlockInside(start, end, blockStart, blockEnd) {
  8299. if ((blockStart < start) && (blockEnd > end)) return true; // Block is wider than timespan
  8300. if ((blockStart > start) && (blockStart < end)) return true;
  8301. if ((blockEnd > start) && (blockEnd < end)) return true;
  8302. return false;
  8303. }
  8304. function addDeviceAttribute(name, value) { return '<tr><td class=style7>' + name + '</td><td class=style9>' + value + '</td></tr>'; }
  8305. function editDeviceAmtSettings(nodeid, func, arg) {
  8306. if (xxdialogMode) return;
  8307. var x = '', node = getNodeFromId(nodeid), buttons = 3, meshrights = GetNodeRights(node);
  8308. if ((meshrights & 4) == 0) return;
  8309. x += addHtmlValue("Username", '<input id=dp10username style=width:230px maxlength=32 autocomplete=nope placeholder="admin" onchange=validateDeviceAmtSettings() onkeyup=validateDeviceAmtSettings() />');
  8310. x += addHtmlValue("Password", '<input id=dp10password type=password style=width:230px autocomplete=nope maxlength=32 onchange=validateDeviceAmtSettings() onkeyup=validateDeviceAmtSettings() />');
  8311. // Only display the TLS setting if the Intel AMT manager is not running on the server. With the manager TLS is auto-detected.
  8312. if ((features2 & 1) == 0) { x += addHtmlValue("Security", '<select id=dp10tls style=width:236px><option value=0>' + "No TLS security" + '</option><option value=1>' + "TLS security required" + '</option></select>'); }
  8313. if ((node.intelamt.user != null) && (node.intelamt.user != '')) { buttons = 7; }
  8314. setDialogMode(2, "Edit Intel&reg; AMT credentials", buttons, editDeviceAmtSettingsEx, x, { node: node, func: func, arg: arg });
  8315. if ((node.intelamt.user != null) && (node.intelamt.user != '')) { Q('dp10username').value = node.intelamt.user; } else { Q('dp10username').value = 'admin'; }
  8316. if ((features2 & 1) == 0) { Q('dp10tls').value = node.intelamt.tls; }
  8317. validateDeviceAmtSettings();
  8318. }
  8319. function validateDeviceAmtSettings() {
  8320. QE('idx_dlgOkButton', passwordcheck(Q('dp10password').value));
  8321. }
  8322. function editDeviceAmtSettingsEx(button, tag) {
  8323. if (button == 2) {
  8324. // Delete button pressed, remove credentials
  8325. meshserver.send({ action: 'changedevice', nodeid: tag.node._id, intelamt: { user: '', pass: '' } });
  8326. } else {
  8327. // Change Intel AMT credentials
  8328. var amtuser = Q('dp10username').value;
  8329. if (amtuser == '') amtuser = 'admin';
  8330. var amtpass = Q('dp10password').value;
  8331. if (amtpass == '') amtuser = '';
  8332. var x = { action: 'changedevice', nodeid: tag.node._id, intelamt: { user: amtuser, pass: amtpass } };
  8333. if ((features2 & 1) == 0) { x.intelamt.tls = parseInt(Q('dp10tls').value); }
  8334. meshserver.send(x);
  8335. if (tag.func) { setTimeout(function () { tag.func(null, tag.arg); }, 1000); }
  8336. }
  8337. }
  8338. function p10showSendMqttMsgDialog(nodeids) {
  8339. if (xxdialogMode) return false;
  8340. var x = addHtmlValue("Topic", '<input id=dp2topic style=width:230px maxlength=64 onchange=p10validateSendMqttMsgDialog() onkeyup=p10validateSendMqttMsgDialog(event,1) />');
  8341. x += addHtmlValue("Message", '<div style=width:230px;margin:0;padding:0><textarea id=dp2msg maxlength=4096 style=width:100%;height:150px;resize:none onchange=p10validateSendMqttMsgDialog() onkeyup=p10validateSendMqttMsgDialog(event,1)></textarea></div>');
  8342. setDialogMode(2, "Send MQTT message", 3, p10showSendMqttMsgDialogEx, x, nodeids);
  8343. p10validateSendMqttMsgDialog();
  8344. Q('dp2topic').focus();
  8345. return false;
  8346. }
  8347. function p10validateSendMqttMsgDialog() {
  8348. QE('idx_dlgOkButton', (Q('dp2topic').value.length > 0) && (Q('dp2msg').value.length > 0));
  8349. }
  8350. function p10showSendMqttMsgDialogEx(b, nodeids) {
  8351. meshserver.send({ action: 'sendmqttmsg', nodeids: nodeids, topic: Q('dp2topic').value, msg: Q('dp2msg').value });
  8352. uncheckAllDevices();
  8353. }
  8354. function p10showSendUninstallAgentDialog(nodeids) {
  8355. if (xxdialogMode) return false;
  8356. var x = '';
  8357. if (nodeids.length > 1) { x = format("Are you sure you want to uninstall the selected {0} agents?", nodeids.length); } else { x = "Are you sure you want to uninstall selected agent?"; }
  8358. x += '<br /><br />';
  8359. if (nodeids.length > 1) { x += "This will not remove the devices from the server, but the devices will not longer be able to connect to the server. All remote access to the devices will be lost. The devices must be connected for this command to work."; } else { x += "This will not remove this device from the server, but the device will not longer be able to connect to the server. All remote access to the device will be lost. The device must be connect for this command to work."; }
  8360. x += '<br /><br /><label style=color:red><input id=p10check type=checkbox onchange=p10validateDeleteNodeDialog() />' + "Confirm" + '</label>';
  8361. setDialogMode(2, "Uninstall agent", 3, p10showSendUninstallAgentDialogEx, x, nodeids);
  8362. p10validateSendUninstallAgentDialog();
  8363. return false;
  8364. }
  8365. function p10validateSendUninstallAgentDialog() { QE('idx_dlgOkButton', Q('p10check').checked); }
  8366. function p10showSendUninstallAgentDialogEx(b, nodeids) { meshserver.send({ action: 'uninstallagent', nodeids: nodeids }); uncheckAllDevices(); }
  8367. function p10showChangeGroupDialog(nodeids) {
  8368. if (xxdialogMode) return false;
  8369. var targetMeshId = null;
  8370. if (nodeids.length == 1) { try { targetMeshId = meshes[getNodeFromId(nodeids[0]).meshid]._id; } catch (ex) { } }
  8371. // List all available alternative groups
  8372. var y = '<select id=p10newGroup style=width:236px>', count = 0, altGroups = [];
  8373. for (var i in meshes) { var meshrights = GetMeshRights(i); if ((meshes[i]._id != targetMeshId) && (meshrights & 4)) { altGroups.push(meshes[i]); } }
  8374. altGroups.sort(nameSort);
  8375. for (var i in altGroups) { count++; y += '<option value=\'' + altGroups[i]._id + '\'>' + altGroups[i].name + '</option>'; }
  8376. y += '</select>';
  8377. if (count > 0) {
  8378. var x = (nodeids.length == 1) ? ("Select a new group for this device" + '<br /><br />') : ("Select a new group for selected devices" + '<br /><br />');
  8379. x += addHtmlValue("New Device Group", y);
  8380. setDialogMode(2, "Change Group", 3, p10showChangeGroupDialogEx, x, nodeids);
  8381. } else {
  8382. setDialogMode(2, "Change Group", 1, null, "No other device group of same type exists.");
  8383. }
  8384. return false;
  8385. }
  8386. function p10showChangeGroupDialogEx(b, nodeids) {
  8387. meshserver.send({ action: 'changeDeviceMesh', nodeids: nodeids, meshid: Q('p10newGroup').value });
  8388. uncheckAllDevices();
  8389. }
  8390. function p10showDeleteNodeDialog(nodeid) {
  8391. if (xxdialogMode) return false;
  8392. var x = format("Are you sure you want to delete node {0}?", EscapeHtml(currentNode.name)) + '<br /><br /><label><input id=p10check type=checkbox onchange=p10validateDeleteNodeDialog() />' + "Confirm" + '</label>';
  8393. setDialogMode(2, "Delete Node", 3, p10showDeleteNodeDialogEx, x, nodeid);
  8394. p10validateDeleteNodeDialog();
  8395. return false;
  8396. }
  8397. function p10validateDeleteNodeDialog() {
  8398. QE('idx_dlgOkButton', Q('p10check').checked);
  8399. }
  8400. function p10showDeleteNodeDialogEx(buttons, nodeid) {
  8401. meshserver.send({ action: 'removedevices', nodeids: [ nodeid ] });
  8402. }
  8403. function p10WebRouter(nodeid, protocol, port, addr) {
  8404. var relayid = null;
  8405. var node = getNodeFromId(nodeid);
  8406. if (node.mtype == 3) { // Setup device relay if needed
  8407. var mesh = meshes[node.meshid];
  8408. if (mesh && mesh.relayid) { relayid = mesh.relayid; addr = node.host; }
  8409. }
  8410. var servername = serverinfo.name;
  8411. 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.
  8412. if (webRelayDns != '') { servername = webRelayDns; }
  8413. var url = 'https://' + servername + ':' + webRelayPort + '/control-redirect.ashx?n=' + nodeid + '&p=' + port + '&appid=' + protocol + '&c=' + authRelayCookie; // Protocol: 1 = HTTP, 2 = HTTPS
  8414. if (addr != null) { url += '&addr=' + addr; }
  8415. if (relayid != null) { url += '&relayid=' + relayid; }
  8416. safeNewWindow(url, 'WebRelay');
  8417. return false;
  8418. }
  8419. function p10MCRouter(nodeid, protocol, port, addr, localport) {
  8420. var node = getNodeFromId(nodeid);
  8421. var mesh = meshes[node.meshid];
  8422. if ((protocol == 3) && (port == null)) { if (node.rdpport != null) { port = node.rdpport; } else { port = 3389; } } // Adjust RDP port if needed
  8423. if (node.mtype == 3) { if (mesh && mesh.relayid) { nodeid = mesh.relayid; addr = node.host; } } // Setup device replay if needed
  8424. meshserver.send({ action: 'getcookie', nodeid: nodeid, tcpport: port, ip: addr, tag: 'MCRouter', protocol: protocol, localport: localport, name: mesh ? mesh.name : null }); // Protocol: 0 = Custom, 1 = HTTP, 2 = HTTPS, 3 = RDP, 4 = PuTTY, 5 = WinSCP, 6 = MCRDesktop, 7 = MCRFiles
  8425. return false;
  8426. }
  8427. function p10rfb(nodeid, port) {
  8428. var node = getNodeFromId(nodeid), addr = null;
  8429. var mesh = meshes[node.meshid];
  8430. if (port == null) { if (node.rfbport != null) { port = node.rfbport; } else { port = 5900; } }
  8431. if (node.mtype == 3) { if (mesh && mesh.relayid) { nodeid = mesh.relayid; addr = node.host; } } // Setup device relay if needed
  8432. meshserver.send({ action: 'getcookie', nodeid: nodeid, tcpport: port, tcpaddr: addr, tag: 'novnc', name: mesh ? mesh.name : null });
  8433. }
  8434. function p10mstsc(nodeid, port) {
  8435. var node = getNodeFromId(nodeid);
  8436. var mesh = meshes[node.meshid];
  8437. if (port == null) { if (node.rdpport != null) { port = node.rdpport; } else { port = 3389; } } // Adjust RDP port if needed
  8438. meshserver.send({ action: 'getcookie', nodeid: nodeid, tcpport: port, tag: 'mstsc', name: mesh ? mesh.name : null });
  8439. }
  8440. function p10ssh(nodeid, port) {
  8441. var node = getNodeFromId(nodeid);
  8442. var mesh = meshes[node.meshid];
  8443. if (port == null) { if (node.sshport != null) { port = node.sshport; } else { port = 22; } }
  8444. meshserver.send({ action: 'getcookie', nodeid: nodeid, tcpport: port, tag: 'ssh', name: mesh ? mesh.name : null });
  8445. }
  8446. // Show current location
  8447. var d2map = null;
  8448. function p10showNodeLocationDialog() {
  8449. if ((xxdialogMode != null) && (xxdialogTag == '@xxmap')) { setDialogMode(0); } else { if (xxdialogMode) return false; }
  8450. var markers = [], types = ['iploc', 'wifiloc', 'gpsloc', 'userloc'], boundingBox = null;
  8451. for (var loctype in types) {
  8452. if (currentNode[types[loctype]] != null) {
  8453. var loc = currentNode[types[loctype]].split(','), lat = parseFloat(loc[0]), lon = parseFloat(loc[1]);
  8454. if ((lat < 90) && (lat > -90) && (lon < 180) && (lon > -180)) { // Check valid lat/lon
  8455. var deviceMark = new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat([lon, lat])) });
  8456. deviceMark.setStyle(markerStyle(currentNode, parseInt(loctype) + 1));
  8457. markers.push(deviceMark);
  8458. if (boundingBox == null) { boundingBox = [ lat, lon, lat, lon, 0 ]; } else { if (lat < boundingBox[0]) { boundingBox[0] = lat; } if (lon < boundingBox[1]) { boundingBox[1] = lon; } if (lat > boundingBox[2]) { boundingBox[2] = lat; } if (lon > boundingBox[3]) { boundingBox[3] = lon; } }
  8459. }
  8460. }
  8461. }
  8462. // Setup the device mark layer
  8463. var vectorSource = new ol.source.Vector({ features: markers });
  8464. var vectorLayer = new ol.layer.Vector({ source: vectorSource });
  8465. //var x = '<div><a href="https://www.google.com/maps/preview/@' + lat + ',' + lng + ',12z" rel="noreferrer noopener" target=_blank>Open in Google maps</a></div>';
  8466. var x = '<div id=d2map style=width:100%;height:300px></div>';
  8467. setDialogMode(2, "Device Location", 1, null, x, '@xxmap');
  8468. var clng = 0, clat = 0, zoom = 8;
  8469. if (boundingBox != null) {
  8470. var clat = (boundingBox[0] + boundingBox[2]) / 2;
  8471. var clng = (boundingBox[1] + boundingBox[3]) / 2;
  8472. var cscale = Math.max(Math.abs(boundingBox[0] - boundingBox[2]), Math.abs(boundingBox[1] - boundingBox[3]));
  8473. var i = 360, zoom = -2;
  8474. while (i > cscale) { zoom++; i = i / 2; }
  8475. }
  8476. if (markers.length == 1) { zoom = 8; }
  8477. // Setup the map
  8478. d2map = new ol.Map({
  8479. target: 'd2map',
  8480. interactions: ol.interaction.defaults({dragPan:false, mouseWheelZoom:false}),
  8481. layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }), vectorLayer ],
  8482. view: new ol.View({ center: ol.proj.fromLonLat([clng, clat]), zoom: zoom })
  8483. });
  8484. return false;
  8485. }
  8486. // Show network interfaces
  8487. function p10showNodeNetInfoDialog() {
  8488. if (xxdialogMode) return false;
  8489. setDialogMode(2, "Network Interfaces", 1, null, '<div id=d2netinfo>' + "Loading..." + '</div>', 'if' + currentNode._id );
  8490. meshserver.send({ action: 'getnetworkinfo', nodeid: currentNode._id });
  8491. return false;
  8492. }
  8493. // Show MeshCentral Router dialog
  8494. function p10showMeshRouterDialog() {
  8495. if (xxdialogMode) return;
  8496. var x = '<div>' + "MeshCentral Router is a Windows tool for TCP port mapping. You can, for example, RDP into a remote device thru this server." + '</div><br />';
  8497. x += addHtmlValue("Win32 Executable", '<a style=cursor:pointer onclick=downloadFile("meshagents?meshaction=winrouter' + (urlargs.key?('&key=' + urlargs.key):'') + '",null,true)>MeshCentralRouter.exe</a>');
  8498. x += addHtmlValue("MacOS Installer", '<a style=cursor:pointer onclick=downloadFile("meshagents?meshaction=macrouter' + (urlargs.key?('&key=' + urlargs.key):'') + '",null,true)>MeshCentralRouter.dmg</a>');
  8499. var servername = serverinfo.name;
  8500. 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.
  8501. var domainUrlNoSlash = domainUrl.substring(0, domainUrl.length - 1);
  8502. var portStr = (serverinfo.port == 443) ? '' : (':' + serverinfo.port);
  8503. var url = 'mcrouter://' + servername + portStr + domainUrl + 'control.ashx?c=' + authCookie + '&t=' + serverinfo.tlshash + '&l={{{lang}}}' + (urlargs.key?('&key=' + urlargs.key):'');
  8504. //x += addHtmlValue("Launch", '<a style=cursor:pointer target="mcrouterframe" rel="noreferrer noopener" download href="' + url + '">Start MeshCentral Router</a>');
  8505. //x += '<br /><div style=width:100%><a style=cursor:pointer target="mcrouterframe" rel="noreferrer noopener" download href="' + url + '">' + "Start MeshCentral Router" + '</a>' + ", for this link to work you must download MeshCentral Router run it and click the install button." + '</div>';
  8506. x += '<br /><div>' + "Run MeshCentral Router and click \"install\" to make it launchable from the browser." + '</div>';
  8507. x += '<br /><a style=cursor:pointer target="mcrouterframe" rel="noreferrer noopener" href="' + url + '"><input type=button style=width:100%;cursor:pointer value="' + "Launch MeshCentral Router" + '" /></a>';
  8508. x += '<iframe style=display:none name="mcrouterframe"></iframe>';
  8509. setDialogMode(2, "MeshCentral Router", 1, null, x, 'fileDownload');
  8510. }
  8511. // Request MQTT login credentials
  8512. function p10showMqttLoginDialog(nodeid) { meshserver.send({ action: 'getmqttlogin', nodeid: nodeid }); }
  8513. // Open XTerm
  8514. function p10openxterm(e, nodeid) {
  8515. haltEvent(e);
  8516. var url = '/xterm?nodeid=' + encodeURIComponentEx(nodeid) + '&auto=1';
  8517. var node = getNodeFromId(nodeid);
  8518. if (node == null) return;
  8519. if ([1, 2, 3, 4, 21, 22].indexOf(node.agent.id) >= 0) { url += '&os=win'; } else { url += '&os=linux'; }
  8520. if (urlargs.key) { url += '&key=' + urlargs.key; }
  8521. safeNewWindow(url, 'xterm:' + nodeid);
  8522. return false;
  8523. }
  8524. // Show MeshCmd dialog
  8525. function p10showMeshCmdDialog(mode, nodeid) {
  8526. if (xxdialogMode) return;
  8527. var y = '<select id=aginsSelect onchange=meshCmdOsClick() style=width:236px>';
  8528. y += '<option value=4>' + "Windows x86 (64bit)" + '</option>';
  8529. y += '<option value=3>' + "Windows x86 (32bit)" + '</option>';
  8530. y += '<option value=43>' + "Windows ARM (64bit)" + '</option>';
  8531. y += '<option value=6>' + "Linux x86 (64bit)" + '</option>';
  8532. y += '<option value=5>' + "Linux x86 (32bit)" + '</option>';
  8533. y += '<option value=16>' + "macOS x86 (64bit)" + '</option>';
  8534. y += '<option value=29>' + "macOS ARM (64bit)" + '</option>';
  8535. y += '<option value=25>' + "Linux ARM, Raspberry Pi (32bit)" + '</option>';
  8536. y += '<option value=26>' + "Linux ARM, Raspberry Pi (64bit)" + '</option>';
  8537. y += '</select>';
  8538. var x = '';
  8539. if (mode == 0) { x += '<div>' + "MeshCmd is a command line tool that performs lots of different operations. The action file can optionally be downloaded and edited to provide server information and credentials." + '<br /><br />'; }
  8540. if (mode == 1) { x += '<div>' + "Download \"meshcmd\" with an action file to route traffic thru this server to this device. Make sure to edit meshaction.txt and add your account password or make any changes needed." + '<br /><br />'; }
  8541. x += addHtmlValue("Operating System", y);
  8542. x += addHtmlValue("MeshCmd", '<a id=meshcmddownloadid onclick=downloadFile("meshagents?meshcmd=3' + (urlargs.key?('&key=' + urlargs.key):'') + '")></a>');
  8543. if (mode == 0) { x += addHtmlValue("Action File", '<a onclick=downloadFile("meshagents?meshaction=generic' + (urlargs.key?('&key=' + urlargs.key):'') + '")>' + "MeshAction (.txt)" + '</a>'); }
  8544. if (mode == 1) { x += addHtmlValue("Action File", '<a onclick=downloadFile("meshagents?meshaction=route&nodeid=' + nodeid + (urlargs.key?('&key=' + urlargs.key):'') + '")>' + "MeshAction (.txt)" + '</a>'); }
  8545. x += '</div>';
  8546. setDialogMode(2, "Download MeshCmd", 9, null, x, 'fileDownload');
  8547. meshCmdOsClick();
  8548. }
  8549. function meshCmdOsClick() {
  8550. var os = Q('aginsSelect').value, osn = '', osurl = '';
  8551. //Q('meshcmddownloadid').href = 'meshagents?meshcmd=' + os + (urlargs.key?('&key=' + urlargs.key):'');
  8552. if (os == 3) { osn = "MeshCmd (Win x86-32 executable)"; }
  8553. if (os == 4) { osn = "MeshCmd (Win x86-64 executable)"; }
  8554. if (os == 43) { osn = "MeshCmd (Win ARM-64 executable)"; }
  8555. if (os == 5) { osn = "MeshCmd (Linux x86, 32bit)"; }
  8556. if (os == 6) { osn = "MeshCmd (Linux x86, 64bit)"; }
  8557. if (os == 16) { osn = "MeshCmd (macOS, x86-64bit)"; }
  8558. if (os == 29) { osn = "MeshCmd (macOS, ARM-64bit)"; }
  8559. if (os == 25) { osn = "MeshCmd (Linux ARM, 32bit)"; }
  8560. if (os == 26) { osn = "MeshCmd (Linux ARM, 64bit)"; }
  8561. QH('meshcmddownloadid', osn);
  8562. Q('meshcmddownloadid').onclick = function() { downloadFile('meshagents?meshcmd=' + os + (urlargs.key?('&key=' + urlargs.key):'')); }
  8563. }
  8564. function p10showiconselector() {
  8565. if (xxdialogMode) return;
  8566. if ((GetNodeRights(currentNode) & 4) == 0) return;
  8567. var x = '<div style=text-align:center><br />';
  8568. x += '<div tabindex=0 style="display:inline-block;margin:0 10px 0 10px" class=i1 onclick=p10setIcon(1) onkeypress="if (event.key==\'Enter\') p10setIcon(1)"></div>';
  8569. x += '<div tabindex=0 style="display:inline-block;margin:0 10px 0 10px" class=i2 onclick=p10setIcon(2) onkeypress="if (event.key==\'Enter\') p10setIcon(2)"></div>';
  8570. x += '<div tabindex=0 style="display:inline-block;margin:0 10px 0 10px" class=i3 onclick=p10setIcon(3) onkeypress="if (event.key==\'Enter\') p10setIcon(3)"></div>';
  8571. x += '<div tabindex=0 style="display:inline-block;margin:0 10px 0 10px" class=i4 onclick=p10setIcon(4) onkeypress="if (event.key==\'Enter\') p10setIcon(4)"></div>';
  8572. x += '<br />';
  8573. x += '<div tabindex=0 style="display:inline-block;margin:0 10px 0 10px" class=i5 onclick=p10setIcon(5) onkeypress="if (event.key==\'Enter\') p10setIcon(5)"></div>';
  8574. x += '<div tabindex=0 style="display:inline-block;margin:0 10px 0 10px" class=i6 onclick=p10setIcon(6) onkeypress="if (event.key==\'Enter\') p10setIcon(6)"></div>';
  8575. x += '<div tabindex=0 style="display:inline-block;margin:0 10px 0 10px" class=i7 onclick=p10setIcon(7) onkeypress="if (event.key==\'Enter\') p10setIcon(7)"></div>';
  8576. x += '<div tabindex=0 style="display:inline-block;margin:0 10px 0 10px" class=i8 onclick=p10setIcon(8) onkeypress="if (event.key==\'Enter\') p10setIcon(8)"></div>';
  8577. x += '<br /><br /></div>';
  8578. setDialogMode(2, "Icon Selection", 0, null, x);
  8579. QV('id_dialogclose', true);
  8580. }
  8581. function p10setIcon(icon) {
  8582. setDialogMode(0);
  8583. meshserver.send({ action: 'changedevice', nodeid: currentNode._id, icon: icon });
  8584. }
  8585. function showClearSshDialog() { setDialogMode(2, "Edit Device", 3, showClearSshDialogEx, "Clear SSH credentials?"); }
  8586. function showClearSshDialogEx(button, mode) { meshserver.send({ action: 'changedevice', nodeid: currentNode._id, ssh: 0 }); }
  8587. function showClearRdpDialog() { setDialogMode(2, "Edit Device", 3, showClearRdpDialogEx, "Clear RDP credentials?"); }
  8588. function showClearRdpDialogEx(button, mode) { meshserver.send({ action: 'changedevice', nodeid: currentNode._id, rdp: 0 }); }
  8589. var showEditNodeValueDialog_modes = ["Device Name", "Hostname", "Description", "Tags"];
  8590. var showEditNodeValueDialog_modes2 = ['name', 'host', 'desc', 'tags'];
  8591. var showEditNodeValueDialog_modes3 = ['', '', '', "Tag1, Tag2, Tag3"];
  8592. var showEditNodeValueDialog_modes4 = [64, 64, 64, 4096];
  8593. function showEditNodeValueDialog(mode) {
  8594. if (xxdialogMode) return;
  8595. var x = addHtmlValue(showEditNodeValueDialog_modes[mode], '<input id=dp10devicevalue maxlength=' + showEditNodeValueDialog_modes4[mode] + ' placeholder="' + showEditNodeValueDialog_modes3[mode] + '" onchange=p10editdevicevalueValidate(' + mode + ',event) onkeyup=p10editdevicevalueValidate(' + mode + ',event) />');
  8596. if (mode == 3) {
  8597. // Get a list of all possible device tags
  8598. var allTags = [], y = '';
  8599. 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]); } } } }
  8600. if (allTags.length > 0) {
  8601. allTags.sort();
  8602. 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> '; }
  8603. x += '<div style=margin-top:8px;width:370px;line-height:26px;max-height:160px;overflow-y:auto>' + y + '</div>';
  8604. }
  8605. }
  8606. setDialogMode(2, "Edit Device", 3, showEditNodeValueDialogEx, x, mode);
  8607. var v = currentNode[showEditNodeValueDialog_modes2[mode]];
  8608. if (v == null) v = '';
  8609. if (Array.isArray(v)) { v = v.join(', '); }
  8610. Q('dp10devicevalue').value = v;
  8611. p10editdevicevalueValidate();
  8612. Q('dp10devicevalue').focus();
  8613. }
  8614. function showEditNodeValueDialogAddTag(t) {
  8615. var tt = Q('dp10devicevalue').value.split(','), t2 = [];
  8616. for (var i in tt) { t2.push(tt[i].trim()); }
  8617. if (t2.indexOf(t) >= 0) return;
  8618. Q('dp10devicevalue').value += ((Q('dp10devicevalue').value.length == 0)?'':', ') + decodeURIComponent(t);
  8619. setTimeout(function(){ Q('dp10devicevalue').selectionStart = Q('dp10devicevalue').selectionEnd = 90000; }, 0);
  8620. p10editdevicevalueValidate();
  8621. }
  8622. function showEditNodeValueDialogEx(button, mode) {
  8623. var x = { action: 'changedevice', nodeid: currentNode._id };
  8624. x[showEditNodeValueDialog_modes2[mode]] = Q('dp10devicevalue').value;
  8625. meshserver.send(x);
  8626. }
  8627. function p10editdevicevalueValidate(mode, e) {
  8628. var x = ((mode > 1) || (Q('dp10devicevalue').value.length > 0));
  8629. QE('idx_dlgOkButton', x);
  8630. if ((e != null) && (x == true) && (e.keyCode == 13)) { dialogclose(1); }
  8631. }
  8632. //
  8633. // DESKTOP
  8634. //
  8635. var desktopNode;
  8636. function setupDesktop() {
  8637. // Setup the remote desktop
  8638. if ((desktopNode != currentNode) && (desktop != null)) {
  8639. if (desktopNode._id != currentNode._id) {
  8640. desktop.Stop(); desktopNode = null; desktop = null;
  8641. }
  8642. }
  8643. // If the device desktop is already connected in multi-desktop, use that.
  8644. if ((desktopNode != currentNode) || (desktop == null)) {
  8645. var xdesk = multiDesktop[currentNode._id];
  8646. if (xdesk != null) {
  8647. // This device already has a canvas, use it.
  8648. QH('DeskParent', '');
  8649. var c = xdesk.m.CanvasId;
  8650. c.setAttribute('id', 'Desk');
  8651. c.setAttribute('onmousedown', 'dmousedown(event)');
  8652. c.setAttribute('onmouseup', 'dmouseup(event)');
  8653. c.setAttribute('onmousemove', 'dmousemove(event)');
  8654. c.removeAttribute('onclick');
  8655. Q('DeskParent').appendChild(c);
  8656. desktop = xdesk;
  8657. if (desktop.m.SendCompressionLevel) { desktop.m.SendCompressionLevel(desktopsettings.agentencoding, desktopsettings.quality, desktopsettings.scaling, desktopsettings.framerate); }
  8658. desktop.onStateChanged = onDesktopStateChange;
  8659. desktop.onMetadataChange = function(metadata) { updateMetadata(desktop, 'deskmetadata'); }
  8660. if ((features2 & 0x2000) != 0) desktop.m.stopInput = true;
  8661. if (desktop && desktop.m.mouseCursorActive) { desktop.m.mouseCursorActive(true); }
  8662. QV('DeskInputLockedButton', desktop.m.RemoteInputLock == true);
  8663. QV('DeskInputUnLockedButton', desktop.m.RemoteInputLock == false);
  8664. desktop.m.onRemoteInputLockChanged = function(obj, state) { QV('DeskInputLockedButton', state); QV('DeskInputUnLockedButton', !state); }
  8665. desktop.m.onKeyboardStateChanged = function(obj, state) {
  8666. QS('p11numlock').display = ((state & 1) ? 'inline-block' : 'none');
  8667. QS('p11scrolllock').display = ((state & 2) ? 'inline-block' : 'none');
  8668. QS('p11capslock').display = ((state & 4) ? 'inline-block' : 'none');
  8669. }
  8670. desktopNode = currentNode;
  8671. onDesktopStateChange(desktop, desktop.State);
  8672. delete multiDesktop[currentNode._id];
  8673. } else {
  8674. // Device is not already connected, just setup a blank canvas
  8675. if(desktop == null) { QH('DeskParent', '<canvas id=Desk oncontextmenu="return false" onmousedown=dmousedown(event) onmouseup=dmouseup(event) onmousemove=dmousemove(event)></canvas>'); }
  8676. desktopNode = currentNode;
  8677. }
  8678. // Setup the mouse wheel
  8679. Q('Desk').addEventListener('DOMMouseScroll', function (e) { return dmousewheel(e); });
  8680. Q('Desk').addEventListener('mousewheel', function (e) { return dmousewheel(e); });
  8681. }
  8682. desktopNode = currentNode;
  8683. if(desktop){ desktop.m.onDisplayinfo = deskDisplayInfo; deskDisplayInfo(desktop.m, desktop.m.displays, desktop.m.selectedDisplay); }
  8684. updateDesktopButtons();
  8685. deskAdjust();
  8686. updateMetadata(desktop, 'deskmetadata');
  8687. }
  8688. // Show and enable the right buttons
  8689. function updateDesktopButtons() {
  8690. var deskState = 0;
  8691. if (desktop != null) { deskState = desktop.State; }
  8692. var rights = GetNodeRights(currentNode);
  8693. var mtype = (currentNode.agent == 1) ? 1 : 2;
  8694. // Show the right buttons
  8695. QV('disconnectbutton1span', (deskState != 0));
  8696. QV('connectbutton1span', (deskState == 0) && ((rights & 8) || (rights & 256)) && (currentNode.agent != null) && (currentNode.agent.caps & 1));
  8697. QV('connectbutton1rspan', ((features & 0x40000000) == 0) && (deskState == 0) && (rights & 8) && (currentNode.agent != null) && ((currentNode.agent.id == 3) || (currentNode.agent.id == 4)));
  8698. if (mtype == 1) {
  8699. QV('connectbutton1hspan',
  8700. (deskState == 0) &&
  8701. (rights & 8) &&
  8702. (
  8703. ((currentNode.intelamt != null) &&
  8704. (currentNode.intelamt.state == 2))
  8705. )
  8706. );
  8707. } else {
  8708. QV('connectbutton1hspan',
  8709. (deskState == 0) &&
  8710. (rights & 8) &&
  8711. (
  8712. ((currentNode.intelamt != null) &&
  8713. (currentNode.intelamt.state == 2) &&
  8714. (currentNode.intelamt.ver != null) &&
  8715. ((currentNode.intelamt.sku == null) ||
  8716. ((typeof currentNode.intelamt.sku == 'number') &&
  8717. ((currentNode.intelamt.sku & 8) != 0))))
  8718. )
  8719. );
  8720. }
  8721. // Show the right settings
  8722. QV('td7amtkvm', ((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)));
  8723. QV('td7meshkvm', (webRtcDesktop) || ((currentNode.agent != null) && (currentNode.agent.caps & 1) && ((deskState == 0) || (desktop.contype == 1))));
  8724. QV('td7rdpkvm', ((currentNode.agent != null) && ((currentNode.agent.id == 3) || (currentNode.agent.id == 4)) && ((deskState == 0) || (desktop.contype == 4))));
  8725. // Enable buttons
  8726. var inputAllowed = ((features2 & 0x2000) == 0) && ((currentNode.agent == null) || (currentNode.agent.id != 14)) && ((rights == 0xFFFFFFFF) || (((rights & 8) != 0) && ((rights & 256) == 0) && ((rights & 4096) == 0)));
  8727. var online = ((currentNode.conn & 1) != 0); // If Agent (1) connected, enable remote desktop
  8728. QE('connectbutton1', online);
  8729. QE('connectbutton1r', online || (currentNode.mtype == 3));
  8730. if (currentNode.rdpport && currentNode.rdpport != 3389) {
  8731. QH('desktopCustomUpperRight', '<a style="cursor:pointer;line-height:22px" onclick="cmaltportaction(1,event)">' + format("RDP Port {0}", (currentNode.rdpport?currentNode.rdpport:3389)) + '</a>');
  8732. } else {
  8733. QH('desktopCustomUpperRight', '');
  8734. }
  8735. var hwonline = ((currentNode.conn & 6) != 0); // If CIRA (2) or AMT (4) connected, enable hardware terminal
  8736. QE('connectbutton1h', hwonline);
  8737. QV('deskFocusBtn', (desktop != null) && (desktop.contype == 2) && (deskState != 0) && (desktopsettings.showfocus));
  8738. QE('DeskClip', deskState == 3);
  8739. QV('DeskClip', (inputAllowed) && (currentNode.agent) && ((features2 & 0x1800) != 0x1800) && (currentNode.agent.id != 11) && (currentNode.agent.id != 16) && ((desktop == null) || (desktop.contype != 2)) && ((desktopsettings.autoclipboard != true) || (navigator.clipboard == null) || (navigator.clipboard.readText == null))); // Clipboard not supported on macOS
  8740. QE('DeskESC', (deskState == 3) && (desktop.contype != 4));
  8741. QV('DeskESC', browserfullscreen && inputAllowed);
  8742. QE('DeskType', deskState == 3);
  8743. QV('DeskType', inputAllowed);
  8744. QE('DeskWD', deskState == 3);
  8745. QV('DeskWD', inputAllowed);
  8746. QV('deskkeys', inputAllowed);
  8747. QE('deskkeys', desktop != null);
  8748. QV('DeskTimer', deskState == 3);
  8749. QV('DeskLatency', deskState == 3);
  8750. QS('DeskLatency').display = (deskState == 3 ? 'inline-block' : 'none');
  8751. // Enable browser clipboard read if supported
  8752. var autoclipboard = ((desktop) && (desktop.contype == 4)) ? desktopsettings.rdpautoclipboard : desktopsettings.autoclipboard;
  8753. QV('DeskClipboardOutButton', online && inputAllowed && (desktop != null) && ((features2 & 0x1000) == 0) && (navigator.clipboard != null) && (navigator.clipboard.readText != null) && ((autoclipboard != true) || (navigator.clipboard == null) || (navigator.clipboard.readText == null)));
  8754. QV('d7deskAutoClipboardLabel', (navigator.clipboard.readText != null) && ((features2 & 0x1000) == 0));
  8755. QV('DeskClipboardInButton', online && inputAllowed && (desktop != null) && ((features2 & 0x0800) == 0) && (navigator.clipboard != null) && (navigator.clipboard.writeText != null) && ((autoclipboard != true) || (navigator.clipboard == null) || (navigator.clipboard.readText == null)));
  8756. if (deskState != 3) {
  8757. QV('DeskInputLockedButton', false);
  8758. QV('DeskInputUnLockedButton', false);
  8759. QV('p11numlock', false);
  8760. QV('p11scrolllock', false);
  8761. QV('p11capslock', false);
  8762. }
  8763. // Display this only if we have Chat & Notify permissions
  8764. QV('DeskRunButton', ((rights & 131072) != 0) && online);
  8765. QV('DeskSaveImageButton', (deskState == 3) && (Q('Desk')['toBlob'] != null) && ((features2 & 0x400) == 0));
  8766. QV('DeskRecordButton', (deskState == 3) && (Q('Desk')['toBlob'] != null) && (desktop.m.StartRecording != null) && ((features2 & 0x400) == 0));
  8767. QV('DeskChatButton', ((rights & 16384) != 0) && (browserfullscreen == false) && (currentNode.agent) && online);
  8768. QV('DeskNotifyButton', ((rights & 16384) != 0) && (browserfullscreen == false) && (currentNode.agent) && online);
  8769. QV('DeskLockButton', ((rights & 16384) != 0) && (browserfullscreen == false) && (currentNode.agent) && (currentNode.agent.id < 5) && (inputAllowed) && (deskState == 3));
  8770. QV('DeskToolsButton', (currentNode.agent) && online);
  8771. QE('DeskToolsButton', inputAllowed);
  8772. QV('DeskOpenWebButton', (browserfullscreen == false) && (inputAllowed) && (currentNode.agent) && online);
  8773. QV('DeskBackgroundButton', inputAllowed && (deskState == 3) && (desktop.contype == 1) && (currentNode.agent) && (currentNode.agent.id != 11) && (currentNode.agent.id != 16) && online);
  8774. QV('DeskControlSpan', inputAllowed)
  8775. QV('DeskRefreshButton', (deskState == 3) && (desktop.contype == 1))
  8776. if (rights & 8) { Q('DeskControl').checked = (getstore('DeskControl', 1) == 1); } else { Q('DeskControl').checked = false; }
  8777. QS('DeskControlSpan').color = Q('DeskControl').checked?null:'red';
  8778. if (online == false) QV('DeskTools', false);
  8779. }
  8780. // Debug
  8781. var autoConnectDesktopTimer = null;
  8782. function autoConnectDesktop(e) { if (autoConnectDesktopTimer == null) { autoConnectDesktopTimer = setInterval(function() { connectDesktop(null, 1) }, 1000); } else { clearInterval(autoConnectDesktopTimer); autoConnectDesktopTimer = null; } }
  8783. // Used to translate incoming agent console messages
  8784. var agentConsoleMessages = [ '', "Waiting for user to grant access...", "Denied", "Failed to start remote terminal session, {0} ({1})", "Timeout", "Received invalid network data", "Unable to capture display", "Protocol negotiation failed ({0})", "NLA not supported", "SSL required by server", "SSL not allowed by server", "SSL certificate not on server", "Inconsistent flags", "Hybrid required by server", "SSL with user auth required by server" ];
  8785. function formatAgentConsoleMessage(msg, msgid, msgargs) {
  8786. var r;
  8787. if (msgargs == null) { msgargs = []; }
  8788. 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 ...
  8789. if (msgid && (msgid < agentConsoleMessages.length)) { r = EscapeHtml(format(agentConsoleMessages[msgid], (msgargs[0]), (msgargs[1]), (msgargs[2]))); } else { r = EscapeHtml(msg); }
  8790. return r.split('\n').join('<br />') + '<br /><br />';
  8791. }
  8792. function connectDesktop(e, contype, tsid, consent) {
  8793. if (xxdialogMode) return;
  8794. if ((e != null) && (e.shiftKey != false) && (contype == 3)) { contype = 1; } // If the shift key is not pressed, don't try to ask for session list.
  8795. QV('p11DeskSessionSelector', false);
  8796. p11clearConsoleMsg();
  8797. if (desktop == null) {
  8798. desktopNode = currentNode;
  8799. if (contype == 2) {
  8800. // Setup the Intel AMT remote desktop
  8801. if ((desktopNode.intelamt.user == null) || (desktopNode.intelamt.user == '')) { editDeviceAmtSettings(desktopNode._id, connectDesktop, 2); return; }
  8802. desktop = CreateAmtRedirect(CreateAmtRemoteDesktop('Desk'), authCookie);
  8803. desktop.debugmode = debugmode;
  8804. desktop.onStateChanged = onDesktopStateChange;
  8805. desktop.m.bpp = (desktopsettings.encoding == 1 || desktopsettings.encoding == 3) ? 1 : 2;
  8806. desktop.m.useZRLE = (desktopsettings.encoding < 3);
  8807. desktop.m.localKeyMap = desktopsettings.localkeymap;
  8808. desktop.m.ReverseMouseWheel = desktopsettings.kvmrmw;
  8809. desktop.m.showmouse = desktopsettings.showmouse;
  8810. desktop.m.onScreenSizeChange = deskAdjust;
  8811. desktop.m.onKvmData = function (x) {
  8812. //console.log('onKvmData (' + x.length + '): ' + x);
  8813. // Send the presense probe only once if needed.
  8814. if (x.length == 0) { if (!desktop.m._sentPresence) { desktop.m._sentPresence = true; desktop.m.sendKvmData(JSON.stringify({ action: 'present', ver: 1 })); } return; }
  8815. var data = null;
  8816. try { data = JSON.parse(x); } catch (e) { }
  8817. if ((data != null) && (data.action != null)) {
  8818. if (data.action == 'restart') {
  8819. // Clear WebRTC channel
  8820. webRtcDesktopReset();
  8821. desktop.m.sendKvmData(JSON.stringify({ action: 'present', ver: 1 }));
  8822. } else if ((data.action == 'present') && (webRtcDesktop == null)) {
  8823. // Setup WebRTC channel
  8824. webRtcDesktop = { platform: data.platform };
  8825. var configuration = null; //{ "iceServers": [ { 'urls': 'stun:stun.cloudflare.com:3478' }, { 'urls': 'stun:stun.l.google.com:19302' } ] };
  8826. if (typeof RTCPeerConnection !== 'undefined') { webRtcDesktop.webrtc = new RTCPeerConnection(configuration); }
  8827. else if (typeof webkitRTCPeerConnection !== 'undefined') { webRtcDesktop.webrtc = new webkitRTCPeerConnection(configuration); }
  8828. webRtcDesktop.webchannel = webRtcDesktop.webrtc.createDataChannel("DataChannel", {}); // { ordered: false, maxRetransmits: 2 }
  8829. webRtcDesktop.webchannel.onopen = function () {
  8830. // Switch to software KVM
  8831. //if (urlvars && urlvars['kvmdatatrace']) { console.log('WebRTC Data Channel Open'); }
  8832. console.log('WebRTC Data Channel Open');
  8833. Q('deskstatus').textContent = StatusStrs[desktop.State] + ", Soft-KVM";
  8834. desktop.m.hold(true);
  8835. webRtcDesktop.webRtcActive = true;
  8836. webRtcDesktop.softdesktop = CreateKvmDataChannel(webRtcDesktop.webchannel, CreateAgentRemoteDesktop('Desk', Q('id_mainarea')), desktop.m);
  8837. webRtcDesktop.softdesktop.m.setRotation(desktop.m.rotation);
  8838. webRtcDesktop.softdesktop.m.onScreenSizeChange = deskAdjust;
  8839. webRtcDesktop.softdesktop.m.ImageType = webpSupport?4:1; // Send 4 if WebP is supported, otherwise send 1 for JPEG.
  8840. if (desktopsettings.quality) { webRtcDesktop.softdesktop.m.CompressionLevel = desktopsettings.quality; } // Number from 1 to 100. 50 or less is best.
  8841. if (desktopsettings.scaling) { webRtcDesktop.softdesktop.m.ScalingLevel = desktopsettings.scaling; }
  8842. webRtcDesktop.softdesktop.Start();
  8843. // Check if we can get remote file access
  8844. // ###BEGIN###{DesktopInbandFiles}
  8845. /*
  8846. QV('go24', true); // Files
  8847. gdownloadFile = null;
  8848. p24files = webRtcDesktop.softdesktop;
  8849. p24targetpath = '';
  8850. webRtcDesktop.softdesktop.onControlMsg = onFilesControlData;
  8851. webRtcDesktop.softdesktop.sendCtrlMsg(JSON.stringify({ action: 'ls', reqid: 1, path: '' })); // Ask for the root folder
  8852. */
  8853. // ###END###{DesktopInbandFiles}
  8854. }
  8855. webRtcDesktop.webchannel.onclose = function (event) {
  8856. //if (urlvars['kvmdatatrace']) { console.log('WebRTC Data Channel Closed'); }
  8857. console.log('WebRTC Data Channel Closed');
  8858. webRtcDesktopReset();
  8859. }
  8860. webRtcDesktop.webrtc.onicecandidate = function (e) {
  8861. if (e.candidate == null) {
  8862. desktop.m.sendKvmData(JSON.stringify({ action: 'offer', ver: 1, sdp: webRtcDesktop.webrtcoffer.sdp }));
  8863. } else {
  8864. webRtcDesktop.webrtcoffer.sdp += ('a=' + e.candidate.candidate + '\r\n'); // New candidate, add it to the SDP
  8865. }
  8866. }
  8867. webRtcDesktop.webrtc.oniceconnectionstatechange = function () {
  8868. if ((webRtcDesktop != null) && (webRtcDesktop.webrtc != null) && ((webRtcDesktop.webrtc.iceConnectionState == 'disconnected') || (webRtcDesktop.webrtc.iceConnectionState == 'failed'))) { /*console.log('WebRTC ICE Failed');*/ webRtcDesktopReset(); }
  8869. }
  8870. webRtcDesktop.webrtc.createOffer(function (offer) {
  8871. // Got the offer
  8872. webRtcDesktop.webrtcoffer = offer;
  8873. webRtcDesktop.webrtc.setLocalDescription(offer, function () { }, webRtcDesktopReset);
  8874. }, webRtcDesktopReset, { mandatory: { OfferToReceiveAudio: false, OfferToReceiveVideo: false } });
  8875. } else if ((data.action == 'answer') && (webRtcDesktop != null)) {
  8876. // Complete the WebRTC channel
  8877. webRtcDesktop.webrtc.setRemoteDescription(new RTCSessionDescription({ type: 'answer', sdp: data.sdp }), function () { }, webRtcDesktopReset);
  8878. }
  8879. }
  8880. };
  8881. // Use TLS if TLS is set
  8882. if (desktopNode.conn==4 && desktopNode.intelamt!=null && desktopNode.intelamt.tls==1) {
  8883. desktop.Start(desktopNode._id, 16995, '*', '*', 1);
  8884. } else {
  8885. desktop.Start(desktopNode._id, 16994, '*', '*', 0);
  8886. }
  8887. desktop.contype = 2;
  8888. } else if ((contype == null) || (contype == 1) || ((contype == 3) && ((currentNode.agent.id > 4) && ((debugmode == null))))) {
  8889. // Setup the Mesh Agent remote desktop
  8890. desktop = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
  8891. desktop.m.UseExtendedKeyFlag = (desktopNode.agent.id < 5); // Only use extended keys on Windows agents for now
  8892. desktop.m.mouseCursorActive(xxcurrentView == 11);
  8893. desktop.debugmode = debugmode;
  8894. desktop.m.debugmode = debugmode;
  8895. desktop.attemptWebRTC = attemptWebRTC;
  8896. desktop.webrtcconfig = webrtcconfiguration;
  8897. desktop.options = {};
  8898. if (tsid != null) { desktop.options.tsid = tsid; }
  8899. if (consent != null) { desktop.options.consent = consent; }
  8900. if (desktopsettings.autolock == true) { desktop.options.autolock = true; }
  8901. desktop.onStateChanged = onDesktopStateChange;
  8902. if ((features2 & 0x2000) != 0) desktop.m.stopInput = true;
  8903. desktop.m.onRemoteInputLockChanged = function(obj, state) { QV('DeskInputLockedButton', state); QV('DeskInputUnLockedButton', !state); }
  8904. desktop.m.onKeyboardStateChanged = function(obj, state) {
  8905. QS('p11numlock').display = ((state & 1) ? 'inline-block' : 'none');
  8906. QS('p11scrolllock').display = ((state & 2) ? 'inline-block' : 'none');
  8907. QS('p11capslock').display = ((state & 4) ? 'inline-block' : 'none');
  8908. }
  8909. desktop.onConsoleMessageChange = function () {
  8910. if (desktop.consoleMessage) {
  8911. Q('p11DeskConsoleMsg').innerHTML += formatAgentConsoleMessage(desktop.consoleMessage, desktop.consoleMessageId, desktop.consoleMessageArgs);
  8912. QV('p11DeskConsoleMsg', true);
  8913. if (p11DeskConsoleMsgTimer != null) { clearTimeout(p11DeskConsoleMsgTimer); }
  8914. if (desktop.consoleMessageTimeout) { p11DeskConsoleMsgTimer = setTimeout(p11clearConsoleMsg, desktop.consoleMessageTimeout * 1000); }
  8915. } else {
  8916. p11clearConsoleMsg();
  8917. }
  8918. }
  8919. desktop.onMetadataChange = function(metadata) { updateMetadata(desktop, 'deskmetadata'); }
  8920. desktop.m.ImageType = desktopsettings.agentencoding; // Send 4 if WebP is supported, otherwise send 1 for JPEG.
  8921. desktop.m.CompressionLevel = desktopsettings.quality; // Number from 1 to 100. 50 or less is best.
  8922. desktop.m.ScalingLevel = desktopsettings.scaling;
  8923. if (desktopsettings.framerate) { desktop.m.FrameRateTimer = desktopsettings.framerate; }
  8924. if (desktopsettings.swapmouse) { desktop.m.SwapMouse = desktopsettings.swapmouse; }
  8925. if (desktopsettings.rmw) { desktop.m.ReverseMouseWheel = desktopsettings.rmw; }
  8926. if (desktopsettings.remotekeymap == true) { desktop.m.remoteKeyMap = desktopsettings.remotekeymap; }
  8927. //desktop.m.onDisplayinfo = deskDisplayInfo;
  8928. desktop.m.onScreenSizeChange = deskAdjust;
  8929. desktop.Start(desktopNode._id);
  8930. desktop.latency.callback = function(ms) { /* console.log('latency', ms); */ updateSessionTime(); };
  8931. desktop.contype = 1;
  8932. } else if (contype == 3) {
  8933. // Ask for user sessions
  8934. meshserver.send({ action: 'msg', type: 'userSessions', nodeid: currentNode._id, tag: consent });
  8935. } else if (contype == 4) {
  8936. // Setup RDP remote desktop
  8937. desktop = CreateRDPDesktop('Desk', domainUrl);
  8938. desktop.onStateChanged = onDesktopStateChange;
  8939. desktop.m.onScreenSizeChange = mdeskAdjust;
  8940. desktop.m.onClipboardChanged = function(text) { if ((text != null) && (desktopsettings.rdpautoclipboard) && (navigator.clipboard != null)) { navigator.clipboard.writeText(text).then(function() { }).catch(function(err) { console.log(err); }) } } // Put remote clipboard data into our clipboard
  8941. if (desktopsettings.rdpsmb) { desktop.m.SwapMouse = desktopsettings.rdpsmb; }
  8942. if (desktopsettings.rdprmw) { desktop.m.ReverseMouseWheel = desktopsettings.rdprmw; }
  8943. desktop.Start(desktopNode._id, currentNode.rdpport ? currentNode.rdpport : 3389, tsid);
  8944. desktop.contype = 4;
  8945. desktop.onConsoleMessageChange = function () {
  8946. if (desktop.consoleMessage) {
  8947. Q('p11DeskConsoleMsg').innerHTML += formatAgentConsoleMessage(desktop.consoleMessage, desktop.consoleMessageId, desktop.consoleMessageArgs);
  8948. QV('p11DeskConsoleMsg', true);
  8949. if (p11DeskConsoleMsgTimer != null) { clearTimeout(p11DeskConsoleMsgTimer); }
  8950. if (desktop.consoleMessageTimeout) { p11DeskConsoleMsgTimer = setTimeout(p11clearConsoleMsg, desktop.consoleMessageTimeout * 1000); }
  8951. } else {
  8952. p11clearConsoleMsg();
  8953. }
  8954. }
  8955. }
  8956. } else {
  8957. // Disconnect and clean up the remote desktop
  8958. desktop.Stop();
  8959. webRtcDesktopReset();
  8960. desktopNode = desktop = null;
  8961. if (pluginHandler != null) { pluginHandler.callHook('onDesktopDisconnect'); }
  8962. }
  8963. }
  8964. function askRdpCredentials() {
  8965. if (xxdialogMode) return;
  8966. var x = '<div>';
  8967. if (currentNode.rdp == 1) {
  8968. x += addHtmlValue("Credentials", '<select id=d2mode style=width:230px onchange=askRdpCredentialsValidate()><option value=1>' + "Use server credentials" + '</option><option value=2>' + "Use new credentials" + '</option></select>');
  8969. x += '<div id=d2cred style=display:none>';
  8970. } else {
  8971. x += '<div id=d2cred>';
  8972. }
  8973. x += addHtmlValue("Domain", '<input type=text id=d2domain style=width:230px maxlength=128 />');
  8974. x += addHtmlValue("Username", '<input type=text id=d2user style=width:230px onKeyUp=askRdpCredentialsValidate() maxlength=128 />');
  8975. x += addHtmlValue("Password", '<input type=password id=d2pass style=width:230px maxlength=128 autocomplete=off />');
  8976. if ((features2 & 0x00400000) == 0) { x += addHtmlValue("", '<label><input type="checkbox" id=d2savecred>' + "Remember credentials" + '</label>'); }
  8977. x += '</div>';
  8978. x += MoreStart();
  8979. x += addHtmlValue("Alt Shell", '<input type=text id=d2altshell style=width:230px maxlength=2048 />');
  8980. x += addHtmlValue("Working Dir", '<input type=text id=d2workdir style=width:230px maxlength=2048 />');
  8981. x += MoreEnd();
  8982. setDialogMode(2, "RDP Credentials", 19, askRdpCredentialsEx, x);
  8983. }
  8984. function askRdpCredentialsValidate() {
  8985. var ok = true;
  8986. if (currentNode.rdp == 1) { QV('d2cred', Q('d2mode').value == 2); if (Q('d2mode').value == 1) { QE('idx_dlgOkButton', true); return; } }
  8987. QE('idx_dlgOkButton', Q('d2user').value != '');
  8988. }
  8989. function askRdpCredentialsEx() {
  8990. var width = null, height = null;
  8991. if (desktopsettings.rdpsize) {
  8992. if (desktopsettings.rdpsize == 'browser') {
  8993. width = window.innerWidth;
  8994. height = window.innerHeight;
  8995. } else if (desktopsettings.rdpsize == 'screen') {
  8996. width = window.screen.width;
  8997. height = window.screen.height;
  8998. } else if (desktopsettings.rdpsize == 'canvas') {
  8999. width = Q('DeskParent').offsetWidth;
  9000. height = Q('DeskParent').offsetHeight;
  9001. } else {
  9002. var i = desktopsettings.rdpsize.indexOf('x');
  9003. if (i >= 1) {
  9004. width = parseInt(desktopsettings.rdpsize.substring(0, i));
  9005. height = parseInt(desktopsettings.rdpsize.substring(i + 1));
  9006. }
  9007. }
  9008. } else {
  9009. width = Q('DeskParent').offsetWidth;
  9010. height = Q('DeskParent').offsetHeight;
  9011. }
  9012. if ((currentNode.rdp == 1) && (Q('d2mode').value == 1)) {
  9013. connectDesktop(null, 4, { servercred: true, width: width, height: height, flags: (desktopsettings.rdpflags != null) ? desktopsettings.rdpflags : 0x2F, altshell: Q('d2altshell').value, workdir: Q('d2workdir').value });
  9014. } else {
  9015. var savecred = false;
  9016. if ((features2 & 0x00400000) == 0) { savecred = Q('d2savecred').checked; }
  9017. connectDesktop(null, 4, { domain: Q('d2domain').value, username: Q('d2user').value, password: Q('d2pass').value, savecred: savecred, width: width, height: height, flags: (desktopsettings.rdpflags != null) ? desktopsettings.rdpflags : 0x2F, altshell: Q('d2altshell').value, workdir: Q('d2workdir').value });
  9018. }
  9019. }
  9020. function updateMetadata(conn, elementid) {
  9021. var str = '', viewerCount = 0;
  9022. if (conn && (conn.State == 3)) {
  9023. if (conn.metadata && conn.metadata.users) { for (var i in conn.metadata.users) { viewerCount += conn.metadata.users[i]; } }
  9024. if (viewerCount > 1) { str = '<span onclick=showSessionMetadata(1) style=cursor:pointer>' + format(", {0} watching", viewerCount) + '</span>'; }
  9025. }
  9026. QH('deskmetadata', str);
  9027. if ((conn == desktop) && (xxdialogTag == ('sessionMetadata1'))) { showSessionMetadata(1); }
  9028. }
  9029. function showSessionMetadata(cid) {
  9030. if (xxdialogMode && (xxdialogTag != ('sessionMetadata' + cid))) return;
  9031. if (xxdialogMode) { setDialogMode(0); }
  9032. var conn = null;
  9033. if (cid == 1) { conn = desktop; }
  9034. if (conn && conn.metadata) {
  9035. var x = '';
  9036. if (conn.metadata.startTime) { x += addHtmlValue4("Start Time", printDateTime(new Date(conn.metadata.startTime))); }
  9037. if (conn.metadata.users) {
  9038. for (var i in conn.metadata.users) {
  9039. var val = (conn.metadata.users[i] == 1)?"1 connection":format("{0} connections", conn.metadata.users[i]);
  9040. x += addHtmlValue2(getUserName(i), val);
  9041. }
  9042. }
  9043. setDialogMode(2, "Session Information", 1, null, x, 'sessionMetadata' + cid);
  9044. }
  9045. }
  9046. function p11clearConsoleMsg() { QH('p11DeskConsoleMsg', ''); QV('p11DeskConsoleMsg', false); if (p11DeskConsoleMsgTimer) { clearTimeout(p11DeskConsoleMsgTimer); p11DeskConsoleMsgTimer = null; } }
  9047. function p12clearConsoleMsg() { QH('p12TermConsoleMsg', ''); QV('p12TermConsoleMsg', false); if (p12TermConsoleMsgTimer) { clearTimeout(p12TermConsoleMsgTimer); p12TermConsoleMsgTimer = null; } }
  9048. function p13clearConsoleMsg() { QH('p13FilesConsoleMsg', ''); QV('p13FilesConsoleMsg', false); if (p13FilesConsoleMsgTimer) { clearTimeout(p13FilesConsoleMsgTimer); p13FilesConsoleMsgTimer = null; } }
  9049. function p12setConsoleMsg(msg, timeout) {
  9050. if (msg) {
  9051. Q('p12TermConsoleMsg').innerHTML += msg;
  9052. QV('p12TermConsoleMsg', true);
  9053. if (p12TermConsoleMsgTimer != null) { clearTimeout(p12TermConsoleMsgTimer); }
  9054. if (timeout) { p12TermConsoleMsgTimer = setTimeout(p12clearConsoleMsg, timeout); }
  9055. } else {
  9056. p12clearConsoleMsg();
  9057. }
  9058. }
  9059. function p13setConsoleMsg(msg, timeout) {
  9060. if (msg) {
  9061. Q('p13FilesConsoleMsg').innerHTML += msg;
  9062. QV('p13FilesConsoleMsg', true);
  9063. if (p13FilesConsoleMsgTimer != null) { clearTimeout(p13FilesConsoleMsgTimer); }
  9064. if (timeout) { p13FilesConsoleMsgTimer = setTimeout(p13clearConsoleMsg, timeout); }
  9065. } else {
  9066. p13clearConsoleMsg();
  9067. }
  9068. }
  9069. var webRtcDesktop = null;
  9070. function webRtcDesktopReset() {
  9071. if (webRtcDesktop == null) return;
  9072. if (webRtcDesktop.softdesktop != null) { webRtcDesktop.softdesktop.Stop(); webRtcDesktop.softdesktop = null; }
  9073. if (webRtcDesktop.webchannel != null) { try { webRtcDesktop.webchannel.close(); } catch (e) { } webRtcDesktop.webchannel = null; }
  9074. if (webRtcDesktop.webrtc != null) { try { webRtcDesktop.webrtc.close(); } catch (e) { } webRtcDesktop.webrtc = null; }
  9075. webRtcDesktop = null;
  9076. // Switch back to hardware KVM
  9077. if (desktop && desktop.m) {
  9078. desktop.m.hold(false);
  9079. Q('deskstatus').textContent = StatusStrs[desktop.State];
  9080. }
  9081. // ###BEGIN###{DesktopInbandFiles}
  9082. /*
  9083. p24files = null;
  9084. p24downloadFileCancel() // If any downloads are in process, cancel them.
  9085. p24uploadFileCancel(); // If any uploads are in process, cancel them.
  9086. QV('go24', false); // Files
  9087. if (currentView == 24) { go(14); }
  9088. */
  9089. // ###END###{DesktopInbandFiles}
  9090. }
  9091. function onDesktopStateChange(xdesktop, state) {
  9092. var xstate = state;
  9093. if ((xstate == 3) && (xdesktop.contype == 2)) { xstate++; }
  9094. var str = StatusStrs[xstate];
  9095. if ((desktop != null) && (desktop.webRtcActive == true)) { str += ", WebRTC"; }
  9096. if ((desktop != null) && (xstate == 3) && (desktop.contype == 4)) { str += ", RDP"; }
  9097. //if (desktop.m.stopInput == true) { str += ', Loopback'; }
  9098. QH('deskstatus', str);
  9099. switch (state) {
  9100. case 0:
  9101. if (desktop != null) {
  9102. // Stop recording
  9103. if (desktop.m.recordedData != null) { deskRecordSession(); }
  9104. // Disconnect and clean up the remote desktop
  9105. desktop.Stop();
  9106. }
  9107. desktopNode = desktop = null;
  9108. QV('DeskFocus', false);
  9109. QV('DeskMonitorSelectionSpan', false);
  9110. QV('deskRecordIcon', false);
  9111. QV('DeskInputLockedButton', false);
  9112. QV('DeskInputUnLockedButton', false);
  9113. deskFocusBtn.value = "All Focus";
  9114. if (fullscreen == true) { deskToggleFull(); }
  9115. webRtcDesktopReset();
  9116. deskPreferedStickyDisplay = 0;
  9117. break;
  9118. case 2:
  9119. break;
  9120. case 3:
  9121. if (desktop && (desktop.serverIsRecording == true)) { QV('deskRecordIcon', true); }
  9122. desktop.startTime = new Date();
  9123. deskLastClipboardSent = null;
  9124. if (updateSessionTimer == null) { updateSessionTimer = setInterval(updateSessionTime, 1000); }
  9125. break;
  9126. default:
  9127. //console.log('Unknown onDesktopStateChange state', state);
  9128. break;
  9129. }
  9130. updateDesktopButtons();
  9131. deskAdjust();
  9132. setTimeout(deskAdjust, 50);
  9133. updateMetadata(desktop, 'deskmetadata');
  9134. }
  9135. function updateSessionTime() {
  9136. // Desktop
  9137. var latencyStr = '', seconds = 0;
  9138. if (desktop && desktop.startTime) {
  9139. if (desktop.latency && (desktop.latency.current >= 0)) { latencyStr = format('{0} ms', desktop.latency.current); }
  9140. seconds = Math.floor((new Date() - desktop.startTime) / 1000);
  9141. QH('DeskTimer', zeroPad(Math.floor(seconds / 3600), 2) + ':' + zeroPad((Math.floor(seconds / 60) % 60), 2) + ':' + zeroPad((seconds % 60), 2));
  9142. QH('DeskLatency', latencyStr);
  9143. // Auto-clipboard
  9144. if ((((desktop.contype != 4) && (desktopsettings.autoclipboard === true)) || ((desktop.contype == 4) && (desktopsettings.rdpautoclipboard === true))) && (navigator.clipboard != null) && (navigator.clipboard.readText != null)) {
  9145. if (Mstsc.browser() == 'firefox') return; // this is needed because firefox pops up a PASTE option every second which is annoying
  9146. try {
  9147. navigator.clipboard.readText().then(function(text) {
  9148. if (desktop == null) return;
  9149. if ((text != null) && (deskLastClipboardSent != text)) {
  9150. if (desktop.m.setClipboard) { desktop.m.setClipboard(text); } else { console.log('s3'); meshserver.send({ action: 'msg', type: 'setclip', nodeid: currentNode._id, data: text }); }
  9151. deskLastClipboardSent = text;
  9152. }
  9153. }).catch(function(err) { });
  9154. } catch (ex) { console.log(ex); }
  9155. }
  9156. } else {
  9157. QH('DeskTimer', '');
  9158. QH('DeskLatency', '');
  9159. }
  9160. // Terminal
  9161. seconds = 0;
  9162. if (terminal && terminal.startTime) {
  9163. if (terminal.latency && (terminal.latency.current >= 0)) { latencyStr = format('{0} ms, ', terminal.latency.current); }
  9164. seconds = Math.floor((new Date() - terminal.startTime) / 1000);
  9165. QH('TermTimer', latencyStr + zeroPad(Math.floor(seconds / 3600), 2) + ':' + zeroPad((Math.floor(seconds / 60) % 60), 2) + ':' + zeroPad((seconds % 60), 2));
  9166. } else {
  9167. QH('TermTimer', '');
  9168. }
  9169. if ((desktop == null) && (terminal == null)) { clearInterval(updateSessionTimer); updateSessionTimer = null; }
  9170. }
  9171. function showDesktopSettings() {
  9172. if (xxdialogMode) return;
  9173. applyDesktopSettings();
  9174. updateDesktopButtons();
  9175. var tabSelected = false; // See if a visible tab is currently selected
  9176. if ((QS('td7meshkvm').display != 'none') && (Q('td7meshkvm').className.indexOf('active') > 0)) { tabSelected = true; }
  9177. else if ((QS('td7rdpkvm').display != 'none') && (Q('td7rdpkvm').className.indexOf('active') > 0)) { tabSelected = true; }
  9178. else if ((QS('td7amtkvm').display != 'none') && (Q('td7amtkvm').className.indexOf('active') > 0)) { tabSelected = true; }
  9179. if (tabSelected == false) { // if not, select the first visible one
  9180. if (QS('td7meshkvm').display != 'none') { changeDesktopSettingsTab(null, 'd7meshkvm'); }
  9181. else if (QS('td7rdpkvm').display != 'none') { changeDesktopSettingsTab(null, 'd7rdpkvm'); }
  9182. else if (QS('td7amtkvm').display != 'none') { changeDesktopSettingsTab(null, 'd7amtkvm'); }
  9183. }
  9184. setDialogMode(7, "Remote Desktop Settings", 3, showDesktopSettingsChanged);
  9185. }
  9186. function changeDesktopSettingsTab(evt, tabname) {
  9187. // Declare all variables
  9188. var i, tabcontent, tablinks;
  9189. // Get all elements with class="tabcontent" and hide them
  9190. tabcontent = document.getElementsByClassName('tabcontent');
  9191. for (i = 0; i < tabcontent.length; i++) { tabcontent[i].style.display = 'none'; }
  9192. // Get all elements with class="tablinks" and remove the class "active"
  9193. tablinks = document.getElementsByClassName('tablinks');
  9194. for (i = 0; i < tablinks.length; i++) { tablinks[i].className = tablinks[i].className.replace(' active', ''); }
  9195. // Show the current tab, and add an "active" class to the button that opened the tab
  9196. document.getElementById(tabname).style.display = 'block';
  9197. if (evt != null) { evt.currentTarget.className += ' active'; } else { document.getElementById('t' + tabname).className += ' active'; }
  9198. }
  9199. function showDesktopSettingsChanged() {
  9200. desktopsettings.encoding = d7desktopmode.value;
  9201. desktopsettings.showfocus = d7showfocus.checked;
  9202. desktopsettings.showmouse = d7showcursor.checked;
  9203. desktopsettings.quality = d7bitmapquality.value;
  9204. desktopsettings.scaling = d7bitmapscaling.value;
  9205. desktopsettings.framerate = d7framelimiter.value;
  9206. desktopsettings.agentencoding = d7encoding.value;
  9207. desktopsettings.swapmouse = d7deskSwapMouse.checked;
  9208. desktopsettings.rmw = d7deskrmw.checked;
  9209. desktopsettings.remotekeymap = d7deskRemoteKeyMap.checked;
  9210. desktopsettings.autoclipboard = d7deskAutoClipboard.checked;
  9211. desktopsettings.autolock = d7deskAutoLock.checked;
  9212. desktopsettings.localkeymap = d7localKeyMap.checked;
  9213. desktopsettings.kvmrmw = d7kvmrmw.checked;
  9214. desktopsettings.rdpsize = d7rdpsize.value;
  9215. desktopsettings.rdpsmb = d7rdpsmb.checked;
  9216. desktopsettings.rdprmw = d7rdprmw.checked;
  9217. desktopsettings.rdpautoclipboard = d7rdpclip.checked;
  9218. var rdpflags = 0;
  9219. for (var i = 1; i < 10; i++) { if ((i != 5) && (Q('d7rdp' + i).checked)) { rdpflags |= (1 << (i - 1)); } }
  9220. desktopsettings.rdpflags = rdpflags;
  9221. putstore('desktopsettings', JSON.stringify(desktopsettings));
  9222. applyDesktopSettings();
  9223. updateDesktopButtons();
  9224. if (desktop) {
  9225. if (desktop.contype == 1) { // Mesh Agent Remote Desktop
  9226. desktop.m.SwapMouse = desktopsettings.swapmouse;
  9227. desktop.m.ReverseMouseWheel = desktopsettings.rmw;
  9228. desktop.m.remoteKeyMap = desktopsettings.remotekeymap;
  9229. if (desktop.State != 0) {
  9230. desktop.m.SendCompressionLevel(desktopsettings.agentencoding, desktopsettings.quality, desktopsettings.scaling, desktopsettings.framerate);
  9231. desktop.sendCtrlMsg('{"ctrlChannel":"102938","type":"autolock","value":' + desktopsettings.autolock + '}');
  9232. desktop.m.SendRefresh();
  9233. }
  9234. }
  9235. if (desktop.contype == 2) { // Intel AMT KVM
  9236. desktop.m.ReverseMouseWheel = desktopsettings.kvmrmw;
  9237. if (desktopsettings.showfocus == false) { desktop.m.focusmode = 0; deskFocusBtn.value = "All Focus"; }
  9238. if (desktop.State != 0) { desktop.Stop(); setTimeout(function () { connectDesktop(null, 2); }, 50); }
  9239. }
  9240. if (desktop.contype == 4) { // Web-RDP
  9241. desktop.m.SwapMouse = desktopsettings.rdpsmb;
  9242. desktop.m.ReverseMouseWheel = desktopsettings.rdprmw;
  9243. }
  9244. }
  9245. }
  9246. function applyDesktopSettings() {
  9247. var r = '', ops = (features & 512)?[100,90,80,70,60,50,40,30,20,10,5,1]:[60,50,40,30,20,10,5,1];
  9248. for (var i in ops) { r += '<option value=' + ops[i] + '>' + ops[i] + '%</option>'; }
  9249. QH('d7bitmapquality', r);
  9250. d7desktopmode.value = desktopsettings.encoding;
  9251. d7showfocus.checked = desktopsettings.showfocus;
  9252. d7showcursor.checked = desktopsettings.showmouse;
  9253. if (desktopsettings.agentencoding) { d7encoding.value = desktopsettings.agentencoding; } else { desktopsettings.agentencoding = 4; }
  9254. d7bitmapquality.value = 40; // Default value
  9255. if (ops.indexOf(parseInt(desktopsettings.quality)) >= 0) { d7bitmapquality.value = desktopsettings.quality; }
  9256. d7bitmapscaling.value = desktopsettings.scaling;
  9257. if (desktopsettings.framerate) { d7framelimiter.value = desktopsettings.framerate; } else { d7framelimiter.value = 100; }
  9258. if (desktopsettings.swapmouse != null) { d7deskSwapMouse.checked = desktopsettings.swapmouse; }
  9259. if (desktopsettings.rmw != null) { d7deskrmw.checked = desktopsettings.rmw; }
  9260. if (desktopsettings.remotekeymap != null) { d7deskRemoteKeyMap.checked = desktopsettings.remotekeymap; }
  9261. if (desktopsettings.autoclipboard != null) { d7deskAutoClipboard.checked = desktopsettings.autoclipboard; }
  9262. if (desktopsettings.autolock != null) { d7deskAutoLock.checked = desktopsettings.autolock; }
  9263. if (desktopsettings.localkeymap) { d7localKeyMap.checked = desktopsettings.localkeymap; }
  9264. if (desktopsettings.kvmrmw) { d7kvmrmw.checked = desktopsettings.kvmrmw; }
  9265. QV('deskFocusBtn', (desktop != null) && (desktop.contype == 2) && (desktop.state != 0) && (desktopsettings.showfocus));
  9266. if (desktopsettings.rdpsize != null) { d7rdpsize.value = desktopsettings.rdpsize; }
  9267. if (desktopsettings.rdpflags == null) { desktopsettings.rdpflags = 0x2F; }
  9268. if (desktopsettings.rdpsmb != null) { d7rdpsmb.checked = desktopsettings.rdpsmb; }
  9269. if (desktopsettings.rdprmw != null) { d7rdprmw.checked = desktopsettings.rdprmw; }
  9270. if (desktopsettings.rdpautoclipboard != null) { d7rdpclip.checked = desktopsettings.rdpautoclipboard; }
  9271. for (var i = 1; i < 10; i++) { if (i != 5) { Q('d7rdp' + i).checked = ((desktopsettings.rdpflags & (1 << (i - 1))) != 0); } }
  9272. }
  9273. // Enter browser fullscreen
  9274. function enterBrowserFullscreen(elem) {
  9275. if (navigator.keyboard && navigator.keyboard.lock) { navigator.keyboard.lock(); }
  9276. if (elem.requestFullscreen) { elem.requestFullscreen(); }
  9277. else if (elem.msRequestFullscreen) { elem.msRequestFullscreen(); }
  9278. else if (elem.mozRequestFullScreen) { elem.mozRequestFullScreen(); }
  9279. else if (elem.webkitRequestFullscreen) { elem.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); }
  9280. }
  9281. // Exit browser fullscreen
  9282. function exitBrowserFullscreen() {
  9283. if (document.exitFullscreen) { document.exitFullscreen(); }
  9284. else if (document.msExitFullscreen) { document.msExitFullscreen(); }
  9285. else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); }
  9286. else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); }
  9287. if (navigator.keyboard && navigator.keyboard.unlock) { navigator.keyboard.unlock(); }
  9288. }
  9289. // Return true if the browser is fullscreen. This is a delayed method that will return true/false late. Not very useful.
  9290. function isBrowserFullscreen() {
  9291. if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement) { return false; } else { return true; }
  9292. }
  9293. var fullscreen = false;
  9294. var browserfullscreen = false;
  9295. function deskToggleFull(e) {
  9296. var xtermActive = !((args.xterm === 0) || ((terminal != null) && (xterm == null)));
  9297. fullscreen = !fullscreen;
  9298. if (fullscreen) {
  9299. QC('body').add('fulldesk');
  9300. QS('deskarea3x')['height'] = '100%';
  9301. QS('deskarea3x')['max-height'] = '100%';
  9302. if (xtermActive) {
  9303. // XTerm terminal
  9304. QS('termTable')['position'] = 'absolute';
  9305. QS('termTable')['top'] = QS('termTable')['bottom'] = QS('termTable')['left'] = QS('termTable')['right'] = '0';
  9306. } else {
  9307. // Legacy terminal
  9308. QS('termTable')['height'] = '100%';
  9309. QS('termTable')['max-height'] = '100%';
  9310. }
  9311. // If shift is pressed, enter browser full screen.
  9312. if (e.shiftKey == true) {
  9313. enterBrowserFullscreen(Q('body'));
  9314. browserfullscreen = true;
  9315. }
  9316. } else {
  9317. QC('body').remove('fulldesk');
  9318. var hide = args.hide;
  9319. if (footerBar == false) { hide |= 4; }
  9320. var xh = (((hide & 1) ? 0 : 66) + ((hide & 2) ? 0 : 24) + ((hide & 4) ? 0 : 45) + ((hide & 8) ? 0 : 60)); // 0 to 195
  9321. QS('deskarea3x')['height'] = 'calc(100vh - ' + (75 + xh) + 'px)';
  9322. QS('deskarea3x')['max-height'] = 'calc(100vh - ' + (75 + xh) + 'px)';
  9323. if (xtermActive) {
  9324. // XTerm terminal
  9325. QS('termTable')['position'] = null;
  9326. QS('termTable')['top'] = QS('termTable')['bottom'] = QS('termTable')['left'] = QS('termTable')['right'] = null;
  9327. } else {
  9328. // Legacy terminal
  9329. QS('termTable')['height'] = 'calc(100vh - ' + (75 + xh) + 'px)';
  9330. QS('termTable')['max-height'] = 'calc(100vh - ' + (75 + xh) + 'px)';
  9331. }
  9332. if (browserfullscreen == true) { exitBrowserFullscreen(); browserfullscreen = false; }
  9333. }
  9334. deskAdjust();
  9335. updateDesktopButtons();
  9336. adjustPanels();
  9337. //setTimeout(adjustPanels, 10);
  9338. //setTimeout(function() { xtermfit.fit(); }, 10);
  9339. if (xterm != null) { if (xxcurrentView == 12) { xtermfit.fit(); xterm.focus(); } }
  9340. }
  9341. function deskToggleFocus() {
  9342. desktop.m.focusmode = (desktop.m.focusmode + 64) % 192;
  9343. Q('deskFocusBtn').value = ["All Focus", "Small Focus", "Large Focus"][desktop.m.focusmode / 64];
  9344. }
  9345. function deskAdjust() {
  9346. //if (xxcurrentView == 1) return;
  9347. var parentH = Q('DeskParent').clientHeight, parentW = Q('DeskParent').clientWidth;
  9348. var deskH = Q('Desk').height, deskW = Q('Desk').width;
  9349. if (deskAspectRatio == 2) {
  9350. // Scale mode
  9351. QS('Desk')['margin-top'] = null;
  9352. QS('Desk').height = '100%';
  9353. QS('Desk').width = '100%';
  9354. //QS('deskarea3x').height = null;
  9355. QS('DeskParent').overflow = 'hidden';
  9356. } else if (deskAspectRatio == 1) {
  9357. // Zoomed mode
  9358. QS('Desk')['margin-top'] = '0px';
  9359. QS('Desk').height = deskH + 'px';
  9360. QS('Desk').width = deskW + 'px';
  9361. QS('DeskParent').overflow = 'scroll';
  9362. } else {
  9363. // Fixed aspect ratio
  9364. if ((parentH / parentW) > (deskH / deskW)) {
  9365. var hNew = ((deskH * parentW) / deskW) + 'px';
  9366. //if (webPageFullScreen || fullscreen) {
  9367. // QS('deskarea3x').height = null;
  9368. //} else {
  9369. // QS('deskarea3x').height = hNew;
  9370. // QS('deskarea3x').height = null;
  9371. //}
  9372. QS('Desk').height = hNew;
  9373. QS('Desk').width = '100%';
  9374. } else {
  9375. var wNew = ((deskW * parentH) / deskH) + 'px';
  9376. if (webPageFullScreen || fullscreen) {
  9377. QS('Desk').height = null;
  9378. } else {
  9379. QS('Desk').height = '100%';
  9380. }
  9381. QS('Desk').width = wNew;
  9382. }
  9383. QS('Desk')['margin-top'] = null;
  9384. QS('DeskParent').overflow = 'hidden';
  9385. }
  9386. }
  9387. function mdeskAdjust(module, sw, sh, cv) {
  9388. if (!module || !sw || !sh || !cv) return;
  9389. var view = Q('viewselect').value;
  9390. if ((view != 3) && (view != 5)) return;
  9391. // Check if we are in single desktop mode
  9392. if (cv.id == 'Desk') { deskAdjust(); return; }
  9393. if (view == 5) { // If not in fixed width mode, compute the with
  9394. var vsize = [{ x: 180, y: 101 }, { x: 302, y: 169 }, { x: 454, y: 255 }][Q('sizeselect').selectedIndex];
  9395. QS(cv.id)['width'] = ((module.State != 0)?((sw * vsize.y) / sh):(vsize.x)) + 'px';
  9396. }
  9397. QS(cv.id)['margin-top'] = null;
  9398. QS(cv.id)['margin-bottom'] = null;
  9399. }
  9400. // Press ESC key
  9401. function sendDeskEsc() {
  9402. if (desktop == null || desktop.State != 3) return;
  9403. if (desktop.contype == 2) {
  9404. desktop.m.sendkey([[65307,1],[65307,0]]); // Intel AMT: ESC down, ESC release
  9405. } else {
  9406. desktop.m.SendKeyMsgKC([[desktop.m.KeyAction.EXDOWN,27], [desktop.m.KeyAction.EXUP,27]]); // MeshAgent: ESC press, ESC release
  9407. }
  9408. }
  9409. function p11fileDragDrop(e) {
  9410. haltEvent(e);
  9411. QV('p11bigfail', false);
  9412. QV('p11bigok', false);
  9413. if ((xxdialogMode != null) || (desktop == null) || (desktop.State != 3) || (e.dataTransfer == null) || (e.dataTransfer.files.length == 0)) return;
  9414. var file = e.dataTransfer.files[0];
  9415. var filename = file.name.toLowerCase();
  9416. if (filename.endsWith('.bat') || filename.endsWith('.ps1') || filename.endsWith('.sh') || filename.endsWith('.agentconsole')) {
  9417. d2runCommandDialog({ nodeids: [ currentNode._id ], selectedFile: file });
  9418. } else if (filename.endsWith('.txt')) {
  9419. var reader = new FileReader();
  9420. reader.onload = function (e) { showDeskType(e.target.result); }
  9421. reader.readAsText(file);
  9422. }
  9423. }
  9424. var p11dragtimer = null;
  9425. function p11fileDragOver(e) {
  9426. haltEvent(e);
  9427. if (p11dragtimer != null) { clearTimeout(p11dragtimer); p11dragtimer = null; }
  9428. var ac = ((xxdialogMode == null) && (desktop != null) && (desktop.State == 3));
  9429. QV('p11bigok', ac);
  9430. QV('p11bigfail', !ac);
  9431. }
  9432. function p11fileDragLeave(e) {
  9433. haltEvent(e);
  9434. if (e.target.id != 'Desk') {
  9435. QV('p11bigfail', false);
  9436. QV('p11bigok', false);
  9437. } else {
  9438. p11dragtimer = setTimeout(function () { QV('p11bigfail',false); QV('p11bigok',false); p11dragtimer = null; }, 10);
  9439. }
  9440. }
  9441. //
  9442. // Desktop Shortcut Keys
  9443. //
  9444. function updateDeskShortcutKeys() {
  9445. var x = '', v = parseInt(Q('deskkeys').value);
  9446. if ((v == '') && (deskKeyboardShortcuts.length > 0)) { v = deskKeyboardShortcuts[0]; }
  9447. for (var i in deskKeyboardShortcuts) { x += '<option value=' + deskKeyboardShortcuts[i] + ((v == deskKeyboardShortcuts[i])?' selected':'') + '>' + keyShortcutTotext(deskKeyboardShortcuts[i]) + '</option>'; }
  9448. //x += '<option value=-1' + ((v == -1)?' selected':'') + '>' + "Customize" + '</option>';
  9449. QH('deskkeys', x);
  9450. }
  9451. var keyStrings = { 8 : "BackSpace", 9 : "Tab", 13 : "Enter", 27 : "Escape", 32 : "Space", 44 : "Print Screen", 45 : "Insert", 46 : "Del", 36 : "Home", 35 : "End", 33 : "Page Up", 34 : "Page Down", 37 : "Left", 38 : "Up", 39 : "Right", 40 : "Down", 0 : "None" }
  9452. function keyShortcutTotext(n) {
  9453. var x = [];
  9454. if (n & 0x010000) { x.push("Shift"); }
  9455. if (n & 0x020000) { x.push("Alt"); }
  9456. if (n & 0x080000) { x.push("Ctrl"); }
  9457. if (n & 0x100000) { x.push("Win"); }
  9458. n = (n & 0xFFFF);
  9459. if ((n >= 112) && (n <= 123)) { x.push('F' + (n - 111)); } // Fx keys
  9460. else if ((n != 0) && (keyStrings[n])) { x.push(keyStrings[n]); }
  9461. else { if (n != 0) { x.push(String.fromCharCode(n)); } }
  9462. return x.join(' + ');
  9463. }
  9464. // Customize keyboard shortcuts
  9465. function deskCustomizeKeys() {
  9466. if (xxdialogMode) return;
  9467. 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>';
  9468. 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>';
  9469. x += ' <select id=d2keySelect>';
  9470. for (var i in keyStrings) { x += '<option value=' + i + '>' + keyStrings[i] + '</option>'; }
  9471. for (var i = 1; i <= 12; i++) { x += '<option value=' + (i + 111) + '>F' + i + '</option>'; }
  9472. for (var i = 0; i < 10; i++) { x += '<option value=' + (i + 48) + '>' + i + '</option>'; }
  9473. for (var i = 0; i < 26; i++) { x += '<option value=' + (i + 65) + '>' + String.fromCharCode(i + 65) + '</option>'; }
  9474. x += '</select> <input type=button value=' + "Add" + ' onclick=addDeskCustomizeKey() /></div>';
  9475. x += '<div style=width:100%;padding:2px;text-align:center><input type=button value="' + "Restore Default Keyboard Shortcuts" + '" onclick=restoreDeskCustomizeKey() /></div>';
  9476. setDialogMode(2, "Keyboard Shortcuts Customization", 1, deskCustomizeKeysEx, x);
  9477. deskUpdateShortcutList();
  9478. }
  9479. function deskCustomizeKeysEx() {
  9480. putstore('deskKeyShortcuts', deskKeyboardShortcuts.join(','));
  9481. updateDeskShortcutKeys();
  9482. }
  9483. function deskUpdateShortcutList() {
  9484. var x = '';
  9485. for (var i in deskKeyboardShortcuts) {
  9486. var kt = keyShortcutTotext(deskKeyboardShortcuts[i]), orderButtons = '';
  9487. 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] + ')>'; }
  9488. if (i != 0) { orderButtons += '<img width=8 height=8 style=float:right;cursor:pointer;padding:3px src="images/c3.png" onclick=deskCustomizeKeyUp(' + deskKeyboardShortcuts[i] + ')>'; }
  9489. 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>';
  9490. }
  9491. if (x == '') { x = '<i>' + "No keyboard shortcuts defined" + '</i>'; }
  9492. QH('d2shortcuts', x);
  9493. }
  9494. function deskCustomizeKeyDown(k) {
  9495. var i = deskKeyboardShortcuts.indexOf(k), x = deskKeyboardShortcuts[i + 1];
  9496. deskKeyboardShortcuts[i + 1] = deskKeyboardShortcuts[i];
  9497. deskKeyboardShortcuts[i] = x;
  9498. deskUpdateShortcutList();
  9499. }
  9500. function deskCustomizeKeyUp(k) {
  9501. var i = deskKeyboardShortcuts.indexOf(k), x = deskKeyboardShortcuts[i];
  9502. deskKeyboardShortcuts[i] = deskKeyboardShortcuts[i - 1];
  9503. deskKeyboardShortcuts[i - 1] = x;
  9504. deskUpdateShortcutList();
  9505. }
  9506. function removeDeskCustomizeKey(k) {
  9507. var na = [];
  9508. for (var i in deskKeyboardShortcuts) { if (deskKeyboardShortcuts[i] != k) { na.push(deskKeyboardShortcuts[i]); } }
  9509. deskKeyboardShortcuts = na;
  9510. deskUpdateShortcutList();
  9511. }
  9512. function restoreDeskCustomizeKey() {
  9513. deskKeyboardShortcuts = [];
  9514. putstore('deskKeyShortcuts', null);
  9515. var deskKeyboardShortcutsStr = getstore('deskKeyShortcuts', '0x0A002E,0x100000,0x100028,0x100026,0x10004C,0x10004D,0x11004D,0x100052,0x020073,0x080057,0x020009,0x100025,0x100027').split(',');
  9516. for (var i in deskKeyboardShortcutsStr) { if (deskKeyboardShortcutsStr[i] != "") { deskKeyboardShortcuts.push(parseInt(deskKeyboardShortcutsStr[i])); } }
  9517. updateDeskShortcutKeys();
  9518. deskUpdateShortcutList();
  9519. }
  9520. function addDeskCustomizeKey() {
  9521. var k = parseInt(Q('d2keySelect').value);
  9522. if (Q('d1kshift').checked) { k |= 0x010000; }
  9523. if (Q('d1kalt').checked) { k |= 0x020000; }
  9524. if (Q('d1kctrl').checked) { k |= 0x080000; }
  9525. if (Q('d1kwin').checked) { k |= 0x100000; }
  9526. if ((k > 0) && (deskKeyboardShortcuts.indexOf(k) == -1)) { deskKeyboardShortcuts.push(k); deskUpdateShortcutList(); }
  9527. }
  9528. // Customize keyboard strings
  9529. function deskCustomizeStrings() {
  9530. if (xxdialogMode) return;
  9531. var x = '<div id=d2strings style="width:100%;height:180px;padding:4px;overflow-y:auto;border:1px solid gray"></div><div style=width:100%;padding:5px>';
  9532. x += addHtmlValue("Name", '<input type=text id=d2shortcutname style=width:230px maxlength=32 autocomplete=off onkeyup=deskCustomizeStringsUpdate(event) />');
  9533. x += addHtmlValue("Value", '<input type=text id=d2shortcutvalue style=width:230px maxlength=256 autocomplete=off onkeyup=deskCustomizeStringsUpdate(event) />');
  9534. x += addHtmlValue('', '<input id=d2addbutton type=button value=' + "Add" + ' onclick=addDeskCustomizeString() style=float:right />');
  9535. x + '</div>';
  9536. setDialogMode(2, "Keyboard Strings Customization", 1, deskCustomizeStringsEx, x);
  9537. deskUpdateStringsList();
  9538. deskCustomizeStringsUpdate();
  9539. }
  9540. function deskCustomizeStringsUpdate() {
  9541. QE('d2addbutton', ((Q('d2shortcutname').value != '') && (Q('d2shortcutvalue').value != '')));
  9542. }
  9543. function deskCustomizeStringsEx() {
  9544. putstore('deskStrings', JSON.stringify(deskKeyboardStrings));
  9545. updateDesktopStrings();
  9546. }
  9547. function deskUpdateStringsList() {
  9548. var x = '';
  9549. for (var i in deskKeyboardStrings) {
  9550. var orderButtons = '';
  9551. if (i != (deskKeyboardStrings.length - 1)) { orderButtons += '<img width=8 height=8 style=float:right;cursor:pointer;padding:3px src="images/c2.png" onclick=deskCustomizeStringDown(' + i + ')>'; }
  9552. if (i != 0) { orderButtons += '<img width=8 height=8 style=float:right;cursor:pointer;padding:3px src="images/c3.png" onclick=deskCustomizeStringUp(' + i + ')>'; }
  9553. x += '<div style="width:100%;background-color:#AAA;border-radius:4px;margin-bottom:4px;padding:4px;text-align:left;box-sizing:border-box" value=' + (i + 1000) + '><div><b>' + EscapeHtml(deskKeyboardStrings[i].n) + '</b><img width=10 height=10 style=float:right;cursor:pointer;padding:2px;margin-left:8px src="images/trash.png" onclick=removeDeskCustomizeString(' + i + ')>' + orderButtons + '</div>';
  9554. x += '<div stlye=font-size:x-small>' + EscapeHtml(deskKeyboardStrings[i].v) + '</div>';
  9555. x += '</div>';
  9556. }
  9557. if (x == '') { x = '<i>' + "No keyboard strings defined" + '</i>'; }
  9558. QH('d2strings', x);
  9559. }
  9560. function deskCustomizeStringDown(i) {
  9561. var x = deskKeyboardStrings[i + 1];
  9562. deskKeyboardStrings[i + 1] = deskKeyboardStrings[i];
  9563. deskKeyboardStrings[i] = x;
  9564. deskUpdateStringsList();
  9565. }
  9566. function deskCustomizeStringUp(i) {
  9567. var x = deskKeyboardStrings[i];
  9568. deskKeyboardStrings[i] = deskKeyboardStrings[i - 1];
  9569. deskKeyboardStrings[i - 1] = x;
  9570. deskUpdateStringsList();
  9571. }
  9572. function removeDeskCustomizeString(i) {
  9573. deskKeyboardStrings.splice(i, 1);
  9574. deskUpdateStringsList();
  9575. }
  9576. function addDeskCustomizeString() {
  9577. if (!Array.isArray(deskKeyboardStrings)) { deskKeyboardStrings = [];}
  9578. var name = Q('d2shortcutname').value;
  9579. var value = Q('d2shortcutvalue').value;
  9580. if ((name != '') && (value != '')) { deskKeyboardStrings.push({ n: name, v: value }); deskUpdateStringsList(); }
  9581. }
  9582. function updateDesktopStrings() {
  9583. var x = '';
  9584. for (var i in deskKeyboardStrings) { var j = (parseInt(i) + 1000); x += '<div class="cmtext" onclick="cmdeskpreconfigtypeaction(' + j + ',event)">' + EscapeHtml(deskKeyboardStrings[i].n) + '</div>'; }
  9585. if (x != '') { x += '<hr />' };
  9586. QH('deskPreConfigShortcutContextMenu2', x);
  9587. }
  9588. // Remote desktop special key combos for Windows
  9589. function deskSendKeys() {
  9590. Q('DeskWD').blur();
  9591. if (xxdialogMode || desktop == null || desktop.State != 3) return;
  9592. // Construct the key command
  9593. var ks = parseInt(Q('deskkeys').value);
  9594. if (ks == 0x0A002E) { desktop.m.sendcad(); return; } // CTRL-ALT-DEL
  9595. //if ((desktop.contype == 1) && (ks == 0x10004C)) { desktop.sendCtrlMsg('{"action":"lock"}'); return; } // Lock desktop, WIN + L
  9596. var flags = (ks & 0xFF0000) >> 16, key = (ks & 0xFFFF), keyArray = [], keyArray2 = [];
  9597. var amtTranslate = {
  9598. 8 : 0xff08, // BackSpace
  9599. 9 : 0xff09, // Tab
  9600. 13 : 0xff0d, // Return or Enter
  9601. 27 : 0xff1b, // Escape
  9602. 45 : 0xff63, // Insert
  9603. 46 : 0xffff, // Delete
  9604. 36 : 0xff50, // Home
  9605. 35 : 0xff57, // End
  9606. 33 : 0xff55, // Page Up
  9607. 34 : 0xff56, // Page Down
  9608. 37 : 0xff51, // Left arrow
  9609. 38 : 0xff52, // Up arrow
  9610. 39 : 0xff53, // Right arrow
  9611. 40 : 0xff54, // Down arrow
  9612. 112 : 0xffbe, // F1
  9613. 113 : 0xffbf, // F2
  9614. 114 : 0xffc0, // F3
  9615. 115 : 0xffc1, // F4
  9616. 116 : 0xffc2, // F5
  9617. 117 : 0xffc3, // F6
  9618. 118 : 0xffc4, // F7
  9619. 119 : 0xffc5, // F8
  9620. 120 : 0xffc6, // F9
  9621. 121 : 0xffc7, // F10
  9622. 122 : 0xffc8, // F11
  9623. 123 : 0xffc9 // F12
  9624. }
  9625. // 0x010000 = Shift
  9626. // 0x020000 = Left-Alt
  9627. // 0x080000 = Ctrl
  9628. // 0x100000 = Window
  9629. // Examples:
  9630. // WIN+DOWN = 0x100028
  9631. // WIN+UP = 0x100026
  9632. // WIN+L = 0x10004C
  9633. // WIN+M = 0x10004D
  9634. // Shift+WIN+M = 0x11004D
  9635. // WIN = 0x100000
  9636. // WIN+R = 0x100052
  9637. // ALT+F4 = 0x020073
  9638. // CTRL+W = 0x080057
  9639. // ALT+TAB = 0x020009
  9640. // CTRL-ALT-DEL = 0x0A002E
  9641. // WIN-LEFT = 0x100025
  9642. // WIN-RIGHT = 0x100027
  9643. // SHIFT+F10 = 0x010079
  9644. if (desktop.contype == 2) {
  9645. // Intel AMT
  9646. if (flags & 1) { keyArray.push([0xffe1, 1]); keyArray2.push([0xffe1, 0]); } // Shift
  9647. if (flags & 2) { keyArray.push([0xffe9, 1]); keyArray2.push([0xffe9, 0]); } // Left-alt
  9648. if (flags & 8) { keyArray.push([0xffe3, 1]); keyArray2.push([0xffe3, 0]); } // Ctrl
  9649. if (flags & 16) { keyArray.push([0xffe7, 1]); keyArray2.push([0xffe7, 0]); } // Windows key
  9650. if (amtTranslate[key]) { key = amtTranslate[key]; }
  9651. if ((key >= 65) && (key <= 90)) { key += 32; }
  9652. if (key != 0) { keyArray.push([key, 1]); keyArray2.push([key, 0]); }
  9653. keyArray2.reverse();
  9654. for (var i = 0; i < keyArray2.length; i++) { keyArray.push(keyArray2[i]); }
  9655. desktop.m.sendkey(keyArray);
  9656. } else {
  9657. // Agent desktop
  9658. if (flags & 1) { keyArray.push([desktop.m.KeyAction.DOWN, 16]); keyArray2.push([desktop.m.KeyAction.UP, 16]); } // Shift
  9659. if (flags & 2) { keyArray.push([desktop.m.KeyAction.EXDOWN, 18]); keyArray2.push([desktop.m.KeyAction.EXUP, 18]); } // Left-alt
  9660. if (flags & 8) { keyArray.push([desktop.m.KeyAction.EXDOWN, 17]); keyArray2.push([desktop.m.KeyAction.EXUP, 17]); } // Ctrl
  9661. if (flags & 16) { keyArray.push([desktop.m.KeyAction.EXDOWN, 0x5B]); keyArray2.push([desktop.m.KeyAction.EXUP, 0x5B]); } // Windows key
  9662. if (key != 0) { keyArray.push([desktop.m.KeyAction.DOWN, key]); keyArray2.push([desktop.m.KeyAction.UP, key]); }
  9663. keyArray2.reverse();
  9664. for (var i = 0; i < keyArray2.length; i++) { keyArray.push(keyArray2[i]); }
  9665. desktop.m.SendKeyMsgKC(keyArray);
  9666. }
  9667. }
  9668. // Remote desktop typing
  9669. function showDeskType(text) {
  9670. if (xxdialogMode || desktop == null || desktop.State != 3) return;
  9671. Q('DeskType').blur();
  9672. var x = '<div>' + "Enter text and click OK to remotely type it. Make sure to place the remote cursor at the correct position before proceeding." + '<div>';
  9673. x += '<textarea id=d2typeText style="margin-top:5px;width:100%;height:184px;resize:none" maxlength=2000>' + (text ? EscapeHtml(text) : '') + '</textarea>';
  9674. setDialogMode(2, "Remote Keyboard Entry", 3, showDeskTypeEx, x);
  9675. Q('d2typeText').focus();
  9676. }
  9677. var AmtDeskTypeTimer = null;
  9678. var AmtDeskTypeContent = null;
  9679. var DeskTypeTranslate = { 39: 222, 42: 106, 43: 107, 44: 188, 45: 189, 46: 190, 47: 191, 59: 186, 61: 187, 91: 219, 92: 220, 93: 221, 96: 192, 191: 111 };
  9680. var DeskTypeShiftTranslate = { 33: 49, 34: 222, 35: 51, 36: 52, 37: 53, 38: 55, 40: 57, 41: 48, 58: 186, 60: 188, 62: 190, 63: 191, 64: 50, 94: 54, 95: 189, 106: 56, 107: 187, 123: 219, 124: 220, 125: 221, 126: 192 };
  9681. function showDeskTypeEx(text) {
  9682. var txt, ltxt, x = [], shift = false;
  9683. if (typeof text == 'string') { txt = text, ltxt = text.toUpperCase() } else { txt = Q('d2typeText').value, ltxt = Q('d2typeText').value.toUpperCase(); }
  9684. if (desktop.contype == 2) {
  9685. // Intel AMT
  9686. for (var i in txt) { var a = txt.charCodeAt(i); x.push([a, 1], [a, 0]); }
  9687. AmtDeskTypeContent = x;
  9688. AmtDeskTypeTimer = setInterval(function () {
  9689. var key = AmtDeskTypeContent.shift();
  9690. if (desktop) { desktop.m.sendkey(key[0], key[1]); }
  9691. if ((desktop == null) || (AmtDeskTypeContent.length == 0)) { clearInterval(AmtDeskTypeTimer); AmtDeskTypeContent = null; }
  9692. }, 10);
  9693. } else if (desktop.contype == 4) {
  9694. // RDP
  9695. desktop.m.SendStringUnicode(txt);
  9696. } else {
  9697. // MeshAgent
  9698. if (desktopsettings.remotekeymap !== true) {
  9699. // New unicode typing
  9700. desktop.m.SendStringUnicode(txt);
  9701. } else {
  9702. // Old scan code typing. This is for non-unicode system.
  9703. for (var i in txt) {
  9704. var a = txt.charCodeAt(i), b = ltxt.charCodeAt(i);
  9705. if (((a >= 65) && (a <= 90)) || ((a >= 97) && (a <= 122))) {
  9706. if ((a == b) && (shift == false)) { x.push([desktop.m.KeyAction.DOWN, 16]); shift = true; } // LShift down
  9707. if ((a != b) && (shift == true)) { x.push([desktop.m.KeyAction.UP, 16]); shift = false; } // LShift up
  9708. } else if ((a >= 48) && (a <= 57)) {
  9709. if (shift == true) { x.push([desktop.m.KeyAction.UP, 16]); shift = false; } // Shift up
  9710. } else if (DeskTypeTranslate[a]) {
  9711. if (shift == true) { x.push([desktop.m.KeyAction.UP, 16]); shift = false; } // Shift up
  9712. b = DeskTypeTranslate[a];
  9713. } else if (DeskTypeShiftTranslate[a]) {
  9714. if (shift == false) { x.push([desktop.m.KeyAction.DOWN, 16]); shift = true; } // LShift down
  9715. b = DeskTypeShiftTranslate[a];
  9716. }
  9717. x.push([desktop.m.KeyAction.DOWN, b], [desktop.m.KeyAction.UP, b]);
  9718. }
  9719. if (shift == true) { x.push([desktop.m.KeyAction.UP, 16]); shift = false; } // Shift up
  9720. desktop.m.SendKeyMsgKC(x);
  9721. }
  9722. }
  9723. }
  9724. // Show clipboard dialog
  9725. function showDeskClip() {
  9726. if (xxdialogMode || desktop == null || desktop.State != 3) return;
  9727. Q('DeskClip').blur();
  9728. var x = '';
  9729. if ((features2 & 0x0800) == 0) x += '<input id=dlgClipGet type=button value="' + "Get Clipboard" + '" style=width:160px onclick=showDeskClipGet()>';
  9730. if ((features2 & 0x1000) == 0) x += '<input id=dlgClipSet type=button value="' + "Set Clipboard" + '" style=width:160px onclick=showDeskClipSet()>';
  9731. x += '<div id=dlgClipStatus style="display:inline-block;margin-left:8px" ></div>';
  9732. x += '<textarea id=d2clipText style="width:100%;height:184px;resize:none" maxlength=65535></textarea>';
  9733. x += '<input type=button value="Close" style=width:80px;float:right onclick=dialogclose(0)><div style=height:26px;margin-top:3px><span id=linuxClipWarn style=display:none>' + "Remote clipboard is valid for 60 seconds." + '</span>&nbsp;</div><div></div>';
  9734. setDialogMode(2, "Remote Clipboard", 8, null, x, 'clipboard');
  9735. Q('d2clipText').focus();
  9736. }
  9737. function showDeskClipGet() {
  9738. if (desktop == null || desktop.State != 3) return;
  9739. meshserver.send({ action: 'msg', type: 'getclip', nodeid: currentNode._id, tag: 1 });
  9740. }
  9741. function showDeskClipSet() {
  9742. if (desktop == null || desktop.State != 3) return;
  9743. if (desktop.m.setClipboard) {
  9744. desktop.m.setClipboard(Q('d2clipText').value);
  9745. } else {
  9746. meshserver.send({ action: 'msg', type: 'setclip', nodeid: currentNode._id, data: Q('d2clipText').value });
  9747. QV('linuxClipWarn', currentNode && currentNode.agent && (currentNode.agent.id > 4) && (currentNode.agent.id != 21) && (currentNode.agent.id != 22) && (currentNode.agent.id != 34));
  9748. }
  9749. }
  9750. // Send CTRL-ALT-DEL
  9751. function sendCAD() {
  9752. if (xxdialogMode || desktop == null || desktop.State != 3) return;
  9753. desktop.m.sendcad();
  9754. }
  9755. // Show process dialogs
  9756. function toggleDeskTools() {
  9757. Q('DeskToolsButton').blur();
  9758. if (xxdialogMode) return;
  9759. if (QS('DeskTools').display == 'none') {
  9760. QV('DeskTools', true);
  9761. Q('DeskTools').nodeid = currentNode._id;
  9762. QH('DeskToolsProcesses', '');
  9763. QH('DeskToolsServices', '');
  9764. QV('deskToolsTopTabService', false);
  9765. changeDeskToolTab(0)
  9766. refreshDeskTools(0);
  9767. refreshDeskTools(1);
  9768. } else {
  9769. QV('DeskTools', false);
  9770. }
  9771. }
  9772. var deskToolTabSelection = 0;
  9773. function changeDeskToolTab(tabnum) {
  9774. deskToolTabSelection = tabnum;
  9775. QV('DeskToolsProcessTab', tabnum == 0);
  9776. QV('DeskToolsServiceTab', tabnum == 1);
  9777. QS('deskToolsTopTabProcess')['bottom'] = (tabnum == 0) ? '0px' : '3px';
  9778. QS('deskToolsTopTabService')['bottom'] = (tabnum == 1) ? '0px' : '3px';
  9779. QS('deskToolsTopTabProcess')['color'] = (tabnum == 0) ? 'black' : 'gray';
  9780. QS('deskToolsTopTabService')['color'] = (tabnum == 1) ? 'black' : 'gray';
  9781. }
  9782. // Refresh all of the desktop tool panels
  9783. function refreshDeskTools(x) {
  9784. var sel = (x == null) ? deskToolTabSelection : x;
  9785. QV('DeskToolsRefreshButton', false);
  9786. setTimeout(refreshDeskToolsEx, 500);
  9787. if (sel == 0) meshserver.send({ action: 'msg', type: 'ps', nodeid: currentNode._id });
  9788. if (sel == 1) meshserver.send({ action: 'msg', type: 'services', nodeid: currentNode._id });
  9789. }
  9790. function refreshDeskToolsEx() { QV('DeskToolsRefreshButton', true); }
  9791. var deskTools = { sort: 1, ssort: 1, msg: null, smsg: null, resizing: false };
  9792. Q('DeskTools').addEventListener('mousemove', function (event) {
  9793. var mouseX = event.clientX - Q('DeskTools').getBoundingClientRect().left;
  9794. var borderThickness = 5; // Assuming a 5px border
  9795. if (mouseX < borderThickness) {
  9796. Q('DeskTools').style.cursor = 'col-resize';
  9797. } else {
  9798. Q('DeskTools').style.cursor = 'default';
  9799. }
  9800. });
  9801. Q('DeskTools').addEventListener('mousedown', function (event) {
  9802. if (Q('DeskTools').style.cursor === 'col-resize') {
  9803. deskTools.resizing = true;
  9804. Q('Desk').removeAttribute('onmouseup');
  9805. Q('Desk').removeAttribute('onmousedown');
  9806. Q('Desk').removeAttribute('onmousemove');
  9807. }
  9808. });
  9809. document.addEventListener('mouseup', function () {
  9810. if(deskTools.resizing === true && Q('DeskTools').style.cursor === 'col-resize') {
  9811. deskTools.resizing = false;
  9812. Q('Desk').setAttribute('onmouseup','dmouseup(event)');
  9813. Q('Desk').setAttribute('onmousedown','dmousedown(event)');
  9814. Q('Desk').setAttribute('onmousemove','dmousemove(event)');
  9815. }
  9816. });
  9817. document.addEventListener('mousemove', function (event) {
  9818. if (deskTools.resizing) {
  9819. var newWidth = Q('DeskTools').getBoundingClientRect().right - event.clientX;
  9820. if(newWidth < (Q('DeskParent').clientWidth-10)) Q('DeskTools').style.width = newWidth + 'px';
  9821. }
  9822. });
  9823. function sortProcess(sort) { deskTools.sort = sort; showDeskToolsProcesses(deskTools.msg); }
  9824. function sortService(sort) { deskTools.ssort = sort; showDeskToolsServices(deskTools.smsg); }
  9825. function sortProcessPid(a, b) { if (a.p > b.p) return 1; if (a.p < b.p) return (-1); return sortProcessName(a, b); }
  9826. function sortProcessName(a, b) { if (a.d > b.d) return 1; if (a.d < b.d) return (-1); return 0; }
  9827. function showDeskToolsProcesses(message) {
  9828. deskTools.msg = message;
  9829. if (message == null) { QH('DeskToolsProcesses', ''); return; }
  9830. if (Q('DeskTools').nodeid != message.nodeid) return;
  9831. var p = [], processes = null;
  9832. try { processes = JSON.parse(message.value); } catch (e) { }
  9833. if (processes != null) {
  9834. for (var pid in processes) { p.push( { p:parseInt(pid), c:processes[pid].cmd, d:processes[pid].cmd.toLowerCase(), u: processes[pid].user } ); }
  9835. if (deskTools.sort == 0) { p.sort(sortProcessPid); } else if (deskTools.sort == 1) { p.sort(sortProcessName); }
  9836. var x = '';
  9837. for (var i in p) {
  9838. if (p[i].p != 0) {
  9839. var c = p[i].c;
  9840. x += '<div onclick=showProcessDetails(' + p[i].p + ') class=deskToolsBar>';
  9841. x += '<div style=width:50px;float:left;text-align:right;padding-right:5px>' + EscapeHtml(p[i].p) + '</div>';
  9842. x += '<a href=# style=float:right;padding-right:5px;cursor:pointer title="' + "Stop process" + '" onclick=\'return stopProcess(' + EscapeHtml(p[i].p) + ',"' + EscapeHtml(p[i].c) + '")\'><img width=10 height=10 src="images/trash.png"></a>';
  9843. x += '<div style=float:right;padding-right:5px>' + (p[i].u ? EscapeHtml(p[i].u) : '') + '</div>';
  9844. x += '<div style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:5px" title="' + EscapeHtml(c) + '">' + c + '</div>';
  9845. x += '</div>';
  9846. }
  9847. }
  9848. QH('DeskToolsProcesses', x);
  9849. }
  9850. }
  9851. function showProcessDetails(pid) {
  9852. if (xxdialogMode) return;
  9853. setDialogMode(2, format("Process Details, #{0}", pid), 1, null, "Requesting Process Details...", 'ps|' + currentNode._id + '|' + pid);
  9854. meshserver.send({ action: 'msg', type: 'psinfo', nodeid: currentNode._id, pid: pid });
  9855. }
  9856. function showDeskToolsServices(message) {
  9857. deskTools.smsg = message;
  9858. if (message == null) { QH('DeskToolsProcesses', ''); return; }
  9859. if (Q('DeskTools').nodeid != message.nodeid) return;
  9860. QV('deskToolsTopTabService', true);
  9861. var s = [], services = null;
  9862. try { services = JSON.parse(message.value); } catch (e) { }
  9863. deskTools.services = services;
  9864. if (services != null) {
  9865. for (var i in services) {
  9866. if (services[i].status) {
  9867. // Windows
  9868. s.push({ p: capitalizeFirstLetter(services[i].status.state.toLowerCase()), d: services[i].displayName, i: i });
  9869. } else if (services[i].serviceType) {
  9870. // Linux (TODO: This the service status is not displayed, not sure start/stop/restart will work).
  9871. s.push({ p: services[i].serviceType, d: services[i].name, i: i });
  9872. }
  9873. }
  9874. if (deskTools.ssort == 0) { s.sort(sortProcessPid); } else if (deskTools.ssort == 1) { s.sort(sortProcessName); }
  9875. var x = '';
  9876. for (var i in s) {
  9877. if (s[i].p != 0) {
  9878. var c = s[i].d, ss = s[i].p;
  9879. if (ss == 'Stopped') { ss = "Stopped"; } // TODO: Add all other states for translation
  9880. else if (ss == 'Running') { ss = "Running"; }
  9881. x += '<div onclick=showServiceWaitDialog(' + s[i].i + ') class=deskToolsBar>';
  9882. x += '<div style=width:70px;float:left;padding-right:5px>' + EscapeHtml(ss) + '</div>';
  9883. x += '<div style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis" title="' + c + '">' + EscapeHtml(c) + '</div>';
  9884. x += '</div>';
  9885. }
  9886. }
  9887. QH('DeskToolsServices', x);
  9888. }
  9889. }
  9890. function showServiceDetailsDialog(data) {
  9891. if (xxdialogMode && (xxdialogTag.indexOf('service_') != -1)) {
  9892. var index = xxdialogTag.replace('service_','')
  9893. // var org_service = deskTools.services[index];
  9894. var service = data.value ? JSON.parse(data.value) : deskTools.services[index];
  9895. if(data.value) service.displayName = deskTools.services[index].displayName;
  9896. if (service != null) {
  9897. var x = '';
  9898. if (service.name) { x += addHtmlValue("Name", service.name); }
  9899. if (service.displayName) { x += addHtmlValue("Display name", service.displayName); }
  9900. if (service.status) {
  9901. var ss = capitalizeFirstLetter(service.status.state.toLowerCase());
  9902. if (ss == 'Stopped') { ss = "Stopped"; } // TODO: Add all other states for translation
  9903. else if (ss == 'Running') { ss = "Running"; }
  9904. if (service.status.state) { x += addHtmlValue("State", ss); }
  9905. if (service.status.pid) { x += addHtmlValue("PID", service.status.pid); }
  9906. var serviceTypes = [];
  9907. if (service.status.isFileSystemDriver === true) { serviceTypes.push("FileSystemDriver"); }
  9908. if (service.status.isInteractive === true) { serviceTypes.push("Interactive"); }
  9909. if (service.status.isKernelDriver === true) { serviceTypes.push("KernelDriver"); }
  9910. if (service.status.isOwnProcess === true) { serviceTypes.push("OwnProcess"); }
  9911. if (service.status.isSharedProcess === true) { serviceTypes.push("SharedProcess"); }
  9912. if (serviceTypes.length > 0) { x += addHtmlValue("Type", serviceTypes.join(', ')); }
  9913. }
  9914. if (service.startType) { x += addHtmlValue("Start Type", service.startType); }
  9915. if (service.user) { x += addHtmlValue("User", service.user); }
  9916. if (service.installedBy) { x += addHtmlValue("Installed By", service.installedBy); }
  9917. if (service.installedDate) { x += addHtmlValue("Installed Date", printDateTime(new Date(service.installedDate))); }
  9918. if (service.failureActions) {
  9919. if (service.failureActions.resetPeriod) {
  9920. var abc = ((service.failureActions.resetPeriod < 86400 ? 0 : service.failureActions.resetPeriod) / (24 * 60 * 60));
  9921. x += addHtmlValue("Restart Fail Count After ", abc + ' Day' + (abc != 1 ? 's': '')); }
  9922. if (service.failureActions.actions) {
  9923. if (service.failureActions.actions[0]) { x += addHtmlValue("First Failure",service.failureActions.actions[0].type); }
  9924. if (service.failureActions.actions[1]) { x += addHtmlValue("Second Failure",service.failureActions.actions[1].type); }
  9925. if (service.failureActions.actions[2]) { x += addHtmlValue("Subsequent Failures",service.failureActions.actions[2].type); }
  9926. }
  9927. }
  9928. x += '<br/><div style=float:right;margin-bottom:12px><input type=button value="' + "Close" + '" onclick=showServiceDetailsDialogEx(0,' + index + ')></div><div style=margin-bottom:12px><input type=button value="' + "Start" + '" onclick=showServiceDetailsDialogEx(1,' + index + ')><input type=button value="' + "Stop" + '" onclick=showServiceDetailsDialogEx(2,' + index + ')><input type=button value="' + "Restart" + '" onclick=showServiceDetailsDialogEx(3,' + index + ')></div>';
  9929. setDialogMode(2, "Service Details", 8, null, x, "service_"+index);
  9930. }
  9931. }
  9932. }
  9933. function showServiceWaitDialog(index) {
  9934. if (xxdialogMode) return false;
  9935. var service = deskTools.services[index];
  9936. if (service != null) {
  9937. meshserver.send({ action: 'msg', type: 'service', nodeid: currentNode._id, serviceName: service.name });
  9938. setDialogMode(2, "Service Details", 1, null, "Requesting Service Details...", "service_" + index);
  9939. }
  9940. return false;
  9941. }
  9942. function showServiceDetailsDialogEx(action, index) {
  9943. setDialogMode(0);
  9944. if (action == 0) return;
  9945. var service = deskTools.services[index];
  9946. if (service != null) {
  9947. if (action == 1) { meshserver.send({ action: 'msg', type: 'serviceStart', nodeid: currentNode._id, serviceName: service.name }); }
  9948. if (action == 2) { meshserver.send({ action: 'msg', type: 'serviceStop', nodeid: currentNode._id, serviceName: service.name }); }
  9949. if (action == 3) { meshserver.send({ action: 'msg', type: 'serviceRestart', nodeid: currentNode._id, serviceName: service.name }); }
  9950. setTimeout(function () { refreshDeskTools(1) }, 1000);
  9951. }
  9952. }
  9953. // Toggle mouse and keyboard input
  9954. function toggleKvmControl() { Q('DeskControl').blur(); putstore('DeskControl', (Q('DeskControl').checked?1:0)); QS('DeskControlSpan').color = Q('DeskControl').checked?null:'red'; }
  9955. // Toggle desktop session recording
  9956. function deskRecordSession() {
  9957. if (desktop == null) return;
  9958. if (desktop.m.recordedData == null) {
  9959. // Start recording
  9960. Q('DeskRecordButtonImage').src = 'images/icon-film-red.png';
  9961. desktop.m.StartRecording();
  9962. } else {
  9963. // Stop recording
  9964. Q('DeskRecordButtonImage').src = 'images/icon-film.png';
  9965. var d = new Date(), n = "DesktopSession" + '-' + 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);
  9966. saveAs(data2blob(desktop.m.StopRecording().join('')), n + '.mcrec');
  9967. }
  9968. }
  9969. // Save the desktop image to file
  9970. function deskSaveImage() {
  9971. if (xxdialogMode || desktop == null || desktop.State != 3) return;
  9972. 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);
  9973. Q('Desk')['toBlob'](function (blob) { saveAs(blob, n + '.png'); });
  9974. }
  9975. function deskDisplayInfo(sender, displays, selDisplay) {
  9976. var displayCount = 0, displaySelector = '';
  9977. for (var i in displays) {
  9978. displayCount++;
  9979. var str = displays[i], allDisplays = 1;
  9980. if (str == 'All Displays') { str = "All Displays"; allDisplays = 2; } // Language translation
  9981. if (str.startsWith('Display ')) { str = format("Display {0}", str.substring(8)); } // Language translation
  9982. displaySelector += '<img id=DeskMonitorSelectionX' + i + ' class="' + ((selDisplay == i) ? '' : ' gray') + '" src=\'images/icon-monitor' + allDisplays + '.png\' title="' + EscapeHtml(str) + '" onclick=deskSetDisplay(' + i + ') height=16 width=16 style=padding-top:2px;margin-left:2px />';
  9983. if ((deskPreferedStickyDisplay == i) && (selDisplay != deskPreferedStickyDisplay)) { desktop.m.SetDisplay(i); }
  9984. deskPreferedStickyDisplay = -1;
  9985. }
  9986. QH('DeskMonitorSelectionSpan', displaySelector);
  9987. QV('DeskMonitorSelectionSpan', displayCount > 1);
  9988. }
  9989. function deskGetDisplayNumbers(e) { desktop.m.GetDisplayNumbers(); }
  9990. var deskPreferedStickyDisplay = -1;
  9991. function deskSetDisplay(v) { desktop.m.SetDisplay(deskPreferedStickyDisplay = parseInt(v)); }
  9992. // Double click detection. This is important for macOS.
  9993. var dblClickDetectArgs = { t:0, x:0, y:0 };
  9994. function dblClickDetect(e) {
  9995. if (e.buttons != 1) return;
  9996. var t = Date.now();
  9997. if (((t - dblClickDetectArgs.t) < 250) && (Math.abs(e.clientX - dblClickDetectArgs.x) < 2) && (Math.abs(e.clientY - dblClickDetectArgs.y) < 2)) {
  9998. if (!xxdialogMode && desktop != null && Q('DeskControl').checked) { if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousedblclick(e); desktop.m.sendKeepAlive(); } else { desktop.m.mousedblclick(e); } }
  9999. }
  10000. dblClickDetectArgs.t = t;
  10001. dblClickDetectArgs.x = e.clientX;
  10002. dblClickDetectArgs.y = e.clientY;
  10003. }
  10004. function dmousedown(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && Q('DeskControl').checked) { if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousedown(e); desktop.m.sendKeepAlive(); } else { desktop.m.mousedown(e); } } dblClickDetect(e); }
  10005. function dmouseup(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && Q('DeskControl').checked) if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mouseup(e); desktop.m.sendKeepAlive(); } else { desktop.m.mouseup(e); } }
  10006. function dmousemove(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && Q('DeskControl').checked) { Q('Desk').style.cursor = ''; if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousemove(e); desktop.m.sendKeepAlive(); } else { desktop.m.mousemove(e); } } else if (!xxdialogMode && desktop != null && !Q('DeskControl').checked) { Q('Desk').style.cursor = 'not-allowed'; } }
  10007. function dmousewheel(e) { setSessionActivity(); e.addx = Q('DeskParent').scrollLeft; e.addy = Q('DeskParent').scrollTop; if (!xxdialogMode && desktop != null && Q('DeskControl').checked) { if ((webRtcDesktop != null) && (webRtcDesktop.softdesktop != null)) { webRtcDesktop.softdesktop.m.mousewheel(e); desktop.m.sendKeepAlive(); } else { if (desktop.m.mousewheel) { desktop.m.mousewheel(e); } } haltEvent(e); return true; } return false; }
  10008. function drotate(x) { if (!xxdialogMode && desktop != null) { desktop.m.setRotation(desktop.m.rotation + x); deskAdjust(); deskAdjust(); } }
  10009. function stopProcess(id, name) { setDialogMode(2, "Process Control", 3, stopProcessEx, format("Stop process #{0} \"{1}\"?", id, name), (id + '|' + name)); return false; }
  10010. function stopProcessEx(buttons, tag) { meshserver.send({ action: 'msg', type: 'pskill', nodeid: currentNode._id, value: tag }); setTimeout(refreshDeskTools, 300); }
  10011. //
  10012. // TERMINAL
  10013. //
  10014. var terminalNode;
  10015. function setupTerminal() {
  10016. // Setup the terminal
  10017. if ((terminalNode != currentNode) && (terminal != null)) {
  10018. if (terminalNode._id != currentNode._id) {
  10019. terminal.Stop(); terminalNode = null; terminal = null;
  10020. }
  10021. }
  10022. terminalNode = currentNode;
  10023. updateTerminalButtons();
  10024. }
  10025. // Show and enable the right buttons
  10026. function updateTerminalButtons() {
  10027. var termState = ((terminal != null) && (terminal.state != 0));
  10028. // If we are looking at a local non-windows device, enable terminal capability.
  10029. if ((terminalNode.mtype == 3) && (terminalNode.agent != null) && (terminalNode.agent.id > 4) && (features2 & 0x00000200)) { terminalNode.agent.caps = 6; } // 1 = Terminal, 2 = Desktop, 4 = files
  10030. // Show the right buttons
  10031. QV('disconnectbutton2span', (termState == true));
  10032. QV('connectbutton2span', (termState == false) && (terminalNode.agent != null) && (terminalNode.agent.caps & 2) && (terminalNode.mtype != 3));
  10033. QV('connectbutton2sspan', (features2 & 0x00000200) && (termState == false) && (terminalNode.agent != null) && (terminalNode.agent.caps & 2) && (terminalNode.agent.id != 3) && ((features2 & 0x1000000) == 0));
  10034. if (terminalNode.mtype == 1) {
  10035. QV('connectbutton2hspan', (termState == false) && (terminalNode.intelamt != null) && (terminalNode.intelamt.state == 2));
  10036. QV('terminalSizeDropDown', (termState == false) && (terminalNode.intelamt != null) && (terminalNode.intelamt.state == 2));
  10037. } else {
  10038. QV('connectbutton2hspan', (termState == false) && (terminalNode.intelamt != null) && (terminalNode.intelamt.state == 2) && (terminalNode.intelamt.ver != null));
  10039. QV('terminalSizeDropDown', (termState == false) && (terminalNode.intelamt != null) && (terminalNode.intelamt.state == 2) && (terminalNode.intelamt.ver != null));
  10040. }
  10041. // Enable action button if mesh type is not "local devices"
  10042. QV('termActionsBtn', terminalNode.mtype != 3);
  10043. if (((termState == true) && (terminal.contype != 3)) || (terminalNode.agent == null) || (terminalNode.agent.id == 3) || (terminalNode.agent.id == 4)) {
  10044. QH('terminalCustomUpperRight', '');
  10045. } else {
  10046. QH('terminalCustomUpperRight', '<a style=cursor:pointer onclick=cmsshportaction(1,event)>' + format("SSH Port {0}", (terminalNode.sshport?terminalNode.sshport:22)) + '</a>');
  10047. }
  10048. // Enable buttons
  10049. var online = ((terminalNode.conn & 1) != 0) || (terminalNode.mtype == 3); // If Agent (1) connected, enable Terminal
  10050. QE('connectbutton2', online);
  10051. QE('connectbutton2s', online);
  10052. var hwonline = ((terminalNode.conn & 6) != 0); // If CIRA (2) or AMT (4) connected, enable hardware terminal
  10053. QE('connectbutton2h', hwonline);
  10054. // Key buttons
  10055. QE('ctrlcbutton', termState);
  10056. QE('ctrlxbutton', termState);
  10057. QE('escbutton', termState);
  10058. QE('bsbutton', termState);
  10059. QE('pastebutton', termState);
  10060. QE('specialkeylist', termState);
  10061. QE('specialkeylistinput', termState);
  10062. // Terminal settings
  10063. QV('terminalSettingsButtons', (terminal) && (terminal.contype == 2));
  10064. if (terminal) {
  10065. Q('id_ttypebutton').value = terminalEmulations[terminal.m.terminalEmulation];
  10066. Q('id_tfxkeysbutton').value = fxEmulations[terminal.m.fxEmulation];
  10067. Q('id_tcrbutton').value = (terminal.m.lineFeed == '\r\n')?"CR+LF":"LF";
  10068. }
  10069. // Display extra buttons on legacy terminal
  10070. var xtermActive = !((args.xterm === 0) || ((terminal != null) && (xterm == null)));
  10071. QV('termarea3xdiv', xtermActive);
  10072. QV('Term', !xtermActive);
  10073. QV('bsbutton', !xtermActive);
  10074. QV('pastebutton', !xtermActive);
  10075. QV('devListToolbarViewIcons2', xtermActive);
  10076. QE('termSizeList', terminal == null);
  10077. }
  10078. // Called when the terminal state changes
  10079. function onTerminalStateChange(xterminal, state) {
  10080. if (terminal != xterminal) return;
  10081. var xstate = state;
  10082. if ((xstate == 3) && (xterminal.contype == 2)) { xstate++; }
  10083. var str = StatusStrs[xstate];
  10084. if (xstate == 3) {
  10085. if (xterminal.contype == 3) { str += ", SSH"; }
  10086. if (xterminal.webRtcActive == true) { str += ", WebRTC"; }
  10087. }
  10088. QH('termstatus', str);
  10089. switch (state) {
  10090. case 0:
  10091. // Disconnected, clear the terminal
  10092. QH('termtitle', '');
  10093. QV('termRecordIcon', false);
  10094. if (xterm == null) {
  10095. try { xterminal.m.TermResetScreen(); xterminal.m.TermDraw(); } catch (ex) { }
  10096. } else {
  10097. xterm.dispose();
  10098. xterm = xtermfit = null;
  10099. }
  10100. if (xterminal != null) { xterminal.Stop(); terminal = null; }
  10101. break;
  10102. case 3:
  10103. if (xterminal && (xterminal.serverIsRecording == true)) { QV('termRecordIcon', true); }
  10104. xterminal.startTime = new Date();
  10105. if (updateSessionTimer == null) { updateSessionTimer = setInterval(updateSessionTime, 1000); }
  10106. if (xterm != null) { xterm.focus(); }
  10107. break;
  10108. default:
  10109. //console.log('Unhandled onTerminalStateChange state', state);
  10110. break;
  10111. }
  10112. updateTerminalButtons();
  10113. }
  10114. // DEBUG
  10115. var autoConnectTerminalTimer = null;
  10116. function autoConnectTerminal(e) { if (autoConnectTerminalTimer == null) { autoConnectTerminalTimer = setInterval(connectTerminal, 100); } else { clearInterval(autoConnectTerminalTimer); autoConnectTerminalTimer = null; } }
  10117. // Handles a tunnel to a remote shell
  10118. function CreateRemoteTunnel(onTunnelUpdate, options) {
  10119. var obj = { protocol: 1 };
  10120. if ((options != null) && (typeof options.protocol == 'number')) { obj.protocol = options.protocol; }
  10121. obj.onTunnelUpdate = onTunnelUpdate;
  10122. obj.xxStateChange = function (state) { }
  10123. obj.ProcessBinaryData = function (data) { obj.onTunnelUpdate(data); }
  10124. obj.ProcessData = function (data) { obj.onTunnelUpdate(data); }
  10125. obj.terminalEmulation = 1;
  10126. obj.fxEmulation = 0;
  10127. obj.lineFeed = '\r\n';
  10128. return obj;
  10129. }
  10130. function tunnelUpdate(data) {
  10131. if (xterm != null) {
  10132. if (xterm.writeUtf8) {
  10133. if (typeof data == 'string') { xterm.writeUtf8(data); } else { xterm.writeUtf8(new Uint8Array(data)); }
  10134. } else {
  10135. if (typeof data == 'string') { xterm.write(data); } else { xterm.write(new Uint8Array(data)); }
  10136. }
  10137. }
  10138. }
  10139. function sshTunnelAuthDialog(j, func) {
  10140. var x = '';
  10141. if (j.askkeypass) {
  10142. x += addHtmlValue("Authentication", '<select id=dp2authmethod style=width:230px onchange=sshAuthUpdate(event)><option value=3 selected>' + "Stored Key" + '</option><option value=1>' + "Username & Password" + '</option><option value=2>' + "Username and Key" + '</option></select>');
  10143. } else {
  10144. x += addHtmlValue("Authentication", '<select id=dp2authmethod style=width:230px onchange=sshAuthUpdate(event)><option value=1 selected>' + "Username & Password" + '</option><option value=2>' + "Username and Key" + '</option></select>');
  10145. }
  10146. x += '<div id=d2userauth style=display:none>';
  10147. x += addHtmlValue("Username", '<input id=dp2user style=width:230px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
  10148. x += '</div>';
  10149. x += '<div id=d2passauth style=display:none>';
  10150. x += addHtmlValue("Password", '<input type=password id=dp2pass style=width:230px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
  10151. if ((features2 & 0x00400000) == 0) { x += addHtmlValue('', '<label><input id=dp2keep type=checkbox>' + "Remember credentials" + '</label>'); }
  10152. x += '</div><div id=d2keyauth style=display:none>';
  10153. x += addHtmlValue("Key File", '<input type=file id=dp2key style=width:230px maxlength=64 autocomplete=off onchange=sshAuthUpdate(event) />' + '<div id=d2badkey style=font-size:x-small>' + "Key file must be in OpenSSH format." + '</div>');
  10154. x += addHtmlValue("Key Password", '<input type=password id=dp2keypass style=width:230px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
  10155. if ((features2 & 0x00400000) == 0) {
  10156. x += addHtmlValue('', '<label><input id=dp2keep1 type=checkbox onchange=sshAuthUpdate(event)>' + "Remember user & key" + '</label>');
  10157. x += addHtmlValue('', '<label><input id=dp2keep2 type=checkbox>' + "Remember password" + '</label>');
  10158. }
  10159. x += '</div>';
  10160. if (j.askkeypass) {
  10161. x += '<div id=d2keyauth2 style=display:none>';
  10162. x += addHtmlValue("Password", '<input type=password id=dp2keypass2 style=width:230px maxlength=64 autocomplete=off onkeyup=sshAuthUpdate(event) />');
  10163. x += '</div>';
  10164. }
  10165. setDialogMode(2, "Authentication", 11, func, x, 'ssh');
  10166. Q('dp2user').focus();
  10167. sshAuthUpdate();
  10168. setTimeout(sshAuthUpdate, 50);
  10169. }
  10170. function sshTunnelUpdate(data) {
  10171. if (typeof data == 'string') {
  10172. if (data[0] == '{') {
  10173. var j = JSON.parse(data);
  10174. switch (j.action) {
  10175. case 'sshauth': {
  10176. sshTunnelAuthDialog(j, sshConnectEx);
  10177. break;
  10178. }
  10179. case 'sshautoauth': {
  10180. terminal.socket.send(JSON.stringify({ action: 'sshautoauth', cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
  10181. break;
  10182. }
  10183. case 'connectionerror': { p12setConsoleMsg("Connection Error", 5000); break; }
  10184. case 'autherror': { p12setConsoleMsg("Authentication Error", 5000); break; }
  10185. case 'sessionerror': { p12setConsoleMsg("Session expired", 5000); break; }
  10186. case 'sessiontimeout': { p12setConsoleMsg("Session timeout", 5000); break; }
  10187. }
  10188. } else if (data[0] == '~') {
  10189. if (xterm.writeUtf8) { xterm.writeUtf8(data.substring(1)); } else { xterm.write(data.substring(1)); }
  10190. }
  10191. }
  10192. }
  10193. function sshAuthUpdate(e) {
  10194. QV('d2userauth', Q('dp2authmethod').value != 3);
  10195. QV('d2passauth', Q('dp2authmethod').value == 1);
  10196. QV('d2keyauth', Q('dp2authmethod').value == 2);
  10197. QV('d2keyauth2', Q('dp2authmethod').value == 3);
  10198. if (Q('dp2authmethod').value == 1) {
  10199. QE('idx_dlgOkButton', (Q('dp2user').value.length > 0) && (Q('dp2pass').value.length > 0));
  10200. } else if (Q('dp2authmethod').value == 3) {
  10201. QE('idx_dlgOkButton', Q('dp2keypass2').value.length > 0);
  10202. } else {
  10203. QE('idx_dlgOkButton', false);
  10204. if ((features2 & 0x00400000) == 0) { QE('dp2keep2', Q('dp2keep1').checked); }
  10205. var ok = (Q('dp2user').value.length > 0) && (Q('dp2key').files != null) && (Q('dp2key').files.length == 1) && (Q('dp2key').files[0].size < 8000);
  10206. if (ok == true) {
  10207. var reader = new FileReader();
  10208. reader.onload = function (e) {
  10209. var validkey =
  10210. ((e.target.result.indexOf('-----BEGIN OPENSSH PRIVATE KEY-----') >= 0) && (e.target.result.indexOf('-----END OPENSSH PRIVATE KEY-----') >= 0)) ||
  10211. ((e.target.result.indexOf('-----BEGIN RSA PRIVATE KEY-----') >= 0) && (e.target.result.indexOf('-----END RSA PRIVATE KEY-----') >= 0));
  10212. QE('idx_dlgOkButton', validkey);
  10213. QS('d2badkey')['color'] = validkey?'#000':'#F00';
  10214. }
  10215. reader.readAsText(Q('dp2key').files[0]);
  10216. }
  10217. }
  10218. // When the enter key is pressed, move to the next field
  10219. if (e && (e.keyCode == 13) && (e.target) && (Q('dp2authmethod').value == 1)) {
  10220. if (e.target.id == 'dp2user') { Q('dp2pass').focus(); }
  10221. if (e.target.id == 'dp2pass') { dialogclose(1); }
  10222. }
  10223. }
  10224. function sshConnectEx(b) {
  10225. if (b == 0) {
  10226. if (terminal != null) { connectTerminal(); } // Disconnect
  10227. } else {
  10228. var keep = 0;
  10229. if (Q('dp2authmethod').value == 1) {
  10230. if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep').checked ? 1 : 0); }
  10231. 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 }));
  10232. } else if (Q('dp2authmethod').value == 3) {
  10233. 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 }));
  10234. } else {
  10235. 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
  10236. var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value;
  10237. 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 })); }
  10238. reader.readAsText(Q('dp2key').files[0]);
  10239. }
  10240. }
  10241. }
  10242. // Send the new terminal size to the agent
  10243. function xTermSendResize() {
  10244. xtermResizeTimer = null;
  10245. if ((xterm != null) && (terminal != null) && (terminal.sendCtrlMsg != null)) {
  10246. if (terminal.urlname == 'sshterminalrelay.ashx') {
  10247. terminal.socket.send(JSON.stringify({ action: 'resize', cols: xterm.cols, rows: xterm.rows, width: Q('termarea3xdiv').offsetWidth, height: Q('termarea3xdiv').offsetHeight }));
  10248. } else {
  10249. terminal.sendCtrlMsg(JSON.stringify({ ctrlChannel: '102938', type: 'termsize', cols: xterm.cols, rows: xterm.rows }));
  10250. }
  10251. }
  10252. }
  10253. // contype: 1 = Agent, 2 = AMT, 3 = SSH
  10254. function connectTerminal(e, contype, options) {
  10255. p12clearConsoleMsg();
  10256. if (!terminal) {
  10257. if (contype == 2) {
  10258. // Setup the Intel AMT terminal
  10259. if ((terminalNode.intelamt.user == null) || (terminalNode.intelamt.user == '')) { editDeviceAmtSettings(terminalNode._id, connectTerminal, 2); return; }
  10260. var termoptions = {};
  10261. if (Q('termSizeList').value == 1) { termoptions.cols = 80; termoptions.rows = 25; }
  10262. else if (Q('termSizeList').value == 2) { termoptions.cols = 100; termoptions.rows = 30; }
  10263. // Get out of full screen
  10264. if (fullscreen) { deskToggleFull(); }
  10265. // Setup legacy terminal
  10266. QV('termarea3xdiv', false);
  10267. QV('Term', true);
  10268. terminal = CreateAmtRedirect(CreateAmtRemoteTerminal('Term', termoptions), authCookie);
  10269. terminal.debugmode = debugmode;
  10270. terminal.m.debugmode = debugmode;
  10271. terminal.m.onTitleChange = function (sender, title) { QH('termtitle', ' - ' + EscapeHtml(title)); }
  10272. terminal.onStateChanged = onTerminalStateChange;
  10273. terminal.Start(terminalNode._id, 16994, '*', '*', 0);
  10274. terminal.contype = 2;
  10275. Q('id_ttypebutton').value = terminalEmulations[terminal.m.terminalEmulation];
  10276. } else {
  10277. // Terminal setup
  10278. var termoptions = { protocol: ((options != null) && (typeof options.protocol == 'number'))?options.protocol:1 };
  10279. if (options && options.requireLogin) { termoptions.requireLogin = true; }
  10280. if (options && options.consent) { termoptions.consent = options.consent; }
  10281. if ([1, 2, 3, 4, 21, 22].indexOf(currentNode.agent.id) == -1) {
  10282. if (Q('termSizeList').value == 1) { termoptions.cols = 80; termoptions.rows = 25; termoptions.xterm = true; }
  10283. else if (Q('termSizeList').value == 2) { termoptions.cols = 100; termoptions.rows = 30; termoptions.xterm = true; }
  10284. else if (Q('termSizeList').value == 3) {
  10285. // TODO: Try to improve terminal auto-size.
  10286. termoptions.cols = Math.floor((Q('column_l').clientWidth - 60) / 10);
  10287. termoptions.rows = Math.floor((Q('column_l').clientHeight - 120) / 20);
  10288. termoptions.xterm = true;
  10289. }
  10290. }
  10291. // If shift is pressed
  10292. if ((e && (e.shiftKey == true))) {
  10293. if (currentNode.agent.id > 4) {
  10294. if (termoptions.protocol == 1) { termoptions.protocol = 7; } // Switch to user shell
  10295. } else {
  10296. if (termoptions.protocol == 1) { termoptions.protocol = 6; } // Switch to Powershell
  10297. }
  10298. }
  10299. // If the server requires a shell type
  10300. if ((serverinfo.linuxshell) != null && (currentNode.agent.id > 4)) {
  10301. if (serverinfo.linuxshell == 'root') { termoptions.protocol = 1; delete termoptions.requireLogin; }
  10302. if (serverinfo.linuxshell == 'user') { termoptions.protocol = 8; delete termoptions.requireLogin; }
  10303. if (serverinfo.linuxshell == 'login') { termoptions.protocol = 1; termoptions.requireLogin = true; }
  10304. }
  10305. if (args.xterm !== 0) {
  10306. // Setup a mesh agent xterm terminal
  10307. QV('termarea3xdiv', true);
  10308. QV('Term', false);
  10309. // Setup the terminal with auto-fit
  10310. if (xterm != null) { xterm.dispose(); }
  10311. xtermfit = new FitAddon.FitAddon();
  10312. xterm = new Terminal();
  10313. if (xtermfit) { xterm.loadAddon(xtermfit); }
  10314. xterm.open(Q('termarea3xdiv')); // termarea3x
  10315. xterm.onData(function (data) { if (terminal != null) { if (terminal.urlname == 'sshterminalrelay.ashx') { terminal.socket.send('~' + data); } else { terminal.sendText(data); } } })
  10316. if (xtermfit) { xtermfit.fit(); }
  10317. xterm.onTitleChange(function (title) { QH('termtitle', ' - ' + EscapeHtml(title)); });
  10318. xterm.onResize(function (size) {
  10319. // Despam resize
  10320. if (xtermResizeTimer) clearTimeout(xtermResizeTimer);
  10321. xtermResizeTimer = setTimeout(xTermSendResize, 200);
  10322. });
  10323. // Setup a terminal tunnel to the agent
  10324. termoptions.cols = xterm.cols;
  10325. termoptions.rows = xterm.rows;
  10326. terminal = CreateAgentRedirect(meshserver, CreateRemoteTunnel((contype == 3)? sshTunnelUpdate : tunnelUpdate, termoptions), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
  10327. if (contype == 3) { terminal.urlname = 'sshterminalrelay.ashx'; } // If this is a SSH session, change the URL to the SSH application relay.
  10328. terminal.debugmode = debugmode;
  10329. terminal.m.debugmode = debugmode;
  10330. terminal.options = termoptions;
  10331. if (termoptions.requireLogin) { terminal.options.requireLogin = true; }
  10332. terminal.Start(terminalNode._id);
  10333. terminal.onStateChanged = onTerminalStateChange;
  10334. terminal.contype = contype;
  10335. terminal.attemptWebRTC = false; // Never do WebRTC on terminal, because of a race condition we can't do it.
  10336. terminal.onConsoleMessageChange = function () { p12setConsoleMsg(terminal.consoleMessage ? formatAgentConsoleMessage(terminal.consoleMessage, terminal.consoleMessageId, terminal.consoleMessageArgs) : null, terminal.consoleMessageTimeout); }
  10337. } else {
  10338. QV('termarea3xdiv', false);
  10339. QV('Term', true);
  10340. // Setup a mesh agent legacy terminal
  10341. terminal = CreateAgentRedirect(meshserver, CreateAmtRemoteTerminal('Term', termoptions), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
  10342. terminal.options = termoptions;
  10343. terminal.debugmode = debugmode;
  10344. terminal.m.debugmode = debugmode;
  10345. terminal.m.onTitleChange = function (sender, title) { QH('termtitle', ' - ' + EscapeHtml(title)); }
  10346. terminal.m.lineFeed = ([1, 2, 3, 4, 21, 22].indexOf(currentNode.agent.id) >= 0) ? '\r\n' : '\r'; // On windows, send \r\n, on Linux only \r
  10347. terminal.attemptWebRTC = false; // Never do WebRTC on terminal, because of a race condition we can't do it.
  10348. terminal.onStateChanged = onTerminalStateChange;
  10349. terminal.onConsoleMessageChange = function () { p12setConsoleMsg(terminal.consoleMessage ? formatAgentConsoleMessage(terminal.consoleMessage, terminal.consoleMessageId, terminal.consoleMessageArgs) : null, terminal.consoleMessageTimeout); }
  10350. terminal.Start(terminalNode._id);
  10351. terminal.contype = contype;
  10352. terminal.m.terminalEmulation = 0;
  10353. terminal.m.fxEmulation = 0;
  10354. Q('id_ttypebutton').value = terminalEmulations[0];
  10355. }
  10356. }
  10357. } else {
  10358. //QH('Term', '');
  10359. if (xxdialogTag == 'ssh') { setDialogMode(0); }
  10360. terminal.Stop();
  10361. terminal = null;
  10362. if (fullscreen) { deskToggleFull(); }
  10363. }
  10364. Q('connectbutton2').blur(); // Deselect the connect button so the button does not get key presses.
  10365. }
  10366. var terminalEmulations = ["UTF8 Terminal", "Extended ASCII", "Intel ASCII"];
  10367. function termToggleType() {
  10368. if (!terminal || xxdialogMode) return;
  10369. terminal.m.terminalEmulation = (terminal.m.terminalEmulation + 1) % 3;
  10370. Q('id_ttypebutton').value = terminalEmulations[terminal.m.terminalEmulation];
  10371. Q('id_ttypebutton').blur(); // Deselect the connect button so the button does not get key presses.
  10372. }
  10373. var fxEmulations = ["Intel (F10 = ESC+[OM)", "Alternate (F10 = ESC+0)", "VT100+ (F10 = ESC+[OY)"];
  10374. function termToggleFx() {
  10375. if (!terminal || xxdialogMode) return;
  10376. terminal.m.fxEmulation = (terminal.m.fxEmulation + 1) % 3;
  10377. Q('id_tfxkeysbutton').value = fxEmulations[terminal.m.fxEmulation];
  10378. Q('id_tfxkeysbutton').blur(); // Deselect the connect button so the button does not get key presses.
  10379. }
  10380. function termToggleCr() {
  10381. if (!terminal || xxdialogMode) return;
  10382. if (terminal.m.lineFeed == '\n') { terminal.m.lineFeed = '\r\n'; } else { terminal.m.lineFeed = '\n'; }
  10383. Q('id_tcrbutton').value = (terminal.m.lineFeed == '\r\n') ? "CR+LF" : "LF";
  10384. }
  10385. function termSendKey(key, id) {
  10386. if (!terminal || xxdialogMode) return;
  10387. if (xterm != null) {
  10388. if (terminal.urlname == 'sshterminalrelay.ashx') {
  10389. // SSH
  10390. terminal.socket.send('~' + String.fromCharCode(key));
  10391. } else if (terminal.sendText) {
  10392. // MeshAgent
  10393. terminal.sendText(String.fromCharCode(key));
  10394. } else {
  10395. // CIRA
  10396. terminal.send(String.fromCharCode(key));
  10397. }
  10398. xterm.focus();
  10399. } else if (terminal != null) {
  10400. terminal.m.TermSendKey(key);
  10401. Q(id).blur(); // Deselect the connect button so the button does not get key presses.
  10402. }
  10403. }
  10404. function showTermPasteDialog() {
  10405. if (!terminal || xxdialogMode) return;
  10406. Q('pastebutton').blur();
  10407. setDialogMode(2, "Paste", 3, showTermPasteDialogEx, '<textarea id=d2pasteText style="width:100%;height:184px;resize:none"></textarea>');
  10408. Q('d2pasteText').focus();
  10409. }
  10410. function showTermPasteDialogEx() {
  10411. if (!terminal) return;
  10412. terminal.m.TermSendKeys(Q('d2pasteText').value);
  10413. }
  10414. // Send special key
  10415. function sendSpecialKey() {
  10416. if (xterm != null) {
  10417. if (terminal.urlname == 'sshterminalrelay.ashx') {
  10418. // SSH
  10419. terminal.socket.send('~' + String.fromCharCode(Q('specialkeylist').value));
  10420. } else {
  10421. // Agent terminal
  10422. terminal.sendText(String.fromCharCode(Q('specialkeylist').value));
  10423. }
  10424. xterm.focus();
  10425. } else if (terminal != null) {
  10426. // Legacy terminal
  10427. terminal.m.TermSendKey(Q('specialkeylist').value);
  10428. Q('specialkeylist').blur();
  10429. Q('specialkeylistinput').blur();
  10430. }
  10431. }
  10432. //
  10433. // FILES
  10434. //
  10435. var filesNode;
  10436. function setupFiles() {
  10437. // Setup the files tab
  10438. if ((filesNode != currentNode) && (files != null)) {
  10439. if (filesNode._id != currentNode._id) {
  10440. files.Stop(); filesNode = null; files = null;
  10441. }
  10442. }
  10443. var samenode = (filesNode == currentNode);
  10444. filesNode = currentNode;
  10445. var online = ((filesNode.conn & 1) != 0) || (filesNode.mtype == 3); // If Agent (1) connected, enable Terminal
  10446. QE('p13Connect', online);
  10447. QE('p13Connects', online);
  10448. QV('p13Connect', (files == null) && (filesNode.mtype == 2));
  10449. QV('p13Connects', ((features2 & 0x200) != 0) && (filesNode.agent != null) && (filesNode.agent.id != 3) && (filesNode.agent.id != 4) && ((features2 & 0x800000) == 0));
  10450. QV('p13Disconnect', files != null);
  10451. p13setActions();
  10452. }
  10453. function onFilesStateChange(xfiles, state) {
  10454. setSessionActivity();
  10455. QV('p13Connect', (state == 0) && (filesNode.mtype == 2));
  10456. QV('p13Connects', (state == 0) && (features2 & 0x200) && (filesNode.agent != null) && (filesNode.agent.id != 3) && (filesNode.agent.id != 4) && ((features2 & 0x800000) == 0));
  10457. QV('p13Disconnect', state != 0);
  10458. var str = StatusStrs[state];
  10459. if (state == 3) {
  10460. if (files.contype == 2) { str += ", SFTP"; }
  10461. if (files.webRtcActive == true) { str += ", WebRTC"; }
  10462. }
  10463. Q('p13Status').textContent = str;
  10464. switch (state) {
  10465. case 0:
  10466. // Disconnected, clear the files
  10467. QH('p13files', '');
  10468. p13filetree = null;
  10469. p13filetreelocation = [];
  10470. QH('p13currentpath', '');
  10471. QE('p13FolderUp', false);
  10472. QV('filesRecordIcon', false);
  10473. p13setActions();
  10474. if (files != null) { files.Stop(); files = null; }
  10475. if (xxdialogTag == 'fileMsgDialog') { setDialogMode(0); }
  10476. if (uploadFile != null) { p13uploadFileTransferDone(); uploadFile = null; }
  10477. break;
  10478. case 3:
  10479. p13filetreelocation = [];
  10480. p13targetpath = '';
  10481. if (files) {
  10482. var filepaths = [];
  10483. try { filepaths = JSON.parse(getstore('_devFilePaths', '[]')); } catch (ex) {}
  10484. for (var i = 0; i < filepaths.length; i++) { if (filepaths[i].n == currentNode._id) { p13targetpath = filepaths[i].p; } }
  10485. p13filetreelocation = p13targetpath.split('/');
  10486. files.sendText({ action: 'ls', reqid: 1, path: p13targetpath });
  10487. if (files.serverIsRecording == true) { QV('filesRecordIcon', true); }
  10488. }
  10489. break;
  10490. default:
  10491. //console.log('Unknown onFilesStateChange state', state);
  10492. break;
  10493. }
  10494. }
  10495. function CreateRemoteFiles(onFileUpdate) {
  10496. var obj = { protocol: 5 };
  10497. obj.onFileUpdate = onFileUpdate;
  10498. obj.xxStateChange = function(state) { }
  10499. obj.ProcessData = function(data) { obj.onFileUpdate(data); }
  10500. return obj;
  10501. }
  10502. // Debug Only
  10503. var autoConnectFilesTimer = null;
  10504. function autoConnectFiles(e) { if (autoConnectFilesTimer == null) { autoConnectFilesTimer = setInterval(connectFiles, 100); } else { clearInterval(autoConnectFilesTimer); autoConnectFilesTimer = null; } }
  10505. // 1 = Agent, 2 = SFTP
  10506. function connectFiles(e, contype, consent) {
  10507. p13clearConsoleMsg();
  10508. if (!files) {
  10509. // Setup a mesh agent files
  10510. files = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotFiles), serverPublicNamePort, authCookie, authRelayCookie, domainUrl);
  10511. if (contype == 2) { files.urlname = 'sshfilesrelay.ashx'; } // If this is a SSH session, change the URL to the SSH application relay.
  10512. files.contype = contype;
  10513. files.options = { consent: consent }
  10514. files.attemptWebRTC = attemptWebRTC;
  10515. files.webrtcconfig = webrtcconfiguration;
  10516. files.onStateChanged = onFilesStateChange;
  10517. files.onConsoleMessageChange = function () {
  10518. if (files.consoleMessage) {
  10519. Q('p13FilesConsoleMsg').innerHTML += formatAgentConsoleMessage(files.consoleMessage, files.consoleMessageId, files.consoleMessageArgs);
  10520. QV('p13FilesConsoleMsg', true);
  10521. if (p13FilesConsoleMsgTimer != null) { clearTimeout(p13FilesConsoleMsgTimer); }
  10522. if (files.consoleMessageTimeout) { p13FilesConsoleMsgTimer = setTimeout(p13clearConsoleMsg, files.consoleMessageTimeout * 1000); }
  10523. } else {
  10524. p13clearConsoleMsg();
  10525. }
  10526. }
  10527. files.Start(filesNode._id);
  10528. } else {
  10529. //QH('Term', '');
  10530. files.Stop();
  10531. files = null;
  10532. }
  10533. p13clipboard = p13clipboardFolder = null;
  10534. p13clipboardCut = 0;
  10535. p13updateClipview();
  10536. }
  10537. var p13filetree = null;
  10538. var p13targetpath = null;
  10539. var p13filetreelocation = [];
  10540. function p13fileOperationDialogEx(b) { if ((b == 0) && (files != null)) { files.sendText({ action: 'cancel' }); } }
  10541. function p13gotFiles(data) {
  10542. if ((data.length > 0) && (data.charCodeAt(0) != 123)) { p13gotDownloadBinaryData(data); return; } // This is ok because 4 first bytes is a control value.
  10543. //console.log('p13gotFiles', data);
  10544. try { data = JSON.parse(decode_utf8(data)); } catch (ex) {
  10545. try { data = JSON.parse(data); } catch (ex) { console.log('Unable to parse: ' + data); return; }
  10546. }
  10547. if (data.action == 'download') { p13gotDownloadCommand(data); return; }
  10548. // Find file result
  10549. if (data.action == 'findfile') {
  10550. if (xxdialogTag == data.reqid) {
  10551. if (data.r == null) {
  10552. QE('d2findFilter', true);
  10553. QE('filefind_dlgOkButton', true);
  10554. xxdialogTag = null;
  10555. if (Q('d2findResults').innerHTML == '') { QH('d2findResults', '<div style=text-align:center;margin:10px><i>' + "No files found" + '</i></div>'); }
  10556. } else {
  10557. QA('d2findResults', '<div style=white-space:nowrap>' + EscapeHtml(data.r) + '</div>');
  10558. }
  10559. }
  10560. return;
  10561. }
  10562. // Process file upload commands
  10563. if ((data.action != null) && (data.action.startsWith('upload'))) { p13gotUploadData(data); return; }
  10564. // Process any SSH actions
  10565. switch (data.action) {
  10566. case 'sshauth': { sshTunnelAuthDialog(data, p13sshConnectEx); return; }
  10567. case 'autherror': { p13setConsoleMsg("Authentication Error", 5000); return; }
  10568. case 'connectionerror': { p13setConsoleMsg("Connection Error", 5000); return; }
  10569. case 'sessionerror': { p13setConsoleMsg("Session expired", 5000); return; }
  10570. case 'sessiontimeout': { p13setConsoleMsg("Session timeout", 5000); return; }
  10571. }
  10572. // Display a dialog message
  10573. if (data.action == 'dialogmessage') {
  10574. if ((data.msg == null) && (xxdialogTag == 'fileMsgDialog')) {
  10575. setDialogMode(0); // Close the dialog box
  10576. } else if ((data.msg == 'zipping') && ((!xxdialogMode) || (xxdialogTag == 'fileMsgDialog'))) {
  10577. // Show the dialog box message
  10578. setDialogMode(2, "File Operation", 10, p13fileOperationDialogEx, '<div style=margin:10px>' + "Compressing files..." + '</div>', 'fileMsgDialog');
  10579. } else if ((data.msg == 'unzipping') && ((!xxdialogMode) || (xxdialogTag == 'fileMsgDialog'))) {
  10580. // Show the dialog box message
  10581. setDialogMode(2, "File Operation", 10, null, '<div style=margin:10px>' + "Unzipping file..." + '</div>', 'fileMsgDialog');
  10582. } else if ((data.msg == 'unziperror') && ((!xxdialogMode) || (xxdialogTag == 'fileMsgDialog'))) {
  10583. // Show the dialog box message
  10584. setDialogMode(2, "File Operation", 10, null, '<div style=margin:10px>' + "Unzipping Error" + '</div><br />' + EscapeHtml(data.error), 'fileMsgDialog');
  10585. } else if ((data.msg == 'zippingFile') && ((!xxdialogMode) || (xxdialogTag == 'fileMsgDialog'))) {
  10586. // Show the dialog box message
  10587. setDialogMode(2, "File Operation", 10, p13fileOperationDialogEx, '<div style=margin:10px>' + EscapeHtml(data.file) + '</div><br /><progress value=' + EscapeHtml(data.progress) + ' style=width:100% max=100 />', 'fileMsgDialog');
  10588. }
  10589. return;
  10590. }
  10591. // Refresh file folder
  10592. if (data.action == 'refresh') { p13folderup(9999); return; }
  10593. if (data.path != null) {
  10594. if (data.dir == null) {
  10595. if (p13targetpath != '') { p13folderup(); }
  10596. } else {
  10597. data.path = data.path.replace(/\//g, '\\');
  10598. if ((p13filetree != null) && (data.path == p13filetree.path)) {
  10599. // This is an update to the same folder
  10600. var checkedNames = p13getCheckedNames();
  10601. p13filetree = data;
  10602. p13updateFiles(checkedNames);
  10603. } else {
  10604. // Make both paths use the same seperator not start with /
  10605. var x1 = data.path.replace(/\//g, '\\'), x2 = p13targetpath.replace(/\//g, '\\');
  10606. while ((x1.length > 0) && (x1[0] == '\\')) { x1 = x1.substring(1); }
  10607. while ((x2.length > 0) && (x2[0] == '\\')) { x2 = x2.substring(1); }
  10608. if ((x1 == x2) || ((data.path == '\\') && (p13targetpath == ''))) {
  10609. // This is a different folder
  10610. p13filetree = data;
  10611. p13updateFiles();
  10612. }
  10613. }
  10614. }
  10615. }
  10616. }
  10617. function p13sshConnectEx(b) {
  10618. if (b == 0) {
  10619. if (files != null) { connectFiles(); } // Disconnect
  10620. } else {
  10621. var keep = 0;
  10622. if (Q('dp2authmethod').value == 1) {
  10623. if ((features2 & 0x00400000) == 0) { keep = (Q('dp2keep').checked ? 1 : 0); }
  10624. files.socket.send(JSON.stringify({ action: 'sshauth', username: Q('dp2user').value, password: Q('dp2pass').value, keep: keep }));
  10625. } else if (Q('dp2authmethod').value == 3) {
  10626. files.socket.send(JSON.stringify({ action: 'sshkeyauth', keypass: Q('dp2keypass2').value }));
  10627. } else {
  10628. 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
  10629. var reader = new FileReader(), username = Q('dp2user').value, keypass = Q('dp2keypass').value;
  10630. reader.onload = function (e) { files.socket.send(JSON.stringify({ action: 'sshauth', username: username, keypass: keypass, key: e.target.result, keep: keep })); }
  10631. reader.readAsText(Q('dp2key').files[0]);
  10632. }
  10633. }
  10634. }
  10635. function p13getCheckedNames() {
  10636. // Save all existing checked boxes
  10637. var checkedNames = [], checkboxes = document.getElementsByName('fd');
  10638. for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { checkedNames.push(p13filetree.dir[checkboxes[i].value].n) }; }
  10639. return checkedNames;
  10640. }
  10641. function p13updateFiles(checkedNames) {
  10642. var html1 = '', html2 = '', displayPath = '<a href=# style=cursor:pointer onclick="return p13folderup(0)">' + "Root" + '</a>', fullPath = 'Root';
  10643. if(p13filetree==null) return;
  10644. // Work on parsing the file path
  10645. var x = p13filetree.path.split('\\');
  10646. p13filetreelocation = [];
  10647. for (var i in x) { if (x[i] != '') { p13filetreelocation.push(x[i]); } } // Remove empty spaces
  10648. for (var i in p13filetreelocation) { displayPath += ' / <a href=# style=cursor:pointer onclick="return p13folderup(' + (parseInt(i) + 1) + ')">' + EscapeHtml(p13filetreelocation[i]) + '</a>' } // Setup the path we display
  10649. var newlinkpath = p13filetreelocation.join('/');
  10650. // Sort the files
  10651. var filetreexx = p13sort_files(p13filetree.dir);
  10652. // Display all files and folders at this location
  10653. for (var i in filetreexx) {
  10654. // Figure out the name and shortname
  10655. var f = filetreexx[i], name = f.n, shortname;
  10656. // if (name.length > 70) { shortname = '<span title="' + EscapeHtml(name) + '">' + EscapeHtml(name.substring(0, 70)) + ("..." + '</span>'); } else { shortname = EscapeHtml(name); }
  10657. // Removed redundant filename length check because we handle it in the CSS
  10658. shortname = EscapeHtml(name);
  10659. // Figure out the date
  10660. var fdatestr = '';
  10661. if (f.d != null) {
  10662. var fdate = new Date(f.d);
  10663. if (typeof f.d == 'number') { fdate = new Date(f.d * 1000); }
  10664. var fdatestr = printDateTime(fdate) + '&nbsp;';
  10665. }
  10666. // Figure out the size
  10667. var fsize = '';
  10668. if (f.s != null) {
  10669. const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
  10670. const i = parseInt(Math.floor(Math.log(Math.abs(f.s)) / Math.log(1024)), 10);
  10671. const option = Q('p13sizedropdown').options[Q('p13sizedropdown').selectedIndex];
  10672. if (Q('p13sizedropdown').value == 0) {
  10673. if (f.s === 0){
  10674. fsize = 'n/a';
  10675. } else {
  10676. fsize = (i === 0 ? `${f.s} ${sizes[i]}` : `${(f.s / (1024 ** i)).toFixed(2)} ${sizes[i]}`);
  10677. }
  10678. } else if (Q('p13sizedropdown').value==1) {
  10679. fsize = getFileSizeStr(f.s);
  10680. } else {
  10681. fsize = `${(f.s / (2 ** option.value)).toFixed(2)} ${option.title}`;
  10682. }
  10683. }
  10684. var h = '';
  10685. if (f.t < 3) {
  10686. var right = '', title = '';
  10687. h = '<div class=filelist file=999>';
  10688. h += '<input file=999 style=float:left name=fd class=fcb type=checkbox onchange=p13setActions() value=\'' + f.nx + '\'>&nbsp;<span style=float:right title="' + title + '">' + right + '</span>';
  10689. h += '<span title="' + shortname + '"><div class=fileIcon' + (f.dt == 'REMOVABLE' ? 5 : (f.dt == 'CDROM' ? 6 : f.t)) + ' onclick=p13folderset("' + encodeURIComponentEx(f.nx) + '")></div><a href=# style=cursor:pointer onclick=\'return p13folderset("' + encodeURIComponentEx(f.nx) + '")\'>';
  10690. if (isWindowsNode(currentNode) && f.dt && currentNode.volumes && currentNode.volumes[shortname.charAt(0).toUpperCase()] && currentNode.volumes[shortname.charAt(0).toUpperCase()].name) {
  10691. h += currentNode.volumes[shortname.charAt(0).toUpperCase()].name + ' (' + shortname + ')';
  10692. } else {
  10693. h += shortname;
  10694. }
  10695. h += '</a></span></div>';
  10696. } else {
  10697. var link = shortname;
  10698. if (f.s > 0) {
  10699. if (filesNode.mtype == 3) {
  10700. // Local link
  10701. link = '<a href=# style=cursor:pointer onclick="return p13downloadfile(\'' + encodeURIComponentEx(newlinkpath + '/' + name) + '\',\'' + encodeURIComponentEx(name) + '\',' + f.s + ')">' + shortname + '</a>';
  10702. } else {
  10703. // Server link
  10704. //link = '<a href="devicefile.ashx?c=' + authCookie + '&m=' + currentNode.meshid.split('/')[2] + '&n=' + currentNode._id.split('/')[2] + '&f=' + encodeURIComponentEx(newlinkpath + '/' + name) + '" download="' + name + '" style=cursor:pointer>' + shortname + '</a>';
  10705. // Server link
  10706. link = '<a onclick=downloadFile("devicefile.ashx?c=' + authCookie + '&m=' + currentNode.meshid.split('/')[2] + '&n=' + currentNode._id.split('/')[2] + '&f=' + encodeURIComponentEx(newlinkpath + '/' + name) + '","' + encodeURIComponentEx(name) + '") style=cursor:pointer>' + shortname + '</a>';
  10707. }
  10708. }
  10709. h = '<div id=fileEntry cmenu=filesContextMenu fileIndex=' + i + ' class=filelist file=3><input file=3 style=float:left name=fd class=fcb type=checkbox onchange=p13setActions() value=\'' + f.nx + '\'>&nbsp;<span class=fsize>' + fdatestr + '</span><span style=float:right>' + EscapeHtml(fsize) + '</span><span title="' + shortname + '"><div class=fileIcon' + f.t + '></div>' + link + '</span></div>';
  10710. }
  10711. if (f.t < 3) { html1 += h; } else { html2 += h; }
  10712. }
  10713. // Display the files and path
  10714. QH('p13files', html1 + html2);
  10715. QH('p13currentpath', displayPath);
  10716. QE('p13FolderUp', p13filetreelocation.length != 0);
  10717. // Re-check all boxes if needed using names
  10718. 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; } } }
  10719. // Update the actions buttons
  10720. p13setActions();
  10721. }
  10722. function p13openfilefolder() {
  10723. setDialogMode(2, "Open File/Folder", 3, p13openfilefolderEx, "Are you sure you want to open this file/folder on the remote devices desktop?");
  10724. }
  10725. function p13openfilefolderEx() {
  10726. var openfilefolder = "", checkboxes = document.getElementsByName('fd');
  10727. for (var i = 0; i < checkboxes.length; i++) {
  10728. if (checkboxes[i].checked) {
  10729. openfilefolder = (isWindowsNode(currentNode) ? '' : '/') + p13filetreelocation.join(isWindowsNode(currentNode) ? '\\' : '/') + (isWindowsNode(currentNode) ? '\\' : '/') + p13filetree.dir[checkboxes[i].value].n;
  10730. }
  10731. }
  10732. if (openfilefolder == "") return;
  10733. files.sendText({ action: 'open', reqid: 1, path: openfilefolder, dir: (p13getFileSelDirCount() == 1) ? true : false });
  10734. }
  10735. function p13gotofolder() {
  10736. setDialogMode(2, "Go To Folder", 3, p13gotofolderEx, '<input type=text id=p13folderinput value="' + (isWindowsNode(currentNode) ? p13targetpath.replaceAll('/','\\') + '\\' : p13targetpath + '/') +'" style=width:100% placeholder="'+(isWindowsNode(currentNode) ? 'C:\\' : '/var/www')+'" />');
  10737. focusTextBox('p13folderinput');
  10738. }
  10739. function p13gotofolderEx() {
  10740. p13targetpath = Q('p13folderinput').value.split('\\').join('/');
  10741. if (files) { p13storeCurrentPath(p13targetpath); files.sendText({ action: 'ls', reqid: 1, path: p13targetpath }); }
  10742. }
  10743. function p13folderset(x) {
  10744. p13targetpath = joinPaths(p13filetree.path, p13filetree.dir[x].n).split('\\').join('/');
  10745. if (files) { p13storeCurrentPath(p13targetpath); files.sendText({ action: 'ls', reqid: 1, path: p13targetpath }); }
  10746. }
  10747. function p13folderup(x) {
  10748. if (x == null) { p13filetreelocation.pop(); } else { while (p13filetreelocation.length > x) { p13filetreelocation.pop(); } }
  10749. p13targetpath = p13filetreelocation.join('/');
  10750. if (files) { p13storeCurrentPath(p13targetpath); files.sendText({ action: 'ls', reqid: 1, path: p13targetpath }); }
  10751. return false;
  10752. }
  10753. // Store the current path for a given node as browser state.
  10754. // This is so, when reconnecting to a device, you go back to the same path.
  10755. function p13storeCurrentPath(path) {
  10756. var filepaths = [], j = -1;
  10757. try { filepaths = JSON.parse(getstore('_devFilePaths', '[]')); } catch (ex) {}
  10758. for (var i = 0; i < filepaths.length; i++) { if (filepaths[i].n == currentNode._id) { j = i; } }
  10759. if (j >= 0) { filepaths.splice(j, 1); }
  10760. filepaths.push({ n: currentNode._id, p: path });
  10761. while (filepaths.length > 40) { filepaths.shift(); } // Keep only 40 devices worth of paths.
  10762. putstore('_devFilePaths', JSON.stringify(filepaths));
  10763. }
  10764. function p13findfile() {
  10765. if (xxdialogMode) return;
  10766. var x = addHtmlValue("Filter", '<input id=d2findFilter style="width:230px" onkeyup=p13findfileValidate() onkeydown=p13findfileDown(event) placeholder="*.txt"></input>');
  10767. x += '<div id=d2findResults style="width:100%;height:184px;background-color:white;border:1px solid black;padding:3px;overflow:scroll;margin-top:8px"></div>';
  10768. x += '<div style=margin-top:8px><input id=filefind_dlgCancelButton type=button value=' + "Close" + ' style=float:right;width:80px;margin-left:5px onclick=p13findfileCancel()>';
  10769. x += '<input id=filefind_dlgOkButton type=submit value=' + "Search" + ' style=float:right;width:80px onclick=p13findfileEx()><br /><br /></div>';
  10770. setDialogMode(2, "Find Files", 0, null, x);
  10771. p13findfileValidate();
  10772. Q('d2findFilter').focus();
  10773. }
  10774. function p13findfileDown(e) { if (e.code == "Enter") { p13findfileEx(); } }
  10775. function p13findfileValidate() { QE('filefind_dlgOkButton', Q('d2findFilter').value.length > 0); }
  10776. function p13findfileCancel() { if (xxdialogTag != null) { files.sendText({ action: 'cancelfindfile', reqid: xxdialogTag }); } dialogclose(0) }
  10777. function p13findfileEx(b, t) {
  10778. if (Q('d2findFilter').value.length == 0) return;
  10779. var winAgent = isWindowsNode(currentNode);
  10780. var slash = winAgent ? '\\' : '/';
  10781. var path = p13filetreelocation.join(slash) + slash;
  10782. if (!winAgent) { path = slash + path; }
  10783. xxdialogTag = 'find:' + Math.random();
  10784. files.sendText({ action: 'findfile', reqid: xxdialogTag, path: path, filter: Q('d2findFilter').value });
  10785. QH('d2findResults', '');
  10786. QE('d2findFilter', false);
  10787. QE('filefind_dlgOkButton', false);
  10788. }
  10789. var p13sortorder;
  10790. function p13sort_filename(a, b) { if (a.ln > b.ln) return (1 * p13sortorder); if (a.ln < b.ln) return (-1 * p13sortorder); return 0; }
  10791. function p13sort_timestamp(a, b) { if (a.d > b.d) return (1 * p13sortorder); if (a.d < b.d) return (-1 * p13sortorder); return 0; }
  10792. function p13sort_bysize(a, b) { if (a.s == b.s) return p13sort_filename(a, b); return (((a.s - b.s)) * p13sortorder); }
  10793. function p13sort_files(files) {
  10794. var r = [], sortselection = Q('p13sortdropdown').value;
  10795. 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]); }
  10796. p13sortorder = 1;
  10797. if (sortselection > 3) { p13sortorder = -1; sortselection -= 3; }
  10798. if (sortselection == 1) { r.sort(p13sort_filename); }
  10799. else if (sortselection == 2) { r.sort(p13sort_bysize); }
  10800. else if (sortselection == 3) { r.sort(p13sort_timestamp); }
  10801. return r;
  10802. }
  10803. function p13setActions() {
  10804. var advancedFeatures = ((currentNode.agent != null) && (currentNode.agent.id != 14)); // Reduce file feature on some devices.
  10805. if (p13filetree == null) {
  10806. QE('p13DeleteFileButton', false);
  10807. QE('p13NewFolderButton', false);
  10808. QE('p13NewFileButton', false);
  10809. QE('p13UploadButton', false);
  10810. QE('p13RenameFileButton', false);
  10811. QE('p13ViewFileButton', false);
  10812. QE('p13SelectAllButton', false);
  10813. Q('p13SelectAllButton').value = "Select All";
  10814. QE('p13RefreshButton', false);
  10815. QE('p13FindButton', false);
  10816. QE('p13CutButton', false);
  10817. QE('p13CopyButton', false);
  10818. QE('p13ZipButton', false);
  10819. QE('p13UnZipButton', false);
  10820. QE('p13PasteButton', false);
  10821. QE('p13GoToFolderButton', false);
  10822. QE('p13DownloadButton', false);
  10823. QE('p13OpenButton', false);
  10824. } else {
  10825. 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)
  10826. var winAgent = isWindowsNode(currentNode);
  10827. QE('p13DeleteFileButton', (cc > 0) && ((p13filetreelocation.length > 0) || (winAgent == false)));
  10828. QE('p13NewFolderButton', advancedFeatures && ((p13filetreelocation.length > 0) || (winAgent == false)));
  10829. QE('p13UploadButton', ((p13filetreelocation.length > 0) || (winAgent == false) || (currentNode.agent.id == 14)));
  10830. QE('p13NewFileButton', advancedFeatures && ((p13filetreelocation.length > 0) || (winAgent == false)));
  10831. QE('p13RenameFileButton', advancedFeatures && (cc == 1) && ((p13filetreelocation.length > 0) || (winAgent == false)));
  10832. QE('p13ViewFileButton', advancedFeatures && (cc == 1) && (sfc == 1) && ((p13filetreelocation.length > 0) || (winAgent == false)));
  10833. QE('p13SelectAllButton', tc > 0);
  10834. Q('p13SelectAllButton').value = (cc > 0 ? "Select None" : "Select All");
  10835. QE('p13RefreshButton', true);
  10836. QE('p13FindButton', advancedFeatures && ((p13filetreelocation.length > 0) || (winAgent == false)));
  10837. QE('p13CutButton', advancedFeatures && (cc > 0) && (cc == sfc) && ((p13filetreelocation.length > 0) || (winAgent == false)));
  10838. QE('p13CopyButton', advancedFeatures && (cc > 0) && (cc == sfc) && ((p13filetreelocation.length > 0) || (winAgent == false)));
  10839. QE('p13ZipButton', advancedFeatures && (cc > 0) && ((p13filetreelocation.length > 0) || (winAgent == false)));
  10840. QE('p13UnzipButton', advancedFeatures && (cc == 1) && (sfc == 1) && ((p13filetreelocation.length > 0) || (winAgent == false)) && p13getFileSelAllowedExt('.zip'));
  10841. QE('p13PasteButton', advancedFeatures && ((p13filetreelocation.length > 0) || (winAgent == false)) && ((p13clipboard != null) && (p13clipboard.length > 0)));
  10842. QE('p13GoToFolderButton', advancedFeatures);
  10843. QE('p13OpenButton', advancedFeatures && (cc == 1));
  10844. QE('p13DownloadButton', (cc > 0) && (cc == sfc) && ((p13filetreelocation.length > 0) || (winAgent == false)));
  10845. }
  10846. var filesState = ((files != null) && (files.state != 0));
  10847. if (((filesState == true) && (files.contype != 2)) || (filesNode.agent == null) || (filesNode.agent.id == 3) || (filesNode.agent.id == 4)) {
  10848. QH('filesCustomUpperRight', '');
  10849. } else {
  10850. QH('filesCustomUpperRight', '<a style=cursor:pointer onclick=cmsshportaction(1,event)>' + format("SSH Port {0}", (filesNode.sshport?filesNode.sshport:22)) + '</a>');
  10851. }
  10852. QV('filesActionsBtn', filesNode.mtype != 3);
  10853. QV('p13FindButton', filesNode.mtype != 3);
  10854. QV('p13CutButton', filesNode.mtype != 3);
  10855. QV('p13CopyButton', filesNode.mtype != 3);
  10856. QV('p13ZipButton', filesNode.mtype != 3);
  10857. QV('p13UnzipButton', filesNode.mtype != 3);
  10858. QV('p13PasteButton', filesNode.mtype != 3);
  10859. }
  10860. 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; }
  10861. 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; }
  10862. function p13getFileCount() { var cc = 0; var checkboxes = document.getElementsByName('fd'); return checkboxes.length; }
  10863. function p13getFileSelAllowedExt(ext) { var checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && (!p13filetree.dir[checkboxes[i].value].n.endsWith(ext))) return false; } return true; }
  10864. function p13selectallfile() { var nv = (p13getFileSelCount() == 0), checkboxes = document.getElementsByName('fd'); for (var i = 0; i < checkboxes.length; i++) { checkboxes[i].checked = nv; } p13setActions(); }
  10865. function p13createfolder() { setDialogMode(2, "New Folder", 3, p13createfolderEx, '<input type=text id=p13renameinput maxlength=64 onkeyup=p13fileNameCheck(event) style=width:100% />'); focusTextBox('p13renameinput'); p13fileNameCheck(); }
  10866. function p13createfolderEx() { files.sendText({ action: 'mkdir', reqid: 1, path: p13filetreelocation.join('/') + '/' + Q('p13renameinput').value }); p13folderup(999); }
  10867. function p13createfile() { setDialogMode(2, "New File", 3, p13createfileEx, '<input type=text id=p13renameinput maxlength=64 onkeyup=p13fileNameCheck(event) style=width:100% />'); focusTextBox('p13renameinput'); p13fileNameCheck(); }
  10868. function p13createfileEx() { files.sendText({ action: 'mkfile', reqid: 1, path: p13filetreelocation.join('/') + '/' + Q('p13renameinput').value }); p13folderup(999); }
  10869. 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)); }
  10870. 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); }
  10871. 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(); }
  10872. function p13renamefileEx(b, t) { t.newname = Q('p13renameinput').value; files.sendText(t); p13folderup(999); }
  10873. function p13fileNameCheck(e) { var x = isFilenameValid(Q('p13renameinput').value); QE('idx_dlgOkButton', x); if ((x == true) && (e != null) && (e.keyCode == 13)) { dialogclose(1); } }
  10874. 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'); }
  10875. function updateUploadDialogOk(x) { QE('idx_dlgOkButton', Q('p13uploadinput').files.length > 0); }
  10876. function p13uploadFileEx() { p13doUploadFiles(Q('p13uploadinput').files); }
  10877. function p13viewfile() {
  10878. var checkboxes = document.getElementsByName('fd');
  10879. for (var i = 0; i < checkboxes.length; i++) {
  10880. if (checkboxes[i].checked) {
  10881. if (p13filetree.dir[checkboxes[i].value].s <= 204800) {
  10882. p13downloadfile(encodeURIComponentEx(p13filetreelocation.join('/') + '/' + p13filetree.dir[checkboxes[i].value].n), encodeURIComponentEx(p13filetree.dir[checkboxes[i].value].n), p13filetree.dir[checkboxes[i].value].s, 'viewer');
  10883. } else { messagebox("File Editor", "Only files less than 200k can be edited."); }
  10884. break;
  10885. }
  10886. }
  10887. }
  10888. function p13unzipFile() {
  10889. var dest, input, checkboxes = document.getElementsByName('fd');
  10890. for (var i = 0; i < checkboxes.length; i++) {
  10891. if (checkboxes[i].checked) {
  10892. var slash = isWindowsNode(currentNode) ? '\\' : '/';
  10893. dest = (isWindowsNode(currentNode) ? '' : '/') + p13filetreelocation.join(slash) + slash + p13filetree.dir[checkboxes[i].value].n.split('.zip')[0] + slash;
  10894. input = (isWindowsNode(currentNode) ? '' : '/') + p13filetreelocation.join(slash) + slash + p13filetree.dir[checkboxes[i].value].n;
  10895. }
  10896. }
  10897. setDialogMode(2, "Unzip To Folder", 3, p13unzipFileEx, '<input type=text id=p13unzipfolderinput maxlength=64 style=width:100% value="' + dest + '" />', { action: 'unzip', input: input });
  10898. focusTextBox('p13unzipfolderinput');
  10899. }
  10900. function p13unzipFileEx(a, tag) {
  10901. tag.dest = Q('p13unzipfolderinput').value;
  10902. files.sendText(tag);
  10903. }
  10904. function p13zipFiles() {
  10905. var inputFiles = [], checkboxes = document.getElementsByName('fd');
  10906. for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { inputFiles.push(p13filetree.dir[checkboxes[i].value].n); } }
  10907. setDialogMode(2, "Zip Filename", 3, p13zipFilesEx, '<input type=text id=p13renameinput maxlength=64 onkeyup=p13fileNameCheck(event) style=width:100% value="" />', { action: 'zip', path: p13filetreelocation.join('/'), files: inputFiles});
  10908. focusTextBox('p13renameinput');
  10909. p13fileNameCheck();
  10910. }
  10911. function p13zipFilesEx(b, tag) {
  10912. tag.output = Q('p13renameinput').value;
  10913. if (!tag.output.toLowerCase().endsWith('.zip')) { tag.output += '.zip'; }
  10914. files.sendText(tag);
  10915. }
  10916. var p13clipboard = null, p13clipboardFolder = null, p13clipboardCut = 0;
  10917. 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(); }
  10918. function p13pasteFile() {
  10919. var x = '';
  10920. if ((p13clipboard != null) && (p13clipboard.length > 0)) {
  10921. if (p13clipboardCut == 0) {
  10922. if (p13clipboard.length > 1) { x = format("Confirm copy of {0} entries to this location?", p13clipboard.length); } else { x = format("Confirm copy of 1 entrie to this location?"); }
  10923. } else {
  10924. if (p13clipboard.length > 1) { x = format("Confirm move of {0} entries to this location?", p13clipboard.length); } else { x = format("Confirm move of 1 entrie to this location?"); }
  10925. }
  10926. }
  10927. setDialogMode(2, "Paste", 3, p13pasteFileEx, x);
  10928. }
  10929. 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(); } }
  10930. function p13updateClipview() {
  10931. var x = '';
  10932. if ((p13clipboard != null) && (p13clipboard.length > 0)) {
  10933. if (p13clipboardCut == 0) {
  10934. if (p13clipboard.length > 1) {
  10935. x = format("Holding {0} entries for copy" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.', p13clipboard.length);
  10936. } else {
  10937. x = format("Holding 1 entrie for copy" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.');
  10938. }
  10939. } else {
  10940. if (p13clipboard.length > 1) {
  10941. x = format("Holding {0} entries for move" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.', p13clipboard.length);
  10942. } else {
  10943. x = format("Holding 1 entrie for move" + ', <a href=# onclick="return p13clearClip()" style=cursor:pointer>' + "Clear" + '</a>.');
  10944. }
  10945. }
  10946. }
  10947. QH('p13bottomstatus', x);
  10948. p13setActions();
  10949. }
  10950. function p13clearClip() { p13clipboard = null; p13clipboardFolder = null; p13clipboardCut = 0; p13updateClipview(); return false; }
  10951. function p13fileDragDrop(e) {
  10952. haltEvent(e);
  10953. QV('p13bigfail', false);
  10954. QV('p13bigok', false);
  10955. if ((e.dataTransfer == null) || (e.dataTransfer.files.length == 0) || (p13filetree == null)) return;
  10956. // Check if these are files we can upload, remove all folders.
  10957. var files = [];
  10958. for (var i in e.dataTransfer.files) { if ((e.dataTransfer.files[i].size != null) && (e.dataTransfer.files[i].size != 0)) { files.push(e.dataTransfer.files[i]); } }
  10959. if (files.length == 0) return;
  10960. p13doUploadFiles(files);
  10961. }
  10962. var p13dragtimer = null;
  10963. function p13fileDragOver(e) {
  10964. haltEvent(e);
  10965. if (p13dragtimer != null) { clearTimeout(p13dragtimer); p13dragtimer = null; }
  10966. var ac = (p13filetree != null); // Set to true if we can accept the file
  10967. QV('p13bigok', ac);
  10968. QV('p13bigfail', !ac);
  10969. }
  10970. function p13fileDragLeave(e) {
  10971. haltEvent(e);
  10972. if (e.target.id != 'p13filetable') {
  10973. QV('p13bigfail', false);
  10974. QV('p13bigok', false);
  10975. } else {
  10976. p13dragtimer = setTimeout(function () { QV('p13bigfail',false); QV('p13bigok',false); p13dragtimer=null; }, 10);
  10977. }
  10978. }
  10979. //
  10980. // FILES DOWNLOAD
  10981. //
  10982. var gdownloadFile; // Global state for file download
  10983. // Called by the html page to start a download, arguments are: path, file name and file size.
  10984. function p13downloadfile(x, y, z, tag) {
  10985. if (xxdialogMode || !files) return;
  10986. gdownloadFile = { path: decodeURIComponent(x), file: decodeURIComponent(y), size: z, tsize: 0, data: '', state: 0, id: Math.random(), tag: tag }
  10987. //console.log('p13downloadFileCancel', gdownloadFile);
  10988. files.sendText({ action: 'download', sub: 'start', id: gdownloadFile.id, path: gdownloadFile.path });
  10989. setDialogMode(2, "Download File", 10, p13downloadFileCancel, '<div>' + EscapeHtml(gdownloadFile.file) + '</div><br /><progress id=d2progressBar style=width:100% value=0 max=' + z + ' />');
  10990. }
  10991. // Called by the html page to cancel the download
  10992. function p13downloadFileCancel() { setDialogMode(0); files.sendText({ action: 'download', sub: 'cancel', id: gdownloadFile.id }); gdownloadFile = null; }
  10993. // Called by the transport when download control command is received
  10994. function p13gotDownloadCommand(cmd) {
  10995. //console.log('p13gotDownloadCommand', cmd);
  10996. if ((gdownloadFile == null) || (cmd.id != gdownloadFile.id)) return;
  10997. if (cmd.sub == 'start') { gdownloadFile.state = 1; files.sendText({ action: 'download', sub: 'startack', id: gdownloadFile.id }); }
  10998. else if (cmd.sub == 'cancel') { gdownloadFile = null; setDialogMode(0); }
  10999. }
  11000. // Called by the transport when binary data is received
  11001. function p13gotDownloadBinaryData(data) {
  11002. if (!gdownloadFile || gdownloadFile.state == 0) return;
  11003. if (data.length > 4) {
  11004. gdownloadFile.tsize += (data.length - 4); // Add to the total bytes received
  11005. gdownloadFile.data += data.substring(4); // Append the data
  11006. Q('d2progressBar').value = gdownloadFile.tsize; // Change the progress bar
  11007. }
  11008. if ((ReadInt(data, 0) & 1) != 0) { // Check end flag
  11009. if (gdownloadFile.tag == 'viewer') {
  11010. // View the file in the dialog box
  11011. setDialogMode(4, EscapeHtml(gdownloadFile.file), 3, p13editSaveBack, null, gdownloadFile.file);
  11012. QV('d4EncodingButton', true);
  11013. QV('d4LineBreakButton', true);
  11014. QS('dialog').width = 'auto';
  11015. QS('dialog').bottom = '80px';
  11016. QS('dialog').top = QS('dialog').left = QS('dialog').right = '100px';
  11017. if (d4EditEncodingVal == 1) {
  11018. Q('d4editorarea').value = decode_utf8(gdownloadFile.data); // UTF8 Encoding
  11019. } else {
  11020. Q('d4editorarea').value = gdownloadFile.data; // RAW Encoding
  11021. }
  11022. gdownloadFile = null;
  11023. } else {
  11024. // Save the file to disk
  11025. saveAs(data2blob(gdownloadFile.data), gdownloadFile.file); gdownloadFile = null; setDialogMode(0); // Save the file
  11026. }
  11027. } else {
  11028. files.sendText({ action: 'download', sub: 'ack', id: gdownloadFile.id }); // Send the ACK
  11029. }
  11030. }
  11031. function p13downloadButton() {
  11032. var checkboxes = document.getElementsByName('fd');
  11033. for (var i = 0; i < checkboxes.length; i++) {
  11034. if (checkboxes[i].checked) {
  11035. var thefile = p13filetree.dir[checkboxes[i].value];
  11036. var link = 'devicefile.ashx?c=' + authCookie;
  11037. link += '&m=' + currentNode.meshid.split('/')[2];
  11038. link += '&n=' + currentNode._id.split('/')[2];
  11039. link += '&f=' + encodeURIComponentEx(p13filetreelocation.join('/') + '/' + thefile.n);
  11040. downloadFile(link,encodeURIComponentEx(thefile.n));
  11041. }
  11042. }
  11043. }
  11044. var d4EditWrapVal = 0;
  11045. var d4EditSizeVal = 0;
  11046. var d4EditEncodingVal = 0;
  11047. var d4EditLineBreakVal = 0;
  11048. function d4ToggleWrap(update) {
  11049. if (!update) { d4EditWrapVal = ++d4EditWrapVal % 2; }
  11050. Q('d4WrapButton').value = ["Wrap: ON","Wrap: OFF"][d4EditWrapVal];
  11051. QS('d4editorarea').overflow = (d4EditWrapVal == 0)?'auto':'scroll';
  11052. QS('d4editorarea')['white-space'] = (d4EditWrapVal == 0)?null:'pre';
  11053. putstore('editorWrap', d4EditWrapVal);
  11054. }
  11055. function d4ToggleSize(update) {
  11056. if (!update) { d4EditSizeVal = ++d4EditSizeVal % 4; }
  11057. QS('d4editorarea')['font-size'] = ['100%','125%','150%','200%'][d4EditSizeVal];
  11058. Q('d4SizeButton').value = ["Size: 100%","Size: 125%","Size: 150%","Size: 200%"][d4EditSizeVal];
  11059. putstore('editorSize', d4EditSizeVal);
  11060. }
  11061. function d4ToggleEncoding(update) {
  11062. if (!update) {
  11063. d4EditEncodingVal = ++d4EditEncodingVal % 2;
  11064. if (d4EditEncodingVal == 1) {
  11065. Q('d4editorarea').value = decode_utf8(Q('d4editorarea').value);
  11066. } else {
  11067. Q('d4editorarea').value = encode_utf8(Q('d4editorarea').value);
  11068. }
  11069. }
  11070. Q('d4EncodingButton').value = ["Encoding: RAW","Encoding: UTF8"][d4EditEncodingVal];
  11071. putstore('editorEncoding', d4EditEncodingVal);
  11072. }
  11073. function d4ToggleLineBreak(update) {
  11074. if (!update) {
  11075. d4EditLineBreakVal = ++d4EditLineBreakVal % 3;
  11076. }
  11077. Q('d4LineBreakButton').value = ["Line Break: Windows (CR LF)","Line Break: Linux (LF)","Line Break: Mac (CR)"][d4EditLineBreakVal];
  11078. putstore('editorLineBreak', d4EditLineBreakVal);
  11079. }
  11080. function p13editSaveBack(b, tag) {
  11081. var data;
  11082. var value = Q('d4editorarea').value;
  11083. value = d4EditLineBreakVal === 0 ? value.replace(/\r?\n|\r/g, '\r\n') : // Windows
  11084. d4EditLineBreakVal === 2 ? value.replace(/\r\n|\n/g, '\r') : // Mac
  11085. value.replace(/\r\n|\r/g, '\n'); // Linux
  11086. if (d4EditEncodingVal == 1) {
  11087. data = new TextEncoder().encode(value); // UTF8 encoding
  11088. } else {
  11089. data = new TextEncoder().encode(decode_utf8(value)); // RAW encoding
  11090. }
  11091. p13uploadFileContinue(1, [{ name: tag, size: data.byteLength, type: 'text/plain', xdata: data }]);
  11092. }
  11093. //
  11094. // FILES UPLOAD
  11095. //
  11096. var uploadFile;
  11097. function p13doUploadFiles(files) {
  11098. if (xxdialogMode) return;
  11099. // Check if we are going to overwrite any files
  11100. var winAgent = isWindowsNode(currentNode);
  11101. var targetFiles = [], overWriteCount = 0;
  11102. for (var i in p13filetree.dir) { if (winAgent) { targetFiles.push(p13filetree.dir[i].n.toLowerCase()); } else { targetFiles.push(p13filetree.dir[i].n); } }
  11103. for (var i = 0; i < files.length; i++) {
  11104. if (winAgent) {
  11105. if (targetFiles.indexOf(files[i].name.toLowerCase()) >= 0) { overWriteCount++; }
  11106. } else {
  11107. if (targetFiles.indexOf(files[i].name) >= 0) { overWriteCount++; }
  11108. }
  11109. }
  11110. if (overWriteCount == 0) {
  11111. // If no overwrite, go ahead with upload
  11112. p13uploadFileContinue(1, files);
  11113. } else {
  11114. // Otherwise, prompt for confirmation
  11115. setDialogMode(2, "Upload File", 3, p13uploadFileContinue, format((overWriteCount == 1)?"Upload will overwrite 1 file. Continue?":"Upload will overwrite {0} files. Continue?", overWriteCount), files);
  11116. }
  11117. }
  11118. function p13uploadFileContinue(b, files) {
  11119. uploadFile = {};
  11120. uploadFile.xpath = p13filetreelocation.join('/');
  11121. uploadFile.xfiles = files;
  11122. uploadFile.xfilePtr = -1;
  11123. setDialogMode(2, "Upload File", 10, p13uploadFileCancel, '<div id=p13dfileName>' + "Connecting..." + '</div><br /><progress id=d2progressBar style=width:100% value=0 max=0 />');
  11124. p13uploadNextFile();
  11125. }
  11126. // Perform SHA-384 hashing
  11127. const byteToHex = [];
  11128. for (var n = 0; n <= 0xff; ++n) { var hexOctet = n.toString(16).padStart(2, '0'); byteToHex.push(hexOctet); }
  11129. function arrayBufferToHex(arrayBuffer) { return Array.prototype.map.call( new Uint8Array(arrayBuffer), n => byteToHex[n] ).join(''); }
  11130. function performHash(data, f) { window.crypto.subtle.digest('SHA-384', data).then(function (v) { f(arrayBufferToHex(v)); }, function() { f(null); }); }
  11131. function performHashOnFile(file, f) {
  11132. // TODO: At some point, try to make this work for files of unlimited size using a digest stream
  11133. var reader = new FileReader();
  11134. reader.onerror = function (err) { f(null); }
  11135. reader.onload = function () { window.crypto.subtle.digest('SHA-384', reader.result).then(function (v) { f(arrayBufferToHex(v)); }, function() { f(null); }); };
  11136. reader.readAsArrayBuffer(file);
  11137. }
  11138. // Push the next file
  11139. function p13uploadNextFile() {
  11140. uploadFile.xfilePtr++;
  11141. if (uploadFile.xfiles.length > uploadFile.xfilePtr) {
  11142. uploadFile.xptr = 0;
  11143. var file = uploadFile.xfiles[uploadFile.xfilePtr];
  11144. QH('p13dfileName', EscapeHtml(file.name));
  11145. Q('d2progressBar').max = file.size;
  11146. Q('d2progressBar').value = 0;
  11147. if (file.xdata == null) {
  11148. uploadFile.xfile = file;
  11149. // If the remote file already exists and is smaller then our file, see if we can resume the trasfer
  11150. var f = null;
  11151. for (var i in p13filetree.dir) { if (p13filetree.dir[i].n == file.name) { f = p13filetree.dir[i]; } }
  11152. if ((f != null) && (f.s <= uploadFile.xfile.size)) {
  11153. 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 } })); });
  11154. } else {
  11155. files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xfile.size }));
  11156. }
  11157. } else {
  11158. // Data already loaded
  11159. uploadFile.xdata = file.xdata;
  11160. files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xdata.byteLength }));
  11161. }
  11162. } else {
  11163. p13uploadFileTransferDone();
  11164. }
  11165. }
  11166. // Used to cancel the entire transfer.
  11167. function p13uploadFileCancel(button, tag) {
  11168. if (uploadFile != null) { files.sendText(JSON.stringify({ action: 'uploadcancel', reqid: uploadFile.xfilePtr })); uploadFile = null; }
  11169. p13uploadFileTransferDone();
  11170. }
  11171. // Used to cancel the entire transfer.
  11172. function p13uploadFileTransferDone() {
  11173. uploadFile = null; // No more files to upload, clean up.
  11174. setDialogMode(0); // Close the dialog box
  11175. p13folderup(9999); // Refresh the current folder
  11176. }
  11177. // Receive upload ack from the mesh agent, use this to keep sending more data
  11178. function p13gotUploadData(cmd) {
  11179. if ((uploadFile == null) || (parseInt(uploadFile.xfilePtr) != parseInt(cmd.reqid))) { return; }
  11180. switch (cmd.action) {
  11181. case 'uploadstart': { uploadFile.xdataPriming = 8; p13uploadNextPart(false); break; } // Send 8 more blocks of 16k to fill the websocket.
  11182. case 'uploadack': { p13uploadNextPart(false); break; }
  11183. case 'uploaddone': { if (uploadFile.xfiles.length > uploadFile.xfilePtr + 1) { p13uploadNextFile(); } else { p13uploadFileTransferDone(); } break; }
  11184. case 'uploaderror': { p13uploadFileCancel(); break; }
  11185. case 'uploadhash': {
  11186. var file = uploadFile.xfiles[uploadFile.xfilePtr];
  11187. if (file) {
  11188. if (cmd.tag.h === cmd.hash) {
  11189. if (cmd.tag.skip) {
  11190. p13uploadNextFile();
  11191. } else {
  11192. uploadFile.xptr = cmd.tag.s;
  11193. files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xfile.size, append: true }));
  11194. }
  11195. } else {
  11196. files.sendText(JSON.stringify({ action: 'upload', reqid: uploadFile.xfilePtr, path: uploadFile.xpath, name: file.name, size: uploadFile.xfile.size, append: false }));
  11197. }
  11198. }
  11199. break;
  11200. }
  11201. }
  11202. }
  11203. // 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.
  11204. function p13uploadNextPart(dataPriming) {
  11205. if (uploadFile.xdata) {
  11206. var data = uploadFile.xdata, start = uploadFile.xptr;
  11207. if (start >= data.byteLength) {
  11208. files.sendText(JSON.stringify({ action: 'uploaddone', reqid: uploadFile.xfilePtr }));
  11209. } else {
  11210. var end = uploadFile.xptr + (attemptWebRTC ? 16384 : 65536);
  11211. if (end > data.byteLength) { if (dataPriming == true) { return; } end = data.byteLength; }
  11212. var dataslice = new Uint8Array(data.slice(start, end))
  11213. if ((dataslice[0] == 123) || (dataslice[0] == 0)) {
  11214. var datapart = new Uint8Array(end - start + 1);
  11215. datapart.set(dataslice, 1); // Add a zero char at the start of the send, this will indicate that it's not a JSON command.
  11216. files.send(datapart);
  11217. } else {
  11218. files.send(dataslice); // The data does not start with 0 or 123 "{" so it can't be confused for JSON.
  11219. }
  11220. uploadFile.xptr = end;
  11221. Q('d2progressBar').value = end;
  11222. }
  11223. } else if (uploadFile.xfile) {
  11224. if (uploadFile.xreader != null) return; // Data reading already in process
  11225. if (uploadFile.xptr >= uploadFile.xfile.size) return;
  11226. var end = uploadFile.xptr + (attemptWebRTC ? 16384 : 65536);
  11227. if (end > uploadFile.xfile.size) { if (dataPriming == true) { return; } end = uploadFile.xfile.size; }
  11228. uploadFile.xreader = new FileReader();
  11229. uploadFile.xreader.onerror = function (err) { console.log(err); }
  11230. uploadFile.xreader.onload = function () {
  11231. var data = uploadFile.xreader.result;
  11232. delete uploadFile.xreader;
  11233. if (data == null) return;
  11234. var dataslice = new Uint8Array(data)
  11235. if ((dataslice[0] == 123) || (dataslice[0] == 0)) {
  11236. var datapart = new Uint8Array(data.byteLength + 1);
  11237. datapart.set(dataslice, 1); // Add a zero char at the start of the send, this will indicate that it's not a JSON command.
  11238. files.send(datapart);
  11239. } else {
  11240. files.send(dataslice); // The data does not start with 0 or 123 "{" so it can't be confused for JSON.
  11241. }
  11242. uploadFile.xptr = end;
  11243. Q('d2progressBar').value = end;
  11244. if (uploadFile.xptr >= uploadFile.xfile.size) {
  11245. files.sendText(JSON.stringify({ action: 'uploaddone', reqid: uploadFile.xfilePtr }));
  11246. } else {
  11247. if (uploadFile.xdataPriming > 0) { uploadFile.xdataPriming--; p13uploadNextPart(true); }
  11248. }
  11249. };
  11250. uploadFile.xreader.readAsArrayBuffer(uploadFile.xfile.slice(uploadFile.xptr, end));
  11251. }
  11252. }
  11253. //
  11254. // DEVICE EVENTS
  11255. //
  11256. var currentDeviceEvents = null;
  11257. function deviceEventsUpdate() {
  11258. var x = '', dateHeader = null;
  11259. for (var i in currentDeviceEvents) {
  11260. var event = currentDeviceEvents[i], time = new Date(event.time);
  11261. if (event.msg) {
  11262. if (event.h == null) { event.h = Math.random(); }
  11263. if (printDate(time) != dateHeader) {
  11264. if (dateHeader != null) x += '</table>';
  11265. dateHeader = printDate(time);
  11266. x += '<table class=p3eventsTable cellpadding=0 cellspacing=0><tr><td colspan=4 class=DevSt>' + dateHeader + '</td></tr>';
  11267. }
  11268. var icon = 'si3';
  11269. if (event.etype == 'user') icon = 'm2';
  11270. if (event.etype == 'server') icon = 'si3';
  11271. var msg;
  11272. if ((event.msgid == null) || (eventsMessageId[event.msgid] == null)) {
  11273. if (typeof event.msg == 'string') { msg = EscapeHtml(event.msg).split('(R)').join('&reg;'); } else { msg = ''; }
  11274. } else {
  11275. msg = eventsMessageId[event.msgid];
  11276. for (var i in event.msgArgs) {
  11277. var xx = event.msgArgs[i];
  11278. if ((typeof xx == 'string') && (xx.indexOf('DATETIME:') == 0)) { xx = printFlexDateTime(new Date(parseInt(xx.substring(9)))); }
  11279. msg = msg.split('{' + i + '}').join(xx);
  11280. }
  11281. //if (event.msgArgs != null) { for (var i in event.msgArgs) { msg = msg.split('{' + i + '}').join(event.msgArgs[i]); } }
  11282. msg = EscapeHtml(msg).split('(R)').join('&reg;');
  11283. }
  11284. if (event.username) {
  11285. var guestname = '';
  11286. if (event.guestname) { guestname = ' / <span title="' + "This is a guest sharing session" + '">' + EscapeHtml(event.guestname) + '</span>'; }
  11287. if ((userinfo.siteadmin & 2) && (event.userid)) {
  11288. msg = '<a href=# onclick=\'gotoUser("' + encodeURIComponentEx(event.userid) + '");haltEvent(event);\'>' + EscapeHtml(event.username) + '</a>' + guestname + ' &rarr; ' + msg;
  11289. } else {
  11290. msg = EscapeHtml(event.username) + guestname + ' &rarr; ' + msg;
  11291. }
  11292. }
  11293. if (event.etype == 'relay' || event.action == 'relaylog') icon = 'relayIcon16';
  11294. x += '<tr onclick=showEventDetails(' + event.h + ',1) onmouseover=eventMouseHover(this,1) onmouseout=eventMouseHover(this,0) style=cursor:pointer><td style=width:18px><div class=' + icon + '></div></td><td class=g1>&nbsp;</td><td class=style10>' + printTime(time) + ' - ' + msg + '</td><td class=g2>&nbsp;</td></tr><tr style=height:2px></tr>';
  11295. }
  11296. }
  11297. if (dateHeader != null) x += '</table>';
  11298. if (x == '') x = '<br><i>' + "No Events Found" + '</i><br><br>';
  11299. QH('p16events', x);
  11300. }
  11301. function refreshDeviceEvents() {
  11302. if (p16filterevents.value != "") {
  11303. meshserver.send({ action: 'events', nodeid: currentNode._id, limit: parseInt(p16limitdropdown.value), filter: p16filterevents.value });
  11304. } else {
  11305. meshserver.send({ action: 'events', nodeid: currentNode._id, limit: parseInt(p16limitdropdown.value) });
  11306. }
  11307. }
  11308. function showEventDetails(h, mode) {
  11309. if (xxdialogMode) return false;
  11310. var eventList, xevent;
  11311. if (mode == 1) { eventList = currentDeviceEvents; }
  11312. if (mode == 2) { eventList = events; }
  11313. if (mode == 3) { eventList = currentUserEvents; }
  11314. for (var i in eventList) { if (eventList[i].h == h) { xevent = eventList[i]; break; } }
  11315. if (xevent) {
  11316. var x = '<div style=overflow-y:auto;max-height:300px>';
  11317. for (var i in xevent) {
  11318. if ((i == 'h') || (i == '_id') || (i == 'ids') || (i == 'domain') || (xevent[i] == null)) continue;
  11319. if (typeof xevent[i] == 'object') {
  11320. x += addHtmlValue3(EscapeHtml(i), EscapeHtml(JSON.stringify(xevent[i])));
  11321. } else {
  11322. x += addHtmlValue3(EscapeHtml(i), EscapeHtml(xevent[i]));
  11323. }
  11324. }
  11325. x += '</div>';
  11326. setDialogMode(2, "Event Details", 9, null, x);
  11327. }
  11328. }
  11329. //
  11330. // DEVICE DETAILS
  11331. //
  11332. var deviceDetailsStats = null;
  11333. var deviceDetailsStatsTimer = null;
  11334. var deviceDetailsStatsData = [];
  11335. var deviceDetailsConfig = {
  11336. type: 'line',
  11337. data: {
  11338. labels: [],
  11339. datasets: [
  11340. { label: "CPU", backgroundColor: 'rgba(134, 16, 158, .5)', borderColor: 'rgb(134, 16, 158)', data: [], fill: true },
  11341. { label: "Memory", backgroundColor: 'rgba(255, 99, 132, .5)', borderColor: 'rgb(255, 99, 132)', data: [], fill: true }
  11342. ] },
  11343. options: {
  11344. events: ['click'],
  11345. animation: false,
  11346. responsive: true,
  11347. maintainAspectRatio: false,
  11348. tooltips: { enabled: false },
  11349. elements: { line: { cubicInterpolationMode: 'monotone' } },
  11350. scales: {
  11351. x: { type: 'linear', display: true, title: { display: false, text: '' }, min: 0, max: 100, reverse: true },
  11352. y: { type: 'linear', display: true, title: { display: false, text: '' }, min: 0, max: 100 }
  11353. }
  11354. }
  11355. };
  11356. function deskToggleCpuGraph() {
  11357. var visible = (QS('p17graph').display == 'none');
  11358. if (visible) {
  11359. QV('p17graph', true);
  11360. window.deviceDetailsStatsChart = new Chart(document.getElementById('deviceDetailsStats').getContext('2d'), deviceDetailsConfig);
  11361. deviceDetailsStatsTimer = setInterval(deviceDetailsStatsTimerFunc, 2000);
  11362. deviceDetailsStatsTimerFunc();
  11363. } else deviceDetailsStatsClear();
  11364. }
  11365. function refreshDetails() {
  11366. meshserver.send({ action: 'msg', type: 'sysinfo', nodeid: currentNode._id });
  11367. }
  11368. function deviceDetailsStatsClear() {
  11369. QV('p17graph', false);
  11370. if (window.deviceDetailsStatsChart != null) { window.deviceDetailsStatsChart.destroy(); delete window.deviceDetailsStatsChart; }
  11371. if (deviceDetailsStatsTimer != null) { clearInterval(deviceDetailsStatsTimer); deviceDetailsStatsTimer = null; }
  11372. deviceDetailsStatsData = [];
  11373. deviceDetailsStatsDraw();
  11374. QV('extraGraphValues', false);
  11375. QH('extraGraphValues', '');
  11376. }
  11377. function deviceDetailsStatsTimerFunc() {
  11378. if (currentNode != null) { meshserver.send({ action: 'msg', type: 'cpuinfo', nodeid: currentNode._id }); }
  11379. }
  11380. function deviceDetailsStatsDraw(message) {
  11381. var now = Date.now() / 1000, oldLimit = (now - 100);
  11382. while ((deviceDetailsStatsData.length > 0) && (deviceDetailsStatsData[0][0] < (oldLimit - 5))) { deviceDetailsStatsData.shift(); }
  11383. var cpu = [], memory = [];
  11384. for (var i in deviceDetailsStatsData) {
  11385. var d = deviceDetailsStatsData[i];
  11386. cpu.push({ x: 100 - (d[0] - oldLimit), y: d[1] });
  11387. memory.push({ x: 100 - (d[0] - oldLimit), y: d[2] });
  11388. }
  11389. deviceDetailsConfig.data.datasets[0].data = cpu;
  11390. deviceDetailsConfig.data.datasets[1].data = memory;
  11391. if (window.deviceDetailsStatsChart) { window.deviceDetailsStatsChart.update(); }
  11392. // Display any additional live values
  11393. if (message != null) {
  11394. if (Array.isArray(message.thermals) && (message.thermals.length > 0)) {
  11395. var x = '&nbsp;';
  11396. for (var i in message.thermals) { x += '<div class=thermalSensor title="'+message.thermals[i].InstanceName+'">' + parseFloat(message.thermals[i].CurrentTemperature).toFixed(2) + '&deg;C / ' + parseFloat((message.thermals[i].CurrentTemperature * 1.8) + 32).toFixed(2) + '&deg;F' + '</div>'; }
  11397. QV('extraGraphValues', true);
  11398. QH('extraGraphValues', x);
  11399. }
  11400. }
  11401. }
  11402. var DeviceDetailsHardware = null;
  11403. var DeviceDetailsNetwork = null;
  11404. var DeviceDetailsNodeId = null;
  11405. function updateDeviceDetails(node, hardware, network) {
  11406. if (currentNode == null) return;
  11407. if (node == null) { node = currentNode; }
  11408. if (currentNode._id != node._id) return;
  11409. if (DeviceDetailsNodeId != node._id) { DeviceDetailsHardware = null; DeviceDetailsNetwork = null; DeviceDetailsNodeId = node._id; }
  11410. if (hardware != null) { DeviceDetailsHardware = hardware; }
  11411. if (network != null) { DeviceDetailsNetwork = network; }
  11412. hardware = DeviceDetailsHardware;
  11413. network = DeviceDetailsNetwork;
  11414. if (hardware == null) { hardware = {}; }
  11415. if (network == null) { network = {}; }
  11416. var sections = [], s = {};
  11417. // Operating System
  11418. var x = '';
  11419. if (node.rname) { x += addDetailItem("Name", EscapeHtml(node.rname), s); }
  11420. if (hardware.windows && hardware.windows.osinfo && hardware.windows.osinfo.Description) { x += addDetailItem("Description", EscapeHtml(hardware.windows.osinfo.Description), s); }
  11421. if (node.osdesc) { x += addDetailItem("Version", EscapeHtml(node.osdesc), s); }
  11422. if (hardware.windows && hardware.windows.osinfo) {
  11423. var m = hardware.windows.osinfo;
  11424. if (m.OSArchitecture) {
  11425. if (m.OSArchitecture.startsWith('32')) { x += addDetailItem("Architecture", "32-bit", s); }
  11426. else if (m.OSArchitecture.startsWith('64')) { x += addDetailItem("Architecture", "64-bit", s); }
  11427. else { x += addDetailItem("Architecture", EscapeHtml(m.OSArchitecture), s); }
  11428. }
  11429. if(m.LastBootUpTime){
  11430. var thedate = {
  11431. year: parseInt(m.LastBootUpTime.substring(0, 4)),
  11432. month: parseInt(m.LastBootUpTime.substring(4, 6)) - 1, // Months are 0-based in JavaScript (0 - January, 11 - December)
  11433. day: parseInt(m.LastBootUpTime.substring(6, 8)),
  11434. hours: parseInt(m.LastBootUpTime.substring(8, 10)),
  11435. minutes: parseInt(m.LastBootUpTime.substring(10, 12)),
  11436. seconds: parseInt(m.LastBootUpTime.substring(12, 14)),
  11437. };
  11438. const date = printDateTime(new Date(thedate.year, thedate.month, thedate.day, thedate.hours, thedate.minutes, thedate.seconds));
  11439. x += addDetailItem("Last Boot Up Time", date);
  11440. }
  11441. if(m.Domain){ x += addDetailItem((m.PartOfDomain ? "Domain" : "Workgroup"), EscapeHtml(m.Domain), s); }
  11442. }
  11443. if(hardware.linux && hardware.linux.LastBootUpTime){
  11444. var lastBootUpTime = new Date(hardware.linux.LastBootUpTime);
  11445. const date = printDateTime(lastBootUpTime);
  11446. x += addDetailItem("Last Boot Up Time", date);
  11447. }
  11448. if(hardware.darwin && hardware.darwin.LastBootUpTime){
  11449. var lastBootUpTime = new Date(hardware.darwin.LastBootUpTime * 1000); // must times by 1000 even tho timestamp is correct?
  11450. const date = printDateTime(lastBootUpTime);
  11451. x += addDetailItem("Last Boot Up Time", date);
  11452. }
  11453. if (x != '') { sections.push({ name: "Operating System", html: x, img: 'software64.png'}); }
  11454. // MeshAgent
  11455. if (node.agent) {
  11456. var x = '';
  11457. if ((node.agent != null) && (node.agent.id != null) && (node.agent.ver != null)) {
  11458. var str = '';
  11459. if (node.agent.id <= agentsStr.length) { str = agentsStr[node.agent.id]; } else { str = agentsStr[0]; }
  11460. if (node.agent.ver != 0) { str += ' v' + node.agent.ver; }
  11461. if (node.agent.id == 14) { str = node.agent.core; }
  11462. x += addDetailItem("Mesh Agent", str);
  11463. }
  11464. if (node.firstconnect) { x += addDetailItem("First agent connection", printDateTime(new Date(node.firstconnect))); }
  11465. if ((node.conn & 1) != 0) {
  11466. x += addDetailItem("Last agent connection", "Connected now");
  11467. } else {
  11468. if (node.lastconnect) { x += addDetailItem("Last agent connection", printDateTime(new Date(node.lastconnect))); }
  11469. }
  11470. if (node.lastaddr) {
  11471. var splitip = node.lastaddr.split(':');
  11472. if (splitip.length > 2) {
  11473. // IPv6
  11474. x += addDetailItem("Last agent address", '<a href="https://iplocation.com/?ip=' + splitip.slice(0, -1).join(':') + '" rel="noreferrer noopener" target="MeshIPLoopup">' + splitip.slice(0, -1).join(':') + '</a>');
  11475. } else {
  11476. // IPv4
  11477. if (isPrivateIP(node.lastaddr)) {
  11478. x += addDetailItem("Last agent address", splitip[0]);
  11479. } else {
  11480. x += addDetailItem("Last agent address", '<a href="https://iplocation.com/?ip=' + splitip[0] + '" rel="noreferrer noopener" target="MeshIPLoopup">' + splitip[0] + '</a>');
  11481. }
  11482. }
  11483. }
  11484. if (hardware.agentvers != null) {
  11485. if (hardware.agentvers.compileTime) {
  11486. var d = Date.parse(hardware.agentvers.compileTime)
  11487. x += addDetailItem("Compile time", isNaN(d)?hardware.agentvers.compileTime:printDateTime(new Date(d)));
  11488. }
  11489. }
  11490. if (hardware.time != null) {
  11491. x += addDetailItem("Last details update", printDateTime(new Date(hardware.time)));
  11492. }
  11493. if (x != '') { sections.push({ name: "Mesh Agent", html: x, img: 'meshagent64.png'}); }
  11494. }
  11495. // Mobile
  11496. if (hardware.mobile) {
  11497. var x = '';
  11498. if (hardware.mobile.brand && hardware.mobile.model) { x += addDetailItem("Model", EscapeHtml(hardware.mobile.brand + ', ' + hardware.mobile.model), s); }
  11499. if (hardware.mobile.device) { x += addDetailItem("Device", EscapeHtml(hardware.mobile.device), s); }
  11500. if (hardware.mobile.bootloader) { x += addDetailItem("Bootloader", EscapeHtml(hardware.mobile.bootloader), s); }
  11501. if (hardware.mobile.id) { x += addDetailItem("Identifier", EscapeHtml(hardware.mobile.id), s); }
  11502. if (hardware.mobile.host) { x += addDetailItem("Hostname", EscapeHtml(hardware.mobile.host), s); }
  11503. if (hardware.mobile.androidapi && hardware.mobile.androidrelease) { x += addDetailItem("Android Version", EscapeHtml(hardware.mobile.androidrelease + ', API Level ' + hardware.mobile.androidapi), s); }
  11504. if (x != '') { sections.push({ name: "Mobile Device", html: x, img: 'mobile64.png'}); }
  11505. }
  11506. // Networking (old style)
  11507. if (network.netif != null) {
  11508. // Compact interfaces that have the same MAC addresses
  11509. var macInterfaces = {}
  11510. for (var i in network.netif) {
  11511. var m = network.netif[i];
  11512. if (typeof m.mac != 'string') continue;
  11513. if (macInterfaces[m.mac] == null) {
  11514. macInterfaces[m.mac] = m;
  11515. macInterfaces[m.mac].ipv4layer = [ { v4addr: m.v4addr, v4mask: m.v4mask, v4gateway: m.v4gateway }];
  11516. } else {
  11517. macInterfaces[m.mac].ipv4layer.push({ v4addr: m.v4addr, v4mask: m.v4mask, v4gateway: m.v4gateway });
  11518. }
  11519. }
  11520. // Display one network interface for each MAC address
  11521. var x = '';
  11522. x += '<table style=width:100%>';
  11523. for (var i in macInterfaces) {
  11524. var m = macInterfaces[i];
  11525. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  11526. x += '<div style=margin-bottom:3px><b>' + EscapeHtml(m.name + (m.dnssuffix?(', ' + m.dnssuffix):'')) + '</b></div>';
  11527. if (m.desc) { x += addDetailItem("Description", EscapeHtml(m.desc).split('(R)').join('&reg;')); }
  11528. //if (m.dnssuffix) { x += addDetailItem("DNS Suffix", m.dnssuffix); }
  11529. if (m.mac) {
  11530. if (m.gatewaymac) {
  11531. x += addDetailItem("MAC Layer", format("MAC: {0}, Gateway: {1}", EscapeHtml(m.mac), EscapeHtml(m.gatewaymac)));
  11532. } else {
  11533. if (m.mac) { x += addDetailItem("MAC Layer", format("MAC: {0}", EscapeHtml(m.mac))); }
  11534. }
  11535. }
  11536. for (var j in m.ipv4layer) {
  11537. var ipv4layer = m.ipv4layer[j];
  11538. if (ipv4layer.v4gateway && ipv4layer.v4mask) {
  11539. x += addDetailItem("IPv4 Layer", format("IP: {0}, Mask: {1}, Gateway: {2}", EscapeHtml(ipv4layer.v4addr), EscapeHtml(ipv4layer.v4mask), EscapeHtml(ipv4layer.v4gateway)));
  11540. } else {
  11541. if (ipv4layer.v4addr) { x += addDetailItem("IPv4 Layer", format("IP: {0}", EscapeHtml(ipv4layer.v4addr))); }
  11542. }
  11543. }
  11544. x += '</div></td></tr>';
  11545. }
  11546. x += '</table>';
  11547. if (x != '') { sections.push({ name: "Networking", html: x, img: 'networking64.png'}); }
  11548. }
  11549. // Networking
  11550. if (network.netif2 != null) {
  11551. // Display one network interface for each MAC address
  11552. var x = '';
  11553. x += '<table style=width:100%>';
  11554. for (var i in network.netif2) {
  11555. var m = network.netif2[i];
  11556. 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;
  11557. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  11558. var ifTitle = '';
  11559. if (i != null) ifTitle += i;
  11560. if (m[0].fqdn != null && m[0].fqdn != '') ifTitle += ', ' + m[0].fqdn;
  11561. if (ifTitle.length > 0) { x += '<div style=margin-bottom:3px><b>' + EscapeHtml(ifTitle) + '</b></div>'; }
  11562. if (m.desc) { x += addDetailItem("Description", EscapeHtml(m.desc).split('(R)').join('&reg;')); }
  11563. //if (m.dnssuffix) { x += addDetailItem("DNS Suffix", m.dnssuffix); }
  11564. if (typeof m[0].mac == 'string') {
  11565. if (m[0].gatewaymac) {
  11566. x += addDetailItem("MAC Layer", format("MAC: {0}, Gateway: {1}", EscapeHtml(m[0].mac), EscapeHtml(m[0].gatewaymac)));
  11567. } else {
  11568. x += addDetailItem("MAC Layer", format("MAC: {0}", EscapeHtml(m[0].mac)));
  11569. }
  11570. }
  11571. if (typeof m[0].speed == 'number' && (m[0].speed != 9223372036854775807 && m[0].speed > 0)) {
  11572. x += addDetailItem("Interface Speed", format("{0}", getNetworkSpeed(m[0].speed)));
  11573. }
  11574. for (var j = 0; j < m.length; j++) {
  11575. var iplayer = m[j], items = [];
  11576. if (iplayer.address) { items.push(format("IP: {0}", EscapeHtml(iplayer.address))); }
  11577. if (iplayer.netmask) { items.push(format("Mask: {0}", EscapeHtml(iplayer.netmask))); }
  11578. if (iplayer.gateway) { items.push(format("Gateway: {0}", EscapeHtml(iplayer.gateway))); }
  11579. if (items.length > 0) {
  11580. if (iplayer.family == 'IPv4') { x += addDetailItem("IPv4 Layer", items.join(", ")); }
  11581. if (iplayer.family == 'IPv6') { x += addDetailItem("IPv6 Layer", items.join(", ")); }
  11582. }
  11583. }
  11584. x += '</div></td></tr>';
  11585. }
  11586. if (hardware.network && hardware.network.dns) {
  11587. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  11588. x += addDetailItem('<b>' + "DNS Servers" + '</b>', hardware.network.dns.join(", "));
  11589. x += '</div></td></tr>';
  11590. }
  11591. x += '</table>';
  11592. if (x != '') { sections.push({ name: "Networking", html: x, img: 'networking64.png'}); }
  11593. }
  11594. // Attribute: Intel AMT
  11595. if (node.intelamt != null) {
  11596. var x = '';
  11597. x += addDetailItem("Version", (node.intelamt.ver)?('v' + EscapeHtml(node.intelamt.ver)):('<i>' + "Unknown" + '</i>'), s);
  11598. var hardwareidentifier = hardware.identifiers && hardware.identifiers.product_uuid;
  11599. x += addDetailItem("Identifier", (node.intelamt.uuid) ? (EscapeHtml(node.intelamt.uuid)) : (hardwareidentifier ? EscapeHtml(hardwareidentifier) : '<i>' + "Unknown" + '</i>'), s);
  11600. var provisioningStates = { 0: nobreak("Not Activated"), 1: nobreak("Pre-activation"), 2: nobreak("Activated") };
  11601. var provisioningMode = '';
  11602. 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)"); } }
  11603. x += addDetailItem("Provisioning State", ((node.intelamt.state)?(provisioningStates[node.intelamt.state]):('<i>' + "Unknown" + '</i>')) + provisioningMode, s);
  11604. x += addDetailItem("Security", (node.intelamt.tls == 1)?"Secured using TLS":"TLS is not setup", s);
  11605. // Check that the Intel AMT user is setup and there is no warnings (1 = invalid credentials, 8 = trying)
  11606. x += addDetailItem("Admin Credentials", ((node.intelamt.user) == null || (node.intelamt.user == '') || ((node.intelamt.warn != null) && ((node.intelamt.warn & 9) != 0)))?"Not Known":"Known", s);
  11607. if (x != '') {
  11608. if ((typeof node.intelamt.sku == 'number') && ((node.intelamt.sku & 16) != 0)) {
  11609. sections.push({ name: "Intel&reg; Standard Manageability (Intel&reg; SM)", html: x, img: 'amt64.png' });
  11610. } else {
  11611. sections.push({ name: "Intel&reg; Active Management Technology (Intel&reg; AMT)", html: x, img: 'amt64.png' });
  11612. }
  11613. }
  11614. }
  11615. if (hardware.identifiers) {
  11616. var x = '', ident = hardware.identifiers;
  11617. // BIOS
  11618. if (ident.bios_vendor) { x += addDetailItem("Vendor", EscapeHtml(ident.bios_vendor), s); }
  11619. if (ident.bios_version) { x += addDetailItem("Version", EscapeHtml(ident.bios_version), s); }
  11620. if (ident.bios_serial) { x += addDetailItem("Serial", EscapeHtml(ident.bios_serial), s); }
  11621. if (ident.bios_mode) { x += addDetailItem("Mode", EscapeHtml(ident.bios_mode), s); }
  11622. if (x != '') { sections.push({ name: "BIOS", html: x, img: 'chip64.png' }); }
  11623. // Motherboard
  11624. x = '';
  11625. if (ident.board_vendor) { x += addDetailItem("Vendor", EscapeHtml(ident.board_vendor), s); }
  11626. if (ident.board_name) { x += addDetailItem("Name", EscapeHtml(ident.board_name), s); }
  11627. if (ident.board_serial && (ident.board_serial != '')) { x += addDetailItem("Serial", EscapeHtml(ident.board_serial), s); }
  11628. if (ident.board_version) { x += addDetailItem("Version", EscapeHtml(ident.board_version), s); }
  11629. if (ident.product_uuid) { x += addDetailItem("Identifier", EscapeHtml(ident.product_uuid), s); }
  11630. if (ident.cpu_name) { x += addDetailItem("CPU", EscapeHtml(ident.cpu_name).split('(TM)').join('&trade;').split('(R)').join('&reg;'), s); }
  11631. 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); } }
  11632. if (x != '') { sections.push({ name: "Motherboard", html: x, img: 'motherboard64.png'}); }
  11633. // System
  11634. x = '';
  11635. if (ident.chassis_manufacturer) { x += addDetailItem("Manufacturer", EscapeHtml(ident.chassis_manufacturer), s); }
  11636. if (ident.product_name) { x += addDetailItem("Product Name", EscapeHtml(ident.product_name), s); }
  11637. if (ident.chassis_serial) { x += addDetailItem("Serial", EscapeHtml(ident.chassis_serial), s); }
  11638. if (ident.chassis_assettag) { x += addDetailItem("Asset Tag", EscapeHtml(ident.chassis_assettag), s); }
  11639. if (x != '') { sections.push({ name: "System", html: x, img: 'system64.png' }); }
  11640. }
  11641. // TPM
  11642. if (hardware.tpm) {
  11643. var x = '', tpm = hardware.tpm;
  11644. if (tpm.SpecVersion) { x += addDetailItem("SpecVersion", parseFloat(EscapeHtml(tpm.SpecVersion)).toFixed(1), s); }
  11645. if (tpm.ManufacturerId) { x += addDetailItem("Identifier", EscapeHtml(tpm.ManufacturerId), s); }
  11646. if (tpm.ManufacturerVersion) { x += addDetailItem("Version", EscapeHtml(tpm.ManufacturerVersion), s); }
  11647. if (tpm.IsActivated != null) { x += addDetailItem("Activated", (tpm.IsActivated ? "Yes" : "No"), s); }
  11648. if (tpm.IsEnabled != null) { x += addDetailItem("Enabled", (tpm.IsEnabled ? "Yes" : "No"), s); }
  11649. if (tpm.IsOwned != null) { x += addDetailItem("Owned", (tpm.IsOwned ? "Yes" : "No"), s); }
  11650. if (x != '') { sections.push({ name: "TPM", html: x, img: 'tpm64.png' }); }
  11651. }
  11652. // Batteries
  11653. if (hardware.battery) {
  11654. var x = '';
  11655. x += '<table style=width:100%>';
  11656. for (var i in hardware.battery) {
  11657. var battery = hardware.battery[i];
  11658. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  11659. x += '<div style=margin-bottom:3px><b>' + EscapeHtml(battery.DeviceName ? battery.DeviceName : "Unknown") + '</b></div>';
  11660. if (battery.CycleCount) { x += addDetailItem("Cycle Count", EscapeHtml(battery.CycleCount), s); }
  11661. if (battery.FullChargedCapacity) { x += addDetailItem("Full Charged Capacity", format("{0} mWh", battery.FullChargedCapacity), s); }
  11662. if (battery.EstimatedRuntime) { x += addDetailItem("Estimated Runtime", format("{0} minutes", Math.floor((battery.EstimatedRuntime / 60))), s); }
  11663. if (battery.Chemistry) { x += addDetailItem("Chemistry", EscapeHtml(battery.Chemistry), s); }
  11664. if (battery.DesignedCapacity) { x += addDetailItem("Design Capacity", format("{0} mWh", battery.DesignedCapacity), s); }
  11665. if (battery.ManufactureDate) { x += addDetailItem("Manufacture Date", EscapeHtml(battery.ManufactureDate), s); }
  11666. if (battery.ManufactureName) { x += addDetailItem("Manufacture Name", EscapeHtml(battery.ManufactureName), s); }
  11667. if (battery.SerialNumber) { x += addDetailItem("Serial Number", EscapeHtml(battery.SerialNumber), s); }
  11668. if (battery.ChargeRate) { x += addDetailItem("Charge Rate", format("{0} mW", battery.ChargeRate), s); }
  11669. if (battery.Charging != null) { x += addDetailItem("Charging", (battery.Charging ? "Yes" : "No"), s); }
  11670. if (battery.DischargeRate) { x += addDetailItem("Discharge Rate", format("{0} mW", battery.DischargeRate), s); }
  11671. if (battery.Discharging != null) { x += addDetailItem("Discharging", (battery.Discharging ? "Yes" : "No"), s); }
  11672. if (battery.RemainingCapacity) { x += addDetailItem("Remaining Capacity", format("{0} mWh", battery.RemainingCapacity), s); }
  11673. if (battery.Voltage) { x += addDetailItem("Voltage", format("{0} V", (battery.Voltage / 1000)), s); }
  11674. if (battery.Health) { x += addDetailItem("Health", format("{0} %", battery.Health), s); }
  11675. if (battery.BatteryCharge) { x += addDetailItem("Battery Charge", format("{0} %", battery.BatteryCharge), s); }
  11676. x += '</div>';
  11677. }
  11678. x += '</table>';
  11679. if (x != '') { sections.push({ name: "Battery", html: x, img: 'battery64.png'}); }
  11680. }
  11681. if (hardware.windows) {
  11682. if (hardware.windows.memory && (hardware.windows.memory.length > 0)) {
  11683. var x = '';
  11684. // Sort Memory
  11685. hardware.windows.memory.sort(function(a, b) { if (a.BankLabel > b.BankLabel) return 1; if (a.BankLabel < b.BankLabel) return -1; return 0; });
  11686. x += '<table style=width:100%>';
  11687. for (var i in hardware.windows.memory) {
  11688. var m = hardware.windows.memory[i];
  11689. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  11690. x += '<div style=margin-bottom:3px><b>' + EscapeHtml((m.BankLabel ? m.BankLabel : (m.DeviceLocator ? m.DeviceLocator : 'Unknown'))) + '</b></div>';
  11691. if (m.Capacity && m.Speed) { x += addDetailItem("Capacity / Speed", format("{0} Mb, {1} Mhz", (m.Capacity / 1024 / 1024), m.Speed), s); }
  11692. else if (m.Capacity) { x += addDetailItem("Capacity", format("{0} Mb", (m.Capacity / 1024 / 1024)), s); }
  11693. if (m.PartNumber) { x += addDetailItem("Part Number", EscapeHtml((m.Manufacturer && m.Manufacturer != 'Undefined')?(m.Manufacturer + ', '):'') + EscapeHtml(m.PartNumber), s); }
  11694. x += '</div>';
  11695. }
  11696. x += '</table>';
  11697. if (x != '') { sections.push({ name: "Memory", html: x, img: 'ram64.png'}); }
  11698. }
  11699. }
  11700. if (hardware.linux) {
  11701. if (hardware.linux.memory && (hardware.linux.memory.Memory_Device.length > 0)) {
  11702. var x = '';
  11703. // Sort Memory
  11704. 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; });
  11705. x += '<table style=width:100%>';
  11706. for (var i in hardware.linux.memory.Memory_Device) {
  11707. var m = hardware.linux.memory.Memory_Device[i];
  11708. if(m.Size && (m.Size == 'No Module Installed')) continue;
  11709. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  11710. x += '<div style=margin-bottom:3px><b>' + EscapeHtml((m.Locator ? m.Locator : 'Unknown')) + '</b></div>';
  11711. if (m.Size && m.Speed) { x += addDetailItem("Capacity / Speed", format("{0}, {1}", m.Size, m.Speed), s); }
  11712. else if (m.Size) { x += addDetailItem("Capacity", format("{0}", (m.Size)), s); }
  11713. if (m.PartNumber) { x += addDetailItem("Part Number", EscapeHtml((m.Manufacturer && m.Manufacturer != 'Undefined')?(m.Manufacturer + ', '):'') + EscapeHtml(m.PartNumber), s); }
  11714. x += '</div>';
  11715. }
  11716. x += '</table>';
  11717. if (x != '') { sections.push({ name: "Memory", html: x, img: 'ram64.png'}); }
  11718. }
  11719. }
  11720. if (hardware.darwin) {
  11721. if (hardware.darwin.memory && (hardware.darwin.memory.length > 0)) {
  11722. var x = '';
  11723. x += '<table style=width:100%>';
  11724. for (var i in hardware.darwin.memory) {
  11725. var m = hardware.darwin.memory[i];
  11726. if(m.Size && (m.Size == 'No Module Installed')) continue;
  11727. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  11728. x += '<div style=margin-bottom:3px><b>' + EscapeHtml((m.DeviceLocator ? m.DeviceLocator : 'Unknown')) + '</b></div>';
  11729. if (m.Size && m.Speed) { x += addDetailItem("Capacity / Speed", format("{0}, {1}", m.Size, m.Speed), s); }
  11730. else if (m.Size) { x += addDetailItem("Capacity", format("{0}", (m.Size)), s); }
  11731. if (m.PartNumber) { x += addDetailItem("Part Number", EscapeHtml((m.Manufacturer && m.Manufacturer != '')?(m.Manufacturer + ', '):'') + EscapeHtml(m.PartNumber), s); }
  11732. x += '</div>';
  11733. }
  11734. x += '</table>';
  11735. if (x != '') { sections.push({ name: "Memory", html: x, img: 'ram64.png'}); }
  11736. }
  11737. }
  11738. // Storage
  11739. if (hardware.identifiers && hardware.identifiers.storage_devices) {
  11740. var x = '';
  11741. // Sort Storage
  11742. ident.storage_devices.sort(function(a, b) { if (a.Caption > b.Caption) return 1; if (a.Caption < b.Caption) return -1; return 0; });
  11743. x += '<table style=width:100%>';
  11744. for (var i in ident.storage_devices) {
  11745. var m = ident.storage_devices[i];
  11746. if (m.Size) {
  11747. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  11748. x += '<div style=margin-bottom:3px><b>' + EscapeHtml(m.Caption) + '</b></div>';
  11749. if (m.Model && (m.Model != m.Caption)) { x += addDetailItem("Model", EscapeHtml(m.Model), s); }
  11750. if (m.Size) {
  11751. if ((typeof m.Size == 'string') && (parseInt(m.Size) == m.Size)) { m.Size = parseInt(m.Size); }
  11752. if (typeof m.Size == 'number') { x += addDetailItem("Capacity", format("{0} Mb", Math.floor(m.Size / 1024 / 1024)), s); }
  11753. if (typeof m.Size == 'string') { x += addDetailItem("Capacity", EscapeHtml(m.Size), s); }
  11754. }
  11755. if(hardware.windows && hardware.windows.drives && m.Model){
  11756. const foundObject = hardware.windows.drives.find(obj => obj['Model'] === m.Model);
  11757. if(foundObject && foundObject.Status) x += addDetailItem("Status", EscapeHtml(foundObject.Status), s);
  11758. }
  11759. x += '</div>';
  11760. }
  11761. }
  11762. x += '</table>';
  11763. if (x != '') { sections.push({ name: "Storage", html: x, img: 'storage64.png'}); }
  11764. }
  11765. // Volumes and Bitlocker
  11766. if (hardware.windows && hardware.windows.volumes) {
  11767. var x = '';
  11768. for (var i in hardware.windows.volumes) {
  11769. var m = hardware.windows.volumes[i];
  11770. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  11771. x += '<div style=margin-bottom:3px><b>' + i + ':' + (((m.name == null) || (m.name == '')) ? '' : (' - ' + EscapeHtml(m.name))) + '</b></div>';
  11772. if (m.size) {
  11773. var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
  11774. var j = parseInt(Math.floor(Math.log(Math.abs(m.size)) / Math.log(1024)), 10);
  11775. var fsize = (j === 0 ? `${m.size} ${sizes[j]}` : `${(m.size / (1024 ** j)).toFixed(2)} ${sizes[j]}`);
  11776. x += addDetailItem("Capacity", EscapeHtml(fsize), s);
  11777. }
  11778. if (m.sizeremaining) {
  11779. var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
  11780. var j = parseInt(Math.floor(Math.log(Math.abs(m.sizeremaining)) / Math.log(1024)), 10);
  11781. var fsize = (j === 0 ? `${m.sizeremaining} ${sizes[j]}` : `${(m.sizeremaining / (1024 ** j)).toFixed(2)} ${sizes[j]}`);
  11782. x += addDetailItem("Capacity Remaining", EscapeHtml(fsize), s);
  11783. }
  11784. if (m.type) {
  11785. var type = (m.removable == true ? "Removable" : (m.cdrom == true ? "CD-ROM" : ''));
  11786. x += addDetailItem("File System", (type != '' ? (type + ' / ') : '') + (m.type == 'Unknown' ? "Unknown" : EscapeHtml(m.type)), s);
  11787. }
  11788. if (m.protectionStatus || m.volumeStatus) {
  11789. var bitlockerState = [];
  11790. if (m.protectionStatus) bitlockerState.push("Enabled");
  11791. if (m.volumeStatus && m.volumeStatus == 'FullyDecrypted') bitlockerState.push("Fully Decrypted");
  11792. if (m.volumeStatus && m.volumeStatus == 'EncryptionInProgress') bitlockerState.push("Encryption In Progress");
  11793. if (m.volumeStatus && m.volumeStatus == 'FullyEncrypted') bitlockerState.push("Fully Encrypted");
  11794. bitlockerState = bitlockerState.join(' - ');
  11795. if (m.recoveryPassword) { bitlockerState += addKeyLink('', 'deviceDetailsShowBitlockerInfo(\"' + encodeURIComponentEx(i) + '\",\"' + encodeURIComponentEx(m.identifier) + '\",\"' + encodeURIComponentEx(m.recoveryPassword) + '\")'); }
  11796. x += addDetailItem("BitLocker", bitlockerState, s);
  11797. }
  11798. x += '</div>';
  11799. }
  11800. if (x != '') { sections.push({ name: "Storage Volumes", html: '<table style=width:100%>' + x + '</table>', img: 'storage64.png'}); }
  11801. }
  11802. // Linux Volumes
  11803. if (hardware.linux && hardware.linux.volumes) {
  11804. var x = '';
  11805. for (var i in hardware.linux.volumes) {
  11806. var m = hardware.linux.volumes[i];
  11807. if(m.mount_point.startsWith('/var/lib/docker/overlay2')) continue;
  11808. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  11809. x += '<div style=margin-bottom:3px><b>' + m.mount_point + '</b></div>';
  11810. if (m.size) {
  11811. var sizes = ['KB', 'MB', 'GB', 'TB'];
  11812. var j = parseInt(Math.floor(Math.log(Math.abs(m.size)) / Math.log(1024)), 10);
  11813. var fsize = (j === 0 ? `${m.size} ${sizes[j]}` : `${(m.size / (1024 ** j)).toFixed(2)} ${sizes[j]}`);
  11814. x += addDetailItem("Capacity", EscapeHtml(fsize), s);
  11815. }
  11816. if (m.available) {
  11817. if (Math.abs(m.available) == 0) {
  11818. var fsize = `0 KB`;
  11819. } else {
  11820. var sizes = ['KB', 'MB', 'GB', 'TB'];
  11821. var j = parseInt(Math.floor(Math.log(Math.abs(m.available)) / Math.log(1024)), 10);
  11822. var fsize = (j === 0 ? `${m.available} ${sizes[j]}` : `${(m.available / (1024 ** j)).toFixed(2)} ${sizes[j]}`);
  11823. }
  11824. x += addDetailItem("Capacity Remaining", EscapeHtml(fsize), s);
  11825. }
  11826. if (m.type) {
  11827. var type = (m.removable == true ? "Removable" : (m.cdrom == true ? "CD-ROM" : ''));
  11828. x += addDetailItem("File System", (type != '' ? (type + ' / ') : '') + (m.type == 'Unknown' ? "Unknown" : EscapeHtml(m.type)), s);
  11829. }
  11830. x += '</div>';
  11831. }
  11832. if (x != '') { sections.push({ name: "Storage Volumes", html: '<table style=width:100%>' + x + '</table>', img: 'storage64.png'}); }
  11833. }
  11834. // MacOS Volumes
  11835. if (hardware.darwin && hardware.darwin.volumes) {
  11836. var x = '';
  11837. for (var i in hardware.darwin.volumes) {
  11838. var m = hardware.darwin.volumes[i];
  11839. if(m.mount_point.startsWith('/var/lib/docker/overlay2')) continue;
  11840. x += '<tr><td><div class=style10 style=border-radius:5px;padding:8px>';
  11841. x += '<div style=margin-bottom:3px><b>' + m.mount_point + '</b></div>';
  11842. if (m.size) {
  11843. x += addDetailItem("Capacity", EscapeHtml(m.size), s);
  11844. }
  11845. if (m.available) {
  11846. x += addDetailItem("Capacity Remaining", EscapeHtml(m.available), s);
  11847. }
  11848. if (m.type) {
  11849. var type = (m.removable == true ? "Removable" : (m.cdrom == true ? "CD-ROM" : ''));
  11850. x += addDetailItem("File System", (type != '' ? (type + ' / ') : '') + (m.type == 'Unknown' ? "Unknown" : EscapeHtml(m.type)), s);
  11851. }
  11852. x += '</div>';
  11853. }
  11854. if (x != '') { sections.push({ name: "Storage Volumes", html: '<table style=width:100%>' + x + '</table>', img: 'storage64.png'}); }
  11855. }
  11856. // Render the sections
  11857. var x = '';
  11858. for (var i in sections) {
  11859. if (sections[i].img == null) {
  11860. x += '<div class=DevSt style=margin-bottom:3px;margin-left:16px><b>' + sections[i].name + '</b></div><div style=margin-bottom:10px;margin-left:16px>' + sections[i].html + '</div>';
  11861. } else {
  11862. x += '<table style=width:100%><tr>';
  11863. x += '<td style=width:64px;vertical-align:top><img src=images/details/' + sections[i].img + ' border=0 width=64 /></td>'; // height=12
  11864. x += '<td><div class=DevSt style=margin-bottom:3px;margin-left:16px><b>' + sections[i].name + '</b></div><div style=margin-bottom:10px;margin-left:16px>' + sections[i].html + '</div></td>';
  11865. x += '</tr></table>';
  11866. }
  11867. }
  11868. if (x == '') {
  11869. QH('p17info2', "No information for this device.");
  11870. } else {
  11871. QH('p17info2', x);
  11872. }
  11873. }
  11874. function deviceDetailsShowBitlockerInfo(drive, identifier, password) {
  11875. if (xxdialogMode) return false;
  11876. var x = '<div><p>' + "Identifier" + '</p><p style=user-select:text;font-weight:bold>' + (identifier ? decodeURIComponent(identifier) : "Unknown") + '</p>';
  11877. x += '<p>' + "Recovery Password" + '</p><p style=user-select:text;font-weight:bold>' + (password ? decodeURIComponent(password) : "Unknown") + '</p></div>';
  11878. setDialogMode(2, decodeURIComponent(drive) + ': ' + "BitLocker Information", 1, null, x, '');
  11879. }
  11880. //
  11881. // CONSOLE
  11882. //
  11883. function agentConsoleHandleKeys(e) {
  11884. if ((e.ctrlKey) || (e.altKey)) { return true; }
  11885. var processed = 0, box = Q('p15consoleText');
  11886. if (e.key) {
  11887. if (e.keyCode == 13 && consoleFocus == 0) { p15consoleSend(e); processed = 1; }
  11888. else if (e.keyCode == 8 && consoleFocus == 0) { var x = box.value; box.value = x.substring(0, x.length - 1); processed = 1; }
  11889. else if (e.keyCode == 27) { box.value = ''; processed = 1; }
  11890. else if ((e.keyCode == 38) || (e.keyCode == 40)) { // Arrow up || Arrow down
  11891. var hindex = consoleHistory.indexOf(box.value);
  11892. //console.log(hindex, consoleHistory);
  11893. if ((e.keyCode == 38) && ((consoleHistory.length - 1) > hindex)) { box.value = consoleHistory[hindex + 1]; }
  11894. else if ((e.keyCode == 40) && (hindex > 0)) { box.value = consoleHistory[hindex - 1]; }
  11895. else if ((e.keyCode == 40) && (hindex == 0)) { box.value = ''; }
  11896. processed = 1;
  11897. }
  11898. else if (e.key.length === 1) {
  11899. //box.value = ((box.value + e.key));
  11900. insertTextAtCursor(box, e.key);
  11901. processed = 1;
  11902. }
  11903. } else {
  11904. if (e.charCode != 0 && consoleFocus == 0) { box.value = ((box.value + String.fromCharCode(e.charCode))); processed = 1; }
  11905. }
  11906. if (processed > 0) { return haltEvent(e); }
  11907. }
  11908. // Insert text at the cursor location on the
  11909. function insertTextAtCursor(ctrl, val) {
  11910. if (document.selection) { ctrl.focus(); sel = document.selection.createRange(); sel.text = val; }
  11911. else if (ctrl.selectionStart || ctrl.selectionStart == '0') {
  11912. var start = ctrl.selectionStart, end = ctrl.selectionEnd;
  11913. ctrl.value = ctrl.value.substring(0, start) + val + ctrl.value.substring(end, ctrl.value.length);
  11914. ctrl.setSelectionRange(end + 1, end + 1);
  11915. } else { ctrl.value += myValue; }
  11916. }
  11917. var consoleNode;
  11918. var consoleServerText = '';
  11919. function setupConsole() {
  11920. if (xxcurrentView == 115) {
  11921. // Setup server console
  11922. var samenode = (consoleNode == 'server');
  11923. consoleNode = 'server';
  11924. QH('p15deviceName', "My Server Console");
  11925. QE('p15consoleText', true);
  11926. QH('p15statetext', '');
  11927. QH('p15coreName', '');
  11928. QV('p15outputselecttd', false);
  11929. if (samenode == false) {
  11930. QH('p15agentConsoleText', consoleServerText);
  11931. Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight;
  11932. }
  11933. } else {
  11934. // Setup the console
  11935. var samenode = (consoleNode == currentNode);
  11936. consoleNode = currentNode;
  11937. var mesh = meshes[consoleNode.meshid];
  11938. var rights = GetNodeRights(currentNode);
  11939. if ((rights & 16) != 0) {
  11940. if (consoleNode.consoleText == null) { consoleNode.consoleText = ''; }
  11941. if (samenode == false) {
  11942. QH('p15agentConsoleText', consoleNode.consoleText);
  11943. Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight;
  11944. }
  11945. var online = (((consoleNode.conn & 1) != 0) || ((consoleNode.conn & 16) != 0)) ? true : false;
  11946. var onlineText = ((consoleNode.conn & 1) != 0) ? "Agent is online" : "Agent is offline"
  11947. if ((consoleNode.conn & 16) != 0) { onlineText += ", MQTT is online" }
  11948. QH('p15statetext', onlineText);
  11949. QE('p15uploadCore', ((consoleNode.conn & 1) != 0));
  11950. QV('p15outputselecttd', ((consoleNode.conn & 16) != 0) || ((currentNode.pmt == 1) && ((features2 & 4) != 0)));
  11951. QV('p15outputselect2', ((consoleNode.conn & 16) != 0)); // MQTT channel
  11952. QV('p15outputselect3', ((currentNode.pmt == 1) && ((features2 & 4) != 0))); // Two-way Push Notification channel
  11953. var c = Q('p15outputselect').value;
  11954. if (((consoleNode.conn & 16) == 0) && (c == 2)) { c = 1; Q('p15outputselect').value = 1; }
  11955. if (((currentNode.pmt != 1) || ((features2 & 4) == 0)) && (c == 3)) { c = 1; Q('p15outputselect').value = 1; }
  11956. var active = false;
  11957. if (((consoleNode.conn & 1) != 0) && (c == 1)) { active = true; } // Agent
  11958. if (((consoleNode.conn & 16) != 0) && (c == 2)) { active = true; } // MQTT
  11959. if (((currentNode.pmt == 1) && ((features2 & 4) != 0)) && (c == 3)) { active = true; } // Push
  11960. QE('p15consoleText', active);
  11961. } else {
  11962. QH('p15statetext', "Access Denied");
  11963. QE('p15consoleText', false);
  11964. QE('p15uploadCore', false);
  11965. QV('p15outputselecttd', false);
  11966. }
  11967. QV('devListToolbarViewIcons3', ((consoleNode.conn & 1) != 0));
  11968. }
  11969. }
  11970. // Clear the console for this node
  11971. function p15consoleClear() {
  11972. QH('p15agentConsoleText', '');
  11973. Q('id_p15consoleClear').blur();
  11974. if (xxcurrentView == 115) {
  11975. consoleServerText = '';
  11976. } else {
  11977. consoleNode.consoleText = '';
  11978. }
  11979. }
  11980. // Send a command to the agent
  11981. var consoleHistory = [];
  11982. function p15consoleSend(e) {
  11983. if (e && e.keyCode != 13) return;
  11984. var v = Q('p15consoleText').value, t = '<div style=color:green>&gt; ' + EscapeHtml(v) + '<br/></div>';
  11985. if (xxcurrentView == 115) {
  11986. // Send the command to the server - TODO: In the future, we may support multiple servers.
  11987. consoleServerText += t;
  11988. meshserver.send({ action: 'serverconsole', value: v });
  11989. } else {
  11990. if (((consoleNode.conn & 16) != 0) && (Q('p15outputselect').value == 2)) {
  11991. // Send the command to MQTT
  11992. t = '<div style=color:orange>' + "MQTT" + '&gt; ' + EscapeHtml(v) + '<br/></div>';
  11993. consoleNode.consoleText += t;
  11994. meshserver.send({ action: 'sendmqttmsg', topic: 'console', nodeids: [ consoleNode._id ], msg: v });
  11995. } else if ((consoleNode.pmt == 1) && (Q('p15outputselect').value == 3) && ((features2 & 4) != 0)) {
  11996. // Send the command using push notification
  11997. t = '<div style=color:violet>' + "PUSH" + '&gt; ' + EscapeHtml(v) + '<br/></div>';
  11998. consoleNode.consoleText += t;
  11999. meshserver.send({ action: 'pushconsole', nodeid: consoleNode._id, console: v });
  12000. } else if ((consoleNode.conn & 1) != 0) {
  12001. // Send the command to the mesh agent
  12002. consoleNode.consoleText += t;
  12003. meshserver.send({ action: 'msg', type: 'console', nodeid: consoleNode._id, value: v });
  12004. }
  12005. }
  12006. Q('p15agentConsoleText').innerHTML += t;
  12007. Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight;
  12008. Q('p15consoleText').value = '';
  12009. // Add command to history list
  12010. if (v.length > 0) {
  12011. // Move this command to the top if it already exists
  12012. var j = consoleHistory.indexOf(v);
  12013. if (j >= 0) { consoleHistory.splice(j, 1); }
  12014. consoleHistory.unshift(v);
  12015. consoleHistory.splice(10);
  12016. }
  12017. }
  12018. // Handle Mesh Agent console data
  12019. function p15consoleReceive(node, data, source) {
  12020. if (node === 'serverconsole') {
  12021. // Server console data
  12022. data = '<div>' + EscapeHtml(data) + '</div>'
  12023. consoleServerText += data;
  12024. if (consoleNode == 'server') {
  12025. Q('p15agentConsoleText').innerHTML += data;
  12026. Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight;
  12027. }
  12028. } else {
  12029. // Agent console data
  12030. if (source == 'MQTT') { data = '<div style=color:red>' + "MQTT" + '&gt; ' + EscapeHtml(data) + '<br/></div>'; } else { data = '<div>' + EscapeHtml(data) + '</div>' }
  12031. if (node.consoleText == null) { node.consoleText = data; } else { node.consoleText += data; }
  12032. if (consoleNode == node) {
  12033. Q('p15agentConsoleText').innerHTML += data;
  12034. Q('p15agentConsoleText').scrollTop = Q('p15agentConsoleText').scrollHeight;
  12035. }
  12036. }
  12037. }
  12038. // Save console text to file
  12039. function p15downloadConsoleText() {
  12040. saveAs(new Blob([Q('p15agentConsoleText').innerText], { type: 'application/octet-stream' }), "console.txt");
  12041. }
  12042. // Called then user presses the "Change Core" button
  12043. function p15uploadCore(e) {
  12044. if (xxdialogMode) return;
  12045. if (e.shiftKey == true) { meshserver.send({ action: 'uploadagentcore', nodeids: [ consoleNode._id ], type: 'default' }); } // Upload default core
  12046. else if (e.altKey == true) { meshserver.send({ action: 'uploadagentcore', nodeids: [ consoleNode._id ], type: 'clear' }); } // Clear the core
  12047. else if (e.ctrlKey == true) { p15uploadCore2(); } // Upload the core from a file
  12048. else {
  12049. var htmlValue = '<select id=d3coreMode style=width:230px>' +
  12050. '<option value=1>' + "Upload default server core" + '</option>' +
  12051. '<option value=2>' + "Clear the core" + '</option>' +
  12052. '<option value=3>' + "Upload a core file" + '</option>' +
  12053. '<option value=4>' + "Soft disconnect agent" + '</option>' +
  12054. '<option value=5>' + "Hard disconnect agent" + '</option>' +
  12055. '<option value=6>' + "Upload recovery core" + '</option>' +
  12056. '<option value=7>' + "Upload tiny core" + '</option>' +
  12057. '<option value=8>' + "Restart agent service" + '</option>' +
  12058. '<option value=9>' + "Force agent update" + '</option></select>';
  12059. setDialogMode(2, "Perform Agent Action", 3, p15uploadCoreEx, addHtmlValue("Action", htmlValue));
  12060. }
  12061. }
  12062. function p15uploadCoreEx() {
  12063. if (Q('d3coreMode').value == 1) {
  12064. // Upload default core
  12065. meshserver.send({ action: 'uploadagentcore', nodeids: [ consoleNode._id ], type: 'default' });
  12066. } else if (Q('d3coreMode').value == 2) {
  12067. // Clear the core
  12068. meshserver.send({ action: 'uploadagentcore', nodeids: [ consoleNode._id ], type: 'clear' });
  12069. } else if (Q('d3coreMode').value == 3) {
  12070. // Upload file as core
  12071. p15uploadCore2();
  12072. } else if (Q('d3coreMode').value == 4) {
  12073. // Soft disconnect the mesh agent
  12074. meshserver.send({ action: 'agentdisconnect', nodeid: consoleNode._id, disconnectMode: 1 });
  12075. } else if (Q('d3coreMode').value == 5) {
  12076. // Hard disconnect the mesh agent
  12077. meshserver.send({ action: 'agentdisconnect', nodeid: consoleNode._id, disconnectMode: 2 });
  12078. } else if (Q('d3coreMode').value == 6) {
  12079. // Upload a recovery core
  12080. meshserver.send({ action: 'uploadagentcore', nodeids: [ consoleNode._id ], type:'recovery' });
  12081. } else if (Q('d3coreMode').value == 7) {
  12082. // Upload a tiny core
  12083. meshserver.send({ action: 'uploadagentcore', nodeids: [ consoleNode._id ], type:'tiny' });
  12084. } else if (Q('d3coreMode').value == 8) {
  12085. // Restart MeshAgent service
  12086. meshserver.send({ action: 'msg', type: 'console', nodeid: consoleNode._id, value:'service restart' });
  12087. } else if (Q('d3coreMode').value == 9) {
  12088. // Update mesh agent
  12089. meshserver.send({ action: 'updateAgents', nodeids: [ consoleNode._id ] });
  12090. }
  12091. }
  12092. // Called then user opts to upload a file as core
  12093. function p15uploadCore2() {
  12094. if (xxdialogMode) return;
  12095. Q('d3localmodeform').action = 'uploadmeshcorefile.ashx';
  12096. Q('d3auth').value = authCookie;
  12097. Q('d3filter').value = '.js';
  12098. Q('d3attrib').value = currentNode._id;
  12099. setDialogMode(3, "Upload Mesh Agent Core", 3, p15uploadCoreEx2);
  12100. d3init();
  12101. }
  12102. function p15uploadCoreEx2() {
  12103. var mode = Q('d3uploadMode').value;
  12104. if (mode == 1) {
  12105. // Upload local mesh agent core
  12106. Q('d3submit').click();
  12107. } else {
  12108. // Upload server mesh agent code
  12109. var files = d3getFileSel();
  12110. if (files.length == 1) { meshserver.send({ action: 'uploadagentcore', nodeids: [ consoleNode._id ], type: 'custom', path: d3filetreelocation.join('/') + '/' + files[0] }); }
  12111. }
  12112. }
  12113. //
  12114. // MY ACCOUNT
  12115. //
  12116. function account_viewPreviousLogins() {
  12117. if (xxdialogMode) return;
  12118. setDialogMode(2, "Previous Logins", 1, null, "Loading...", 'previousLogins');
  12119. meshserver.send({ action: 'previousLogins' });
  12120. }
  12121. function account_manageImage(mode) {
  12122. if (xxdialogMode) return;
  12123. var user = (mode == 0)?userinfo:currentUser;
  12124. 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:60px;margin-top:8px;border-radius:16px;box-shadow: 0px 0px 15px #000" onclick=account_canvasClick() /></div>';
  12125. setDialogMode(2, "Manage Account Image", 7, account_manageImageEx2, x, user._id);
  12126. var ctx = Q('p2canvas').getContext('2d');
  12127. if (user.accountImageRnd == null) { user.accountImageRnd = Math.floor(Math.random() * 9999999999); }
  12128. var arg = '';
  12129. if (mode == 1) { arg = '&id=' + user._id.split('/')[2]; }
  12130. var myImg = new Image();
  12131. myImg.onload = function() { ctx.clearRect(0, 0, 256, 256); ctx.drawImage(myImg, 0, 0); };
  12132. myImg.src = ((user.flags != null) && (user.flags & 1))?('userimage.ashx?rnd=' + user.accountImageRnd + arg):'images/user-256.png';
  12133. QE('idx_dlgDeleteButton', (user.flags != null) && (user.flags & 1));
  12134. QE('idx_dlgOkButton', false);
  12135. }
  12136. function account_canvasClick() { Q('p2file').click(); }
  12137. function account_manageImageEx() {
  12138. var file = Q('p2file').files[0];
  12139. var img = new Image;
  12140. img.onload = function() {
  12141. var cx = 0, cy = 0, min = Math.min(img.width, img.height);
  12142. if (img.width > min) { cx = (img.width - min) / 2; }
  12143. if (img.height > min) { cy = (img.height - min) / 2; }
  12144. var ctx = Q('p2canvas').getContext('2d');
  12145. ctx.imageSmoothingEnabled = true;
  12146. ctx.webkitImageSmoothingEnabled = true;
  12147. ctx.mozImageSmoothingEnabled = true;
  12148. ctx.clearRect(0, 0, 256, 256);
  12149. ctx.drawImage(img, cx, cy, min, min, 0, 0, 256, 256);
  12150. QE('idx_dlgOkButton', true);
  12151. }
  12152. img.src = URL.createObjectURL(file);
  12153. }
  12154. function account_manageImageEx2(b, userid) {
  12155. // Send updated image, or 0 if we pressed the delete button
  12156. meshserver.send({ action: 'updateUserImage', userid: userid, image: (b == 2)?0:Q('p2canvas').toDataURL(Q('p2file').files[0].type) });
  12157. //meshserver.send({ action: 'updateUserImage', image: (b == 2)?0:Q('p2canvas').toDataURL('image/png', 0.8) });
  12158. }
  12159. function account_managePhone() {
  12160. if (xxdialogMode || ((features & 0x02000000) == 0)) return;
  12161. var x;
  12162. if (userinfo.phone != null) {
  12163. x = '<table style=width:100%><tr><td style=width:56px><img src="images/phone80.png" style=padding:8px>';
  12164. x += '<td style=text-align:center><div style=padding:6px>' + "Verified phone number" + '</div><div style=font-size:20px>' + EscapeHtml(userinfo.phone) + '</div>';
  12165. x += '<div style=margin:10px><label><input id=d2delPhone type=checkbox onclick=account_managePhoneRemoveValidate() />' + "Remove phone number" + '</label></div>';
  12166. setDialogMode(2, "Phone Notifications", 3, account_managePhoneRemove, x);
  12167. account_managePhoneRemoveValidate();
  12168. } else {
  12169. x = '<table style=width:100%><tr><td style=width:56px><img src="images/phone80.png" style=padding:8px>';
  12170. x += '<td>' + "Enter your SMS capable phone number. Once verified, the number may be used for login verification and other notifications.";
  12171. 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>';
  12172. setDialogMode(2, "Phone Notifications", 3, account_managePhoneAdd, x, 'verifyPhone');
  12173. Q('d2phoneinput').focus();
  12174. account_managePhoneValidate();
  12175. }
  12176. }
  12177. function isPhoneNumber(x) { var ok = true; if (x.startsWith('+')) { x = x.substring(1); } for (var i = 0; i < x.length; i++) { var c = x.charCodeAt(i); if (((c < 48) || (c > 57)) && (c != 32) && (c != 45) && (c != 46)) { ok = false; } } return ok && (x.length >= 10); }
  12178. function account_managePhoneValidate(x) { var ok = isPhoneNumber(Q('d2phoneinput').value); QE('idx_dlgOkButton', ok); if ((x == 1) && ok) { dialogclose(1); } }
  12179. 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); } }
  12180. function account_managePhoneConfirm(b, tag) { meshserver.send({ action: 'confirmPhone', code: Q('d2phoneCodeInput').value, cookie: tag }); }
  12181. function account_managePhoneAdd() { if (isPhoneNumber(Q('d2phoneinput').value) == false) return; QE('d2phoneinput', false); meshserver.send({ action: 'verifyPhone', phone: Q('d2phoneinput').value }); }
  12182. function account_managePhoneRemove() { if (Q('d2delPhone').checked) { meshserver.send({ action: 'removePhone' }); } }
  12183. function account_managePhoneRemoveValidate() { QE('idx_dlgOkButton', Q('d2delPhone').checked); }
  12184. function account_manageMessaging() {
  12185. if (xxdialogMode || ((features2 & 0x02000000) == 0)) return;
  12186. var x;
  12187. if (userinfo.msghandle != null) {
  12188. x = '<table style=width:100%><tr><td style=width:56px><img src="images/messaging40.png" style=padding:8px>';
  12189. x += '<td style=text-align:center><div style=padding:6px>' + "Verified handle" + '</div><div style=font-weight:bold>' + EscapeHtml(userinfo.msghandle) + '</div>';
  12190. x += '<div style=margin:10px><label><input id=d2delPhone type=checkbox onclick=account_managePhoneRemoveValidate() />' + "Remove messaging" + '</label></div>';
  12191. setDialogMode(2, "Messaging Notifications", 3, account_manageMessagingRemove, x);
  12192. account_managePhoneRemoveValidate();
  12193. } else {
  12194. x = '<table style=width:100%><tr><td style=width:56px;vertical-align:top><img src="images/messaging40.png" style=padding:8px>';
  12195. x += '<td>' + "Enter your messaging service and handle. Once verified, this server can send you login verification and other notifications." + '<br /><br />';
  12196. var y = '<select id=d2serviceselect style=width:160px;margin-left:8px onchange=account_manageMessagingValidate()>';
  12197. if ((serverinfo.userMsgProviders & 1) != 0) { y += '<option value=1>' + "Telegram" + '</option>'; }
  12198. if ((serverinfo.userMsgProviders & 4) != 0) { y += '<option value=4>' + "Discord" + '</option>'; }
  12199. if ((serverinfo.userMsgProviders & 8) != 0) { y += '<option value=8>' + "XMPP" + '</option>'; }
  12200. if ((serverinfo.userMsgProviders & 16) != 0) { y += '<option value=16>' + "CallMeBot" + '</option>'; }
  12201. if ((serverinfo.userMsgProviders & 32) != 0) { y += '<option value=32>' + "Pushover" + '</option>'; }
  12202. if ((serverinfo.userMsgProviders & 64) != 0) { y += '<option value=64>' + "ntfy" + '</option>'; }
  12203. if ((serverinfo.userMsgProviders & 128) != 0) { y += '<option value=128>' + "Zulip" + '</option>'; }
  12204. if ((serverinfo.userMsgProviders & 256) != 0) { y += '<option value=256>' + "Slack" + '</option>'; }
  12205. y += '</select>';
  12206. x += '<table><tr><td>' + "Service" + '<td>' + y;
  12207. x += '<tr><td>' + "Handle" + '<td><input maxlength=1024 style=width:160px;margin-left:8px id=d2handleinput onKeyUp=account_manageMessagingValidate() onkeypress="if (event.key==\'Enter\') account_manageMessagingValidate(1)">';
  12208. x += '</table>';
  12209. if (serverinfo.discordUrl) { x += '<div id=d2discordurl style=display:none><br /><a href=' + serverinfo.discordUrl + ' target="_discord">' + "Join this Discord server to receive notifications." + '</a></div>'; }
  12210. x += '<div id=d2callmebotinfo style=display:none><br /><a href=https://www.callmebot.com/blog/free-api-signal-send-messages/ target="_callmebot">' + "Signal" + '</a>, <a href=https://www.callmebot.com/blog/free-api-whatsapp-messages/ target="_callmebot">' + "Whatsapp" + '</a>, <a href=https://www.callmebot.com/blog/free-api-facebook-messenger/ target="_callmebot">' + "Facebook" + '</a>, <a href=https://www.callmebot.com/blog/telegram-text-messages/ target="_callmebot">' + "Telegram" + '</a></div>';
  12211. x += '<div id=d2pushoverinfo style=display:none><br /><a href=https://pushover.net/ target="_pushover">' + "Information at Pushover.net" + '</a></div>';
  12212. x += '<div id=d2ntfyinfo style=display:none><br /><a href="' + (serverinfo.userMsgNftyUrl ? serverinfo.userMsgNftyUrl : 'https://ntfy.sh/') + '" target="_ntfy">' + "Free service at ntfy.sh" + '</a></div>';
  12213. x += '<div id=d2slackinfo style=display:none><br /><a href=https://api.slack.com/messaging/webhooks target="_slack">' + "Slack Webhook Setup" + '</a></div>';
  12214. setDialogMode(2, "Messaging Notifications", 3, account_manageMessagingAdd, x, 'verifyMessaging');
  12215. Q('d2handleinput').focus();
  12216. account_manageMessagingValidate();
  12217. }
  12218. }
  12219. function account_manageMessagingValidate(x) {
  12220. if (serverinfo.discordUrl) { QV('d2discordurl', Q('d2serviceselect').value == 4); }
  12221. QV('d2callmebotinfo', Q('d2serviceselect').value == 16);
  12222. QV('d2pushoverinfo', Q('d2serviceselect').value == 32);
  12223. QV('d2ntfyinfo', Q('d2serviceselect').value == 64);
  12224. QV('d2slackinfo', Q('d2serviceselect').value == 256);
  12225. if (Q('d2serviceselect').value == 4) { Q('d2handleinput')['placeholder'] = "Username:0000"; }
  12226. else if (Q('d2serviceselect').value == 8) { Q('d2handleinput')['placeholder'] = "[email protected]"; }
  12227. else if (Q('d2serviceselect').value == 16) { Q('d2handleinput')['placeholder'] = "https://api.callmebot.com/..."; }
  12228. else if (Q('d2serviceselect').value == 32) { Q('d2handleinput')['placeholder'] = "User key"; }
  12229. else if (Q('d2serviceselect').value == 64) { Q('d2handleinput')['placeholder'] = "Topic"; }
  12230. else if (Q('d2serviceselect').value == 128) { Q('d2handleinput')['placeholder'] = "[email protected]"; }
  12231. else if (Q('d2serviceselect').value == 256) { Q('d2handleinput')['placeholder'] = "https://hooks.slack.com/..."; }
  12232. else { Q('d2handleinput')['placeholder'] = "Username"; }
  12233. var ok = (Q('d2handleinput').value.length > 0); QE('idx_dlgOkButton', ok); if ((x == 1) && ok) { dialogclose(1); }
  12234. }
  12235. function account_manageMessagingAdd() { if (Q('d2handleinput').value.length == 0) return; QE('d2handleinput', false); meshserver.send({ action: 'verifyMessaging', service: Q('d2serviceselect').value, handle: Q('d2handleinput').value }); }
  12236. function account_manageMessagingConfirm(b, tag) { meshserver.send({ action: 'confirmMessaging', code: Q('d2phoneCodeInput').value, cookie: tag }); }
  12237. function account_manageMessagingRemove() { if (Q('d2delPhone').checked) { meshserver.send({ action: 'removeMessaging' }); } }
  12238. function account_manageAuthEmail() {
  12239. if (xxdialogMode || ((features & 0x00800000) == 0)) return;
  12240. var emailU2Fenabled = ((userinfo.otpekey == 1) && (userinfo.email != null) && (userinfo.emailVerified == true));
  12241. setDialogMode(2, "Email Authentication", 1, function () {
  12242. if (emailU2Fenabled != Q('email2facheck').checked) { meshserver.send({ action: 'otpemail', enabled: Q('email2facheck').checked }); }
  12243. }, "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>');
  12244. }
  12245. function account_manageAuthDuo() {
  12246. if (xxdialogMode || ((features2 & 0x20000000) == 0)) return;
  12247. var duoU2Fenabled = ((userinfo.otpduo == 1));
  12248. if (duoU2Fenabled == false) {
  12249. setDialogMode(2, "Duo Authentication", 3, function () {
  12250. window.location.href = '/add-duo?rurl=' + encodeURIComponentEx(window.location.href) + ((urlargs.key)?('&key=' + urlargs.key):'');
  12251. }, "Confirm enabling of Duo 2FA login security. Once enabled you will be given the option to use Duo at login for added security. Click ok to go thru the steps to enable Duo." + '<p style="text-align: center"><img src="images/duo-2fa-250.png"></p>');
  12252. } else {
  12253. setDialogMode(2, "Duo Authentication", 3, function () {
  12254. meshserver.send({ action: 'otpduo', enabled: false });
  12255. }, '<p><label><input id=duo2facheck type=checkbox onclick=account_manageAuthDuoConfirm() />' + "Confirm disabling 2FA Duo login security." + '</label></p>' + '<p style="text-align: center"><img src="images/duo-2fa-250-disable.png"></p>');
  12256. QE('idx_dlgOkButton', false);
  12257. }
  12258. }
  12259. function account_manageAuthDuoConfirm() {
  12260. QE('idx_dlgOkButton', Q('duo2facheck').checked);
  12261. }
  12262. function account_manageAuthApp() {
  12263. if (xxdialogMode || ((features & 4096) == 0)) return;
  12264. if (userinfo.otpsecret == 1) { account_removeOtp(); } else { account_addOtp(); }
  12265. return false;
  12266. }
  12267. function account_addOtp() {
  12268. if (xxdialogMode || (userinfo.otpsecret == 1) || ((features & 4096) == 0)) return;
  12269. 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');
  12270. meshserver.send({ action: 'otpauth-request' });
  12271. }
  12272. function account_addOtpCheck(e) {
  12273. var tokenIsValid = (Q('d2otpauthinput').value.length == 6);
  12274. QE('idx_dlgOkButton', tokenIsValid);
  12275. if (e && (e.keyCode == 13) && tokenIsValid) { dialogclose(1); }
  12276. }
  12277. function account_removeOtp() {
  12278. if (xxdialogMode || (userinfo.otpsecret != 1) || ((features & 4096) == 0)) return;
  12279. setDialogMode(2, "Authenticator App", 3, function () { meshserver.send({ action: 'otpauth-clear' }); }, "Confirm removal of authenticator application 2-step login?");
  12280. }
  12281. function account_manageOtp(action) {
  12282. if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-manage')) { dialogclose(0); }
  12283. if (xxdialogMode || ((features & 4096) == 0)) return false;
  12284. if (count2factoraAuths() > 0) { meshserver.send({ action: 'otpauth-getpasswords', subaction: action }); }
  12285. return false;
  12286. }
  12287. function account_managePushAuthDev() {
  12288. if (xxdialogMode || ((features2 & 0x40) == 0)) return;
  12289. if (userinfo.otpdev == 1) {
  12290. // Remove the 2FA device
  12291. setDialogMode(2, "Authentication Device", 3, function () { meshserver.send({ action: 'otpdev-clear' }); }, "Confirm removal of push authentication device?");
  12292. } else {
  12293. // Create a list of all mobile devices
  12294. var mobileDevices = [];
  12295. for (var i in nodes) { var node = nodes[i]; if ((node.agent != null) && (node.agent.id == 14) && (node.pmt == 1) && (GetNodeRights(node) == 0xFFFFFFFF)) { mobileDevices.push(node); } }
  12296. if (mobileDevices.length == 0) {
  12297. // No mobile devices found
  12298. setDialogMode(2, "Authentication Device", 1, null, "In order to use push notification authentication, a mobile device must be setup in your account with full rights.");
  12299. } else {
  12300. // Set a 2FA device
  12301. var x = "Select a device to register for push notification authentication. Once selected, the device will prompt for confirmation." + '<br /><br />';
  12302. var y = '<select id=d2devselect style=width:240px>';
  12303. for (var i in mobileDevices) { y += '<option value="' + mobileDevices[i]._id + '">' + EscapeHtml(mobileDevices[i].name) + '</option>'; }
  12304. y += '</select>';
  12305. x += addHtmlValue("Device", y);
  12306. setDialogMode(2, "Authentication Device", 3, function () { meshserver.send({ action: 'otpdev-set', nodeid: Q('d2devselect').value }); }, x);
  12307. }
  12308. }
  12309. return false;
  12310. }
  12311. function account_manageHardwareOtp() {
  12312. if ((xxdialogMode == 2) && (xxdialogTag == 'otpauth-hardware-manage')) { dialogclose(0); }
  12313. if (xxdialogMode || ((features & 4096) == 0)) return false;
  12314. meshserver.send({ action: 'otp-hkey-get' });
  12315. return false;
  12316. }
  12317. function account_addhkey(type) {
  12318. if (type == 3) {
  12319. var x = "Type in the name of the key to add." + '<br /><br />';
  12320. x += addHtmlValue("Key Name", '<input id=dp1keyname style=width:230px maxlength=20 autocomplete=off placeholder="' + "MyKey" + '" onkeyup=account_addhkeyValidate(event,2) />');
  12321. } else if (type == 2) {
  12322. var x = "Type in a key name, select the OTP box and press the button on the YubiKey&trade;." + '<br /><br />';
  12323. x += addHtmlValue("Key Name", '<input id=dp1keyname style=width:230px maxlength=20 autocomplete=off placeholder="' + "MyKey" + '" onkeyup=account_addhkeyValidate(event,1) />');
  12324. x += addHtmlValue("YubiKey&trade; OTP", '<input id=dp1key style=width:230px autocomplete=off onkeyup=account_addhkeyValidate(event,2) />');
  12325. }
  12326. setDialogMode(2, "Add Security Key", 3, account_addhkeyEx, x, type);
  12327. Q('dp1keyname').focus();
  12328. }
  12329. function account_addhkeyValidate(e,action) {
  12330. if ((e != null) && (e.keyCode == 13)) { if (action == 2) { dialogclose(1); } else { Q('dp1key').focus(); } }
  12331. }
  12332. function account_addhkeyEx(button, type) {
  12333. var name = Q('dp1keyname').value;
  12334. if (name == '') { name = 'MyKey'; }
  12335. if (type == 2) {
  12336. meshserver.send({ action: 'otp-hkey-yubikey-add', name: name, otp: Q('dp1key').value });
  12337. setDialogMode(2, "Add Security Key", 0, null, '<br />' + "Checking..." + '<br /><br /><br />', 'otpauth-hardware-manage');
  12338. } else if (type == 3) {
  12339. meshserver.send({ action: 'webauthn-startregister', name: name });
  12340. }
  12341. }
  12342. function account_removehkey(index) {
  12343. meshserver.send({ action: 'otp-hkey-remove', index: index });
  12344. meshserver.send({ action: 'otp-hkey-get' });
  12345. }
  12346. 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" };
  12347. var loclistex = { 'zh-chs': "Chinese (Simplified)", 'zh-cht': "Chinese (Traditional)" };
  12348. function account_showLocalizationSettings() {
  12349. if (xxdialogMode) return false;
  12350. var n = getstore('loctag', 0), y = '';
  12351. var x = '<select id=d2locselect style=width:240px><option value="*">' + "User browser value" + '</option>';
  12352. for (var i in loclist) { x += '<option value="' + i + '"' + ((n == i)?' selected':'') + '>' + i + ' - ' + loclist[i] + '</option>'; }
  12353. x += '</select>';
  12354. if (serverinfo.languages && serverinfo.languages.length > 0) {
  12355. y += "Changing the language will require a refresh of the page." + '<br /><br />';
  12356. var z = '<select id=d2langselect style=width:240px><option value="*">' + "User browser value" + '</option>';
  12357. for (var i in serverinfo.languages) {
  12358. var lang = serverinfo.languages[i];
  12359. z += '<option value="' + lang + '"' + ((userinfo.lang == lang)?' selected':'') + '>' + lang + ' - ' + (loclist[lang]?loclist[lang]:loclistex[lang]) + '</option>';
  12360. }
  12361. z += '</select>';
  12362. y += addHtmlValue("Language", z);
  12363. }
  12364. y += addHtmlValue("Dates & Time", x);
  12365. if ((userinfo.siteadmin == 0xFFFFFFFF) && (domain == '')) {
  12366. y += '<br /><a rel="noreferrer noopener" target="_blank" href="translator.htm">' + "Help translate MeshCentral" + '</a>';
  12367. }
  12368. setDialogMode(2, "Localization Settings", 3, account_showLocalizationSettingsEx, y);
  12369. return false;
  12370. }
  12371. function account_showLocalizationSettingsEx() {
  12372. // Set user language
  12373. var lang = Q('d2langselect').value;
  12374. if ((lang == '*') && (userinfo.lang == null)) { lang = userinfo.lang; }
  12375. if (lang != userinfo.lang) { meshserver.send({ action: 'changelang', lang: lang }); }
  12376. // Set date localization
  12377. var n = getstore('loctag', 0);
  12378. var m = Q('d2locselect').value;
  12379. if (n != m) {
  12380. if (m != '*') { args.locale = m; } else { delete args.locale; }
  12381. putstore('loctag', args.locale);
  12382. mainUpdate(0xFFFFFFFF); // Refresh everything.
  12383. }
  12384. }
  12385. function account_enableNotifications() {
  12386. if (Notification) { Notification.requestPermission().then(function (permission) { QV('accountEnableNotificationsSpan', permission != 'granted'); setupServiceWorker(); }); }
  12387. return false;
  12388. }
  12389. function account_createLoginToken() {
  12390. if (xxdialogMode) return false;
  12391. var y = '', x = "Create a temporary username and password that can be used as alternative login to your account. This is useful for allowing tools or other services to access your account." + '<br /><br />';
  12392. var options = { 0 : "Unlimited", 1 : "1 minute", 5 : "5 minutes", 10 : "10 minutes", 15 : "15 minutes", 30 : "30 minutes", 45 : "45 minutes", 60 : "60 minutes", 120 : "2 hours", 240 : "4 hours", 480 : "8 hours", 720 : "12 hours", 960 : "16 hours", 1440 : "24 hours", 2880 : "2 days", 5760 : "4 days", 10080 : "7 days" }
  12393. for (var i in options) { y += '<option value=' + i + '>' + options[i] + '</option>'; }
  12394. x += addHtmlValue("Token Name", '<input class=boxsize id=d2tokenName style=width:250px maxlength=100 type=text onchange=account_createLoginTokenValidate() onkeyup=account_createLoginTokenValidate() />');
  12395. x += addHtmlValue("Expire Time", '<select class=boxsize id=d2tokenExpire style=width:250px>' + y + '</select>');
  12396. setDialogMode(2, "Create Login Token", 3, account_createLoginTokenEx, x);
  12397. QE('idx_dlgOkButton', false);
  12398. Q('d2tokenName').focus();
  12399. }
  12400. function account_createLoginTokenValidate() {
  12401. QE('idx_dlgOkButton', Q('d2tokenName').value.length > 0);
  12402. }
  12403. function account_createLoginTokenEx() {
  12404. meshserver.send({ action: 'createLoginToken', name: Q('d2tokenName').value, expire: parseInt(Q('d2tokenExpire').value) });
  12405. }
  12406. function account_showAccountNotifySettings() {
  12407. if (xxdialogMode) return false;
  12408. var x = '';
  12409. x += '<div><label><input id=p2notifyPlayNotifySound type=checkbox />' + "Notification sound" + '</label></div>';
  12410. x += '<div><label><input id=p2notifyGroupName type=checkbox />' + "Display device group name" + '</label></div>';
  12411. x += '<div><label><input id=p2notifyIntelDeviceConnect type=checkbox />' + "Device connections" + '</label></div>';
  12412. x += '<div><label><input id=p2notifyIntelDeviceDisconnect type=checkbox />' + "Device disconnections" + '</label></div>';
  12413. x += '<div><label><input id=p2notifyIntelAmtKvmActions type=checkbox />' + "Intel&reg; AMT desktop and serial events" + '</label></div>';
  12414. setDialogMode(2, "Notification Settings", 3, account_showAccountNotifySettingsEx, x);
  12415. var n = getstore('notifications', 0);
  12416. Q('p2notifyPlayNotifySound').checked = (n & 1);
  12417. Q('p2notifyIntelDeviceConnect').checked = (n & 2);
  12418. Q('p2notifyIntelDeviceDisconnect').checked = (n & 4);
  12419. Q('p2notifyIntelAmtKvmActions').checked = (n & 8);
  12420. Q('p2notifyGroupName').checked = (n & 16);
  12421. return false;
  12422. }
  12423. function account_showAccountNotifySettingsEx() {
  12424. var n = 0;
  12425. n += Q('p2notifyPlayNotifySound').checked ? 1 : 0;
  12426. n += Q('p2notifyIntelDeviceConnect').checked ? 2 : 0;
  12427. n += Q('p2notifyIntelDeviceDisconnect').checked ? 4 : 0;
  12428. n += Q('p2notifyIntelAmtKvmActions').checked ? 8 : 0;
  12429. n += Q('p2notifyGroupName').checked ? 16 : 0;
  12430. putstore('notifications', n);
  12431. }
  12432. function account_showVerifyEmail() {
  12433. if (xxdialogMode || (userinfo.emailVerified == true) || (serverinfo.emailcheck != true)) return false;
  12434. 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.";
  12435. setDialogMode(2, "Email Verification", 3, account_showVerifyEmailEx, x);
  12436. return false;
  12437. }
  12438. function account_showVerifyEmailEx() {
  12439. meshserver.send({ action: 'verifyemail', email: userinfo.email });
  12440. }
  12441. function account_showChangeEmail() {
  12442. if (xxdialogMode) return false;
  12443. var x = "Change your account email address here." + '<br /><br />';
  12444. x += addHtmlValue('Email', '<input id=dp2email style=width:230px maxlength=256 onchange=account_validateEmail() onkeyup=account_validateEmail(event) />');
  12445. setDialogMode(2, "Email Address Change", 3, account_changeEmail, x);
  12446. if (userinfo.email != null) { Q('dp2email').value = userinfo.email; }
  12447. account_validateEmail();
  12448. Q('dp2email').focus();
  12449. return false;
  12450. }
  12451. function account_validateEmail(e, email) {
  12452. QE('idx_dlgOkButton', validateEmail(Q('dp2email').value) && (Q('dp2email').value != userinfo.email));
  12453. if ((e != null) && (e.keyCode == 13)) { dialogclose(1); }
  12454. }
  12455. function account_changeEmail() {
  12456. meshserver.send({ action: 'changeemail', email: Q('dp2email').value });
  12457. }
  12458. function account_showDeleteAccount() {
  12459. if (xxdialogMode) return false;
  12460. var x = "To delete this account, type in the account password in both boxes below and hit ok." + '<br /><br />';
  12461. x += '<form method=post><input type=hidden name=action value=deleteaccount /><input type=hidden name=authcookie value=' + authCookie + ' /><table style=margin-left:80px><tr>';
  12462. x += '<td align=right>' + "Password:" + '</td><td><input id=apassword1 type=password name=apassword1 autocomplete=off onchange=account_validateDeleteAccount() onkeyup=account_validateDeleteAccount() /></td>';
  12463. 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>';
  12464. x += '</tr></table><br /><div style=padding:10px;margin-bottom:4px>';
  12465. x += '<input id=account_dlgCancelButton type=button value=Cancel style=float:right;width:80px;margin-left:5px onclick=dialogclose(0)>';
  12466. x += '<input id=account_dlgOkButton type=submit value=OK style="float:right;width:80px" onclick=dialogclose(1)>';
  12467. x += '</div><br /></form>';
  12468. setDialogMode(2, "Delete Account", 0, null, x);
  12469. account_validateDeleteAccount();
  12470. Q('apassword1').focus();
  12471. return false;
  12472. }
  12473. function account_showChangePassword() {
  12474. if (xxdialogMode) return false;
  12475. var x = "Change your account password by entering the old password and new password twice in the boxes below.";
  12476. if (features & 0x00010000) { " Password hint can be used but is not recommended."; }
  12477. x += '<br /><br />';
  12478. //x += "<form action='" + domainUrl + "changepassword' method=post>";
  12479. x += '<table style=margin-left:60px>';
  12480. 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>';
  12481. 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>';
  12482. 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>';
  12483. 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>'; }
  12484. x += '</table>'
  12485. if (passRequirements) {
  12486. var r = [], rc = 0;
  12487. for (var i in passRequirements) { if ((i != 'reset') && (i != 'hint')) { r.push(i + ':' + passRequirements[i]); rc++; } }
  12488. if (rc > 0) { x += '<br /><span style=font-size:x-small>' + "Requirements: " + r.join(', ') + '.</span>'; }
  12489. }
  12490. x += '<br />';
  12491. //x += '<br /><div style=padding:10px;margin-bottom:4px>';
  12492. //x += '<input id=account_dlgCancelButton type=button value=Cancel style=float:right;width:80px;margin-left:5px onclick=dialogclose(0)>';
  12493. //x += '<input id=account_dlgOkButton type=submit value=OK style="float:right;width:80px" onclick=dialogclose(1)>';
  12494. //x += '</div><br /></form>';
  12495. setDialogMode(2, "Change Password", 3, account_showChangePasswordEx, x);
  12496. Q('apassword0').focus();
  12497. account_validateNewPassword();
  12498. return false;
  12499. }
  12500. function account_showChangePasswordEx() {
  12501. if (Q('apassword1').value == Q('apassword2').value) {
  12502. var r = { action: 'changepassword', oldpass: Q('apassword0').value, newpass: Q('apassword1').value };
  12503. if (features & 0x00010000) { r.hint = Q('apasswordhint').value; }
  12504. meshserver.send(r);
  12505. }
  12506. }
  12507. function account_createMesh() {
  12508. if (xxdialogMode) return false;
  12509. // Check if we are disallowed from creating a device group
  12510. 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 false; }
  12511. // Remind the user to verify the email address
  12512. if ((userinfo.emailVerified !== true) && (serverinfo.emailcheck == true) && (userinfo.siteadmin != 0xFFFFFFFF)) { setDialogMode(2, "Account Security", 1, null, "Unable to access this feature until a email address is verified. This is required for password recovery. Go to the \"My Account\" tab to change and verify an email address."); return false; }
  12513. // Remind the user to add two factor authentication
  12514. if ((features & 0x00040000) && (count2factoraAuths() == 0)) { 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\" tab and look at the \"Account Security\" section."); return false; }
  12515. // Look for all relay devices
  12516. var relayDevices = [];
  12517. 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); } } }
  12518. // We are allowed, let's prompt to information
  12519. var x = "Create a new device group using the options below." + '<br /><br />', localGroupType = '';
  12520. x += addHtmlValue("Name", '<input id=dp2meshname style=width:230px maxlength=128 onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate(event,1) />');
  12521. if ((features & 1) == 0) { localGroupType += '<option value=3>' + "Local devices, no agent" + '</option>'; }
  12522. if (((features & 2) == 0) && (relayDevices.length > 0)) { localGroupType += '<option value=103>' + "No agent devices relayed thru agent" + '</option>'; }
  12523. if (features2 & 0x10000) {
  12524. if ((features & 1) == 0) { localGroupType += '<option value=4>' + "IP-KVM / Power device" + '</option>'; }
  12525. if (((features & 2) == 0) && (relayDevices.length > 0)) { localGroupType += '<option value=104>' + "IP-KVM / Power device relayed thru agent" + '</option>'; }
  12526. }
  12527. x += addHtmlValue("Type", '<div style=width:230px;margin:0;padding:0><select id=dp2meshtype style=width:100% onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate(event,2) ><option value=2>' + "Manage using a software agent" + '</option><option value=1>' + "Intel&reg; AMT only, no agent" + '</option>' + localGroupType + '</select></div>');
  12528. if (relayDevices.length > 0) {
  12529. x += '<div id=d2devrelaydiv style=display:none>';
  12530. relayDevices.sort(nameSort);
  12531. var relayDevices2 = [];
  12532. for (var i in relayDevices) { relayDevices2.push('<option value="' + relayDevices[i]._id + '">' + EscapeHtml(relayDevices[i].name) + ' (' + EscapeHtml(relayDevices[i].meshnamel) + ')' + '</option>'); }
  12533. x += addHtmlValue("Relay Device", '<div style=width:230px;margin:0;padding:0><select id=d2devrelay style=width:100% onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate(event,2) >' + relayDevices2.join('') + '</select></div>');
  12534. x += '</div>';
  12535. }
  12536. x += addHtmlValue("Description", '<div style=width:230px;margin:0;padding:0><textarea id=dp2meshdesc maxlength=1024 style=width:100%;resize:none></textarea></div>');
  12537. x += '<div id=d2ipkvm style=display:none><hr />';
  12538. x += addHtmlValue("Model", '<div style=width:230px;margin:0;padding:0><select id=dp2ipkvmmodel style=width:100% onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate(event,2) ><option value=1>' + "Raritan Dominion KX III" + '</option><option value=2>' + "Web Power Switch 7" + '</option></select></div>');
  12539. x += addHtmlValue("Hostname", '<input id=dp2ipkvmhost style=width:230px maxlength=128 onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate(event,1) />');
  12540. x += addHtmlValue("Username", '<input id=dp2ipkvmuser style=width:230px maxlength=128 onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate(event,1) />');
  12541. x += addHtmlValue("Password", '<input id=dp2ipkvmpass type=password style=width:230px maxlength=128 onchange=account_validateMeshCreate() onkeyup=account_validateMeshCreate(event,1) />');
  12542. x += '</div>';
  12543. setDialogMode(2, "New Device Group", 3, account_createMeshEx, x);
  12544. account_validateMeshCreate();
  12545. Q('dp2meshname').focus();
  12546. return false;
  12547. }
  12548. function account_validateMeshCreate(e, x) {
  12549. var meshtype = parseInt(Q('dp2meshtype').value);
  12550. if ((x == 1) && (e != null) && (e.key == "Enter") && (Q('dp2meshname').value.length > 0)) { Q('dp2meshtype').focus(); }
  12551. if ((x == 2) && (e != null) && (e.key == "Enter")) { Q('dp2meshdesc').focus(); }
  12552. var ok = (Q('dp2meshname').value.length > 0);
  12553. QV('d2ipkvm', (meshtype == 4) || (meshtype == 104));
  12554. try { QV('d2devrelaydiv', meshtype > 100); } catch (ex) {}
  12555. if (meshtype == 4) { if ((Q('dp2ipkvmhost').value.length == 0) && (Q('dp2ipkvmuser').value.length == 0) && (Q('dp2ipkvmpass').value.length == 0)) { ok = false; } }
  12556. QE('idx_dlgOkButton', ok);
  12557. }
  12558. function account_createMeshEx(button, tag) {
  12559. var meshtype = parseInt(Q('dp2meshtype').value);
  12560. var cmd = { action: 'createmesh', meshname: Q('dp2meshname').value, meshtype: meshtype, desc: Q('dp2meshdesc').value };
  12561. if ((meshtype == 4) || (meshtype == 104)) {
  12562. cmd.kvmmodel = parseInt(Q('dp2ipkvmmodel').value);
  12563. cmd.kvmhost = Q('dp2ipkvmhost').value;
  12564. cmd.kvmuser = Q('dp2ipkvmuser').value;
  12565. cmd.kvmpass = Q('dp2ipkvmpass').value;
  12566. }
  12567. if (meshtype > 100) {
  12568. cmd.meshtype = (meshtype - 100);
  12569. cmd.relayid = Q('d2devrelay').value;
  12570. }
  12571. meshserver.send(cmd);
  12572. }
  12573. function account_validateDeleteAccount() {
  12574. QE('account_dlgOkButton', (Q('apassword1').value.length > 0) && (Q('apassword1').value == Q('apassword2').value));
  12575. }
  12576. function account_validateNewPassword() {
  12577. 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);
  12578. if ((features & 0x00010000) && (Q('apasswordhint').value == Q('apassword1').value)) { ok = false; }
  12579. if (Q('apassword1').value != '') {
  12580. if (passRequirements == null || passRequirements == '') {
  12581. // No password requirements, display password strength
  12582. var passStrength = checkPasswordStrength(Q('apassword1').value);
  12583. if (passStrength >= 80) { r = '<span style=color:green>' + "Strong" + '<span>'; } else if (passStrength >= 60) { r = '<span style=color:blue>' + "Good" + '<span>'; } else { r = '<span style=color:red>' + "Weak" + '<span>'; }
  12584. } else {
  12585. // Password requirements provided, use that
  12586. var passReq = checkPasswordRequirements(Q('apassword1').value, passRequirements);
  12587. if (passReq == false) { ok = false; r = '<span style=color:red>' + "Policy" + '<span>' }
  12588. }
  12589. }
  12590. QH('dxPassWarn', r);
  12591. //QE('account_dlgOkButton', ok);
  12592. QE('idx_dlgOkButton', ok);
  12593. }
  12594. // Return a password strength score
  12595. function checkPasswordStrength(password) {
  12596. 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) }
  12597. if (!password) return 0;
  12598. for (var i = 0; i< password.length; i++) { letters[password[i]] = (letters[password[i]] || 0) + 1; r += 5.0 / letters[password[i]]; }
  12599. for (var c in variations) { varCount += (variations[c] == true) ? 1 : 0; }
  12600. return parseInt(r + (varCount - 1) * 10);
  12601. }
  12602. // Check password requirements
  12603. function checkPasswordRequirements(password, requirements) {
  12604. if ((requirements == null) || (requirements == '') || (typeof requirements != 'object')) return true;
  12605. if (requirements.min) { if (password.length < requirements.min) return false; }
  12606. if (requirements.max) { if (password.length > requirements.max) return false; }
  12607. var numeric = 0, lower = 0, upper = 0, nonalpha = 0;
  12608. for (var i = 0; i < password.length; i++) {
  12609. if (/\d/.test(password[i])) { numeric++; }
  12610. if (/[a-z]/.test(password[i])) { lower++; }
  12611. if (/[A-Z]/.test(password[i])) { upper++; }
  12612. if (/\W/.test(password[i])) { nonalpha++; }
  12613. }
  12614. if (requirements.numeric && (numeric < requirements.numeric)) return false;
  12615. if (requirements.lower && (lower < requirements.lower)) return false;
  12616. if (requirements.upper && (upper < requirements.upper)) return false;
  12617. if (requirements.nonalpha && (nonalpha < requirements.nonalpha)) return false;
  12618. return true;
  12619. }
  12620. function updateMeshes() {
  12621. var r = '', c = 0, count = 0;
  12622. // Sort the device groups
  12623. var sortedMeshes = [];
  12624. for (i in meshes) { sortedMeshes.push(meshes[i]); }
  12625. sortedMeshes.sort(nameSort);
  12626. for (i in sortedMeshes) {
  12627. // Mesh positioning
  12628. if (c > 1) { r += '</tr><tr>'; c = 0; }
  12629. c++;
  12630. count++;
  12631. // Mesh rights
  12632. var meshrights = GetMeshRights(sortedMeshes[i]);
  12633. var rights = "Partial Rights";
  12634. if (meshrights == 0xFFFFFFFF) rights = "Full Administrator"; else if (meshrights == 0) rights = "No Rights";
  12635. // Print the mesh information
  12636. r += '<div onmouseover=devGrpMouseHover(this,1) onmouseout=devGrpMouseHover(this,0) style=display:inline-block;width:431px;height:50px;padding-top:1px;padding-bottom:1px;float:left><div style=float:left;width:30px;height:100%></div><div tabindex=0 style=height:100%;cursor:pointer onclick=gotoMesh(\'' + sortedMeshes[i]._id + '\') onkeypress="if (event.key==\'Enter\') gotoMesh(\'' + sortedMeshes[i]._id + '\')"><div class=mi style=float:left;width:50px;height:50px></div><div style=height:100%><div class=g1></div><div class=e2 style=width:300px><div class=e1>' + EscapeHtml(sortedMeshes[i].name) + '</div><div>' + rights + '</div></div><div class=g2 style=float:left></div></div></div></div>';
  12637. }
  12638. meshcount = count;
  12639. QH('p2meshes', r);
  12640. QV('p2noMeshFound', count == 0);
  12641. }
  12642. function updateLoginTokens() {
  12643. var x = '', count = 1;
  12644. if ((loginTokens != null) && (loginTokens.length > 0)) {
  12645. x += '<p><strong>' + "Active Login Tokens" + '</strong> - <span id="p2createMeshLink1"> <a href=# onclick="return account_createLoginToken()" class="newMeshBtn"> ' + "New" + '</a></span></p><div style=margin-left:40px><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>' + "Name" + '</th><th scope=col style=text-align:left>' + "Username" + '</th></tr>';
  12646. for (var i = 0; i < loginTokens.length; i++) {
  12647. var ltoken = loginTokens[i];
  12648. var trash = '<a href=# onclick=\'return p2removeLoginToken(event,"' + encodeURIComponentEx(ltoken.tokenUser) + '")\' title="' + "Remove login token" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>';
  12649. var details = '';
  12650. if (ltoken.expire != 0) { details = EscapeHtml(format("Expires {0}", printDateTime(new Date(ltoken.expire)))) + ' '; }
  12651. x += '<tr ' + (((++count % 2) == 0) ? 'style=background-color:#DDD' : '') + '><td style=width:30%><div class=m' + 2 + '></div><div>&nbsp;' + EscapeHtml(ltoken.name) + '<div></div></div></td><td style=width:70%><div style=float:right>' + details + trash + '</div><div>' + EscapeHtml(ltoken.tokenUser) + '</div></td></tr>';
  12652. }
  12653. x += '</tbody></table></div><br />';
  12654. QV('accountCreateLoginTokenSpan', false);
  12655. } else {
  12656. QV('accountCreateLoginTokenSpan', features2 & 0x00000080);
  12657. }
  12658. QH('p2logintokens', x);
  12659. }
  12660. function p2removeLoginToken(e, tokenUser) {
  12661. tokenUser = decodeURIComponent(tokenUser);
  12662. if (loginTokens == null) return;
  12663. var token = null;
  12664. for (var i = 0; i < loginTokens.length; i++) { if (loginTokens[i].tokenUser == tokenUser) { token = loginTokens[i]; } }
  12665. if (token == null) return;
  12666. var x = "Confirm removal of this login token?" + '<br /><br />';
  12667. x += addHtmlValue("Name", EscapeHtml(token.name));
  12668. x += addHtmlValue("Username", EscapeHtml(token.tokenUser));
  12669. setDialogMode(2, "Remove Login Token", 3, function(b, tokenUser) { meshserver.send({ action: 'loginTokens', remove: [ tokenUser ] }); }, x, tokenUser);
  12670. }
  12671. function gotoMesh(meshid) {
  12672. currentMesh = meshes[meshid];
  12673. p20updateMesh();
  12674. go(20);
  12675. return false;
  12676. }
  12677. function server_setupGoogleDriveBackup() {
  12678. if (xxdialogMode || (miscState['googleDrive'] == null)) return false;
  12679. var gd = miscState['googleDrive'];
  12680. if (gd.state < 3) {
  12681. var x = '<img style=float:right src="images/googledrive-48.png" /><div>' + "Setup this server to automatically upload backups to Google Drive. Start by creating and entering a Google Drive ClientID and ClientSecret for your account." + '</div><br />';
  12682. x += addHtmlValue("Credentials", '<a href="https://console.developers.google.com/apis/api/drive.googleapis.com/credentials" rel="noreferrer noopener" target="_blank">' + "Google Drive Console" + '</a>');
  12683. x += addHtmlValue("Client ID", '<input id=gdclientid style=width:240px placeholder="xxxxxxxx.apps.googleusercontent.com" onkeyup=server_setupGoogleDriveBackupCheck1()></input>');
  12684. x += addHtmlValue("Client Secret", '<input id=gdclientsecret style=width:240px onkeyup=server_setupGoogleDriveBackupCheck1()></input>');
  12685. setDialogMode(2, "Google Drive Backup", 3, server_setupGoogleDriveBackupEx, x, 'gd1');
  12686. Q('gdclientid').focus();
  12687. QE('idx_dlgOkButton', false);
  12688. } else if (gd.state == 3) {
  12689. var x = '<img style=float:right src="images/googledrive-48.png" /><div>' + "Google Drive backup is currently active." + '</div>';
  12690. x += '<br /><label><input id=gdcheck type=checkbox onchange=server_setupGoogleDriveBackupCheck2() />' + "Remove Configuration" + '</label>';
  12691. setDialogMode(2, "Google Drive Backup", 3, server_setupGoogleDriveBackupEx, x, 'gd0');
  12692. QE('idx_dlgOkButton', false);
  12693. }
  12694. }
  12695. function server_setupGoogleDriveBackupCheck1() { QE('idx_dlgOkButton', (Q('gdclientid').value.length > 0) && (Q('gdclientsecret').value.length > 0)); }
  12696. function server_setupGoogleDriveBackupCheck2() { QE('idx_dlgOkButton', Q('gdcheck').checked); }
  12697. function server_setupGoogleDriveBackupCheck3() { QE('idx_dlgOkButton', Q('gdcode').value.length > 0); }
  12698. function server_setupGoogleDriveBackupEx(b, t) {
  12699. if (t == 'gd0') { meshserver.send({ action: 'serverBackup', service: 'googleDrive', state: 0 }); }
  12700. if (t == 'gd1') { meshserver.send({ action: 'serverBackup', service: 'googleDrive', state: 1, clientid: Q('gdclientid').value, clientsecret: Q('gdclientsecret').value }); }
  12701. if (t == 'gd2') { meshserver.send({ action: 'serverBackup', service: 'googleDrive', state: 2, code: Q('gdcode').value }); }
  12702. }
  12703. function server_showRestoreDlg() {
  12704. if (xxdialogMode) return false;
  12705. var x = "Restore the server using a backup," + ' <span style=color:red>' + "this will delete the existing server data." + '</span> ' + "Only do this if you know what you are doing." + '<br /><br />';
  12706. x += '<form action="/restoreserver.ashx' + ((urlargs.key)?('?key=' + urlargs.key):'') + '" enctype="multipart/form-data" method="post"><div>';
  12707. x += '<input type=hidden name=auth value=' + authCookie + '>';
  12708. x += '<input id=account_dlgFileInput type=file name=datafile style=width:100% accept=".zip,application/octet-stream,application/zip,application/x-zip,application/x-zip-compressed" onchange=account_validateServerRestore()><br /><br />';
  12709. x += '<input id=account_dlgCancelButton type=button value=' + "Cancel" + ' style=float:right;width:80px;margin-left:5px onclick=dialogclose(0)>';
  12710. x += '<input id=account_dlgOkButton type=submit value=' + "OK" + ' style=float:right;width:80px onclick=dialogclose(1)>';
  12711. x += '</div><br /><br /></form>';
  12712. setDialogMode(2, "Restore Server", 0, null, x);
  12713. account_validateServerRestore();
  12714. return false;
  12715. }
  12716. function account_validateServerRestore() {
  12717. QE('account_dlgOkButton', Q('account_dlgFileInput').files.length == 1);
  12718. }
  12719. function server_showVersionDlg() {
  12720. if (xxdialogMode) return false;
  12721. setDialogMode(2, "MeshCentral Version", 1, null, "Loading...", 'MeshCentralServerUpdate');
  12722. meshserver.send({ action: 'serverversion' });
  12723. return false;
  12724. }
  12725. function server_showVersionDlgUpdate() {
  12726. var stableCheck = Q('d2updateCheck1').checked;
  12727. var latestCheck = Q('d2updateCheck2').checked;
  12728. QE('d2updateCheck1', ((xxdialogTag.stable != "Unknown") && (xxdialogTag.current != xxdialogTag.stable) && (latestCheck == false)));
  12729. QE('d2updateCheck2', ((xxdialogTag.latest != "Unknown") && (xxdialogTag.current != xxdialogTag.latest) && (stableCheck == false)));
  12730. QE('idx_dlgOkButton', ((stableCheck) && (!latestCheck)) || ((!stableCheck) && (latestCheck)));
  12731. }
  12732. function server_showVersionDlgEx(b, tags) {
  12733. if (Q('d2updateCheck1').checked) { meshserver.send({ action: 'serverupdate', tag: 'stable', version: tags.stable }); }
  12734. if (Q('d2updateCheck2').checked) { meshserver.send({ action: 'serverupdate', tag: 'latest', version: tags.latest }); }
  12735. }
  12736. function server_showErrorsDlg() {
  12737. if (xxdialogMode) return false;
  12738. setDialogMode(2, "Server Errors", 1, null, "Loading...", 'MeshCentralServerErrors');
  12739. meshserver.send({ action: 'servererrors' });
  12740. return false;
  12741. }
  12742. function server_showErrorsDlgUpdate() { QE('idx_dlgOkButton', Q('d2clearErrorsCheck').checked); }
  12743. function server_showErrorsDlgEx() { meshserver.send({ action: 'serverclearerrorlog' }); }
  12744. function d2CopyServerErrorsToClip() { saveAs(new Blob([Q('d2ServerErrorsLogPre').innerText], { type: 'application/octet-stream' }), "servererrors.txt"); }
  12745. function server_showConfigDlg() {
  12746. if (xxdialogMode) return false;
  12747. setDialogMode(2, "Server Configuration", 1, null, "Loading...", 'MeshCentralServerConfig');
  12748. meshserver.send({ action: 'serverconfig' });
  12749. return false;
  12750. }
  12751. function d2CopyServerConfigToClip() { saveAs(new Blob([Q('d2ServerConfigPre').innerText], { type: 'application/octet-stream' }), "config.json"); }
  12752. //
  12753. // MY MESHS
  12754. //
  12755. var currentMesh;
  12756. function p20updateMesh() {
  12757. if (currentMesh == null) return;
  12758. // Add device group name
  12759. var meshrights = GetMeshRights(currentMesh), mname = EscapeHtml(currentMesh.name);
  12760. if (mname.length == 0) { mname = '<i>' + "None" + '</i>'; }
  12761. if ((meshrights & 1) != 0) { mname = '<span tabindex=0 title="' + "Click here to edit the device group name" + '" onclick=p20editmesh(1) onkeyup="if (event.key == \'Enter\') p20editmesh(1)" style=cursor:pointer>' + mname + ' <img class=hoverButton src="images/link5.png" /></span>'; }
  12762. QH('p20meshName', mname);
  12763. QV('MeshSummary', (currentMesh.mtype != 4));
  12764. var meshtype = format("Unknown #{0}", currentMesh.mtype);
  12765. if (currentMesh.mtype == 1) meshtype = "Intel&reg; AMT only, no agent";
  12766. if (currentMesh.mtype == 2) meshtype = "Managed using a software agent";
  12767. if (currentMesh.mtype == 3) { if (currentMesh.relayid == null) { meshtype = "Local devices, no agent"; } else { meshtype = "No agent devices relayed thru agent"; } }
  12768. 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'; } }
  12769. var x = '';
  12770. if ((args.hide & 8) != 0) { x += addHtmlValue("Name", mname); } // If title bar is hidden, display the mesh name here
  12771. x += addHtmlValue("Description", addLinkConditional(((currentMesh.desc && currentMesh.desc != '')?EscapeHtml(currentMesh.desc):('<i>' + "None" + '</i>')), 'p20editmesh(2)', (meshrights & 1) != 0));
  12772. // Display group type
  12773. x += addHtmlValue("Type", meshtype);
  12774. //x += addHtmlValue('Identifier', currentMesh._id.split('/')[2]);
  12775. // Display the relay device if applicable
  12776. if (((currentMesh.mtype == 3) || (currentMesh.mtype == 4)) && (currentMesh.relayid != null)) {
  12777. var relayName = '<i>' + "Unknown" + '</i>';
  12778. var relayNode = getNodeFromId(currentMesh.relayid);
  12779. if (relayNode != null) { relayName = EscapeHtml(relayNode.name) + ' (' + EscapeHtml(relayNode.meshnamel) + ')'; }
  12780. x += addHtmlValue("Relay Device", addLinkConditional(relayName, 'p20editmeshrelay()', (meshrights & 1) != 0));
  12781. }
  12782. // Display IP-KVM information if needed
  12783. if (currentMesh.mtype == 4) {
  12784. x += addHtmlValue("Hostname", currentMesh.kvm.host);
  12785. x += addHtmlValue("Username", currentMesh.kvm.user);
  12786. }
  12787. // Display device group creator
  12788. if ((currentMesh.creatorid != null) && (users != null) && (users[currentMesh.creatorid] != null)) {
  12789. var meshcreator = users[currentMesh.creatorid];
  12790. x += addHtmlValue("Creator", '<a onclick=gotoUser(\"' + encodeURIComponentEx(meshcreator._id) + '\",false,null)>' + EscapeHtml(meshcreator.name) + '</a>');
  12791. } else if (currentMesh.creatorname != null) {
  12792. x += addHtmlValue("Creator", EscapeHtml(currentMesh.creatorname));
  12793. }
  12794. // Display creation time
  12795. if (currentMesh.creation != null) { x += addHtmlValue("Creation Time", printDateTime(new Date(currentMesh.creation))); }
  12796. // Display features
  12797. if (currentMesh.mtype != 3) {
  12798. var meshFeatures = [];
  12799. if (currentMesh.flags) {
  12800. if (currentMesh.flags & 1) { meshFeatures.push("Auto-Remove"); }
  12801. if (currentMesh.flags & 2) { meshFeatures.push((currentMesh.mtype == 4)?"Port Name Sync":"Hostname Sync"); }
  12802. if (currentMesh.flags & 8) { meshFeatures.push("prefer --agentname"); }
  12803. if (currentMesh.flags & 16) { meshFeatures.push("allow override"); }
  12804. if ((serverinfo.devGroupSessionRecording == 1) && (currentMesh.flags & 4)) { meshFeatures.push("Record Sessions"); }
  12805. }
  12806. if ((typeof currentMesh.expireDevs == 'number') && (currentMesh.expireDevs > 0)) { meshFeatures.push("Remove inactive"); }
  12807. meshFeatures = meshFeatures.join(', ');
  12808. if (meshFeatures == '') { meshFeatures = '<i>' + "None" + '</i>'; }
  12809. x += addHtmlValue("Features", addLinkConditional(meshFeatures, 'p20editmeshfeatures()', meshrights & 1));
  12810. }
  12811. // Display device group user consent
  12812. if (currentMesh.mtype == 2) {
  12813. var meshFeatures = [];
  12814. var consent = 0;
  12815. if (currentMesh.consent) { consent = currentMesh.consent; }
  12816. if (serverinfo.consent) { consent |= serverinfo.consent; }
  12817. if ((consent & 0x0040) && (consent & 0x0008)) { meshFeatures.push("Desktop Prompt+Toolbar"); } else if (consent & 0x0040) { meshFeatures.push("Desktop Toolbar"); } else if (consent & 0x0008) { meshFeatures.push("Desktop Prompt"); } else { if (consent & 0x0001) { meshFeatures.push("Desktop Notify"); } }
  12818. if (consent & 0x0010) { meshFeatures.push("Terminal Prompt"); } else { if (consent & 0x0002) { meshFeatures.push("Terminal Notify"); } }
  12819. if (consent & 0x0020) { meshFeatures.push("Files Prompt"); } else { if (consent & 0x0004) { meshFeatures.push("Files Notify"); } }
  12820. if (consent == 7) { meshFeatures = ["Always Notify"]; }
  12821. if ((consent & 56) == 56) { meshFeatures = ["Always Prompt"]; }
  12822. meshFeatures = meshFeatures.join(', ');
  12823. if (meshFeatures == '') { meshFeatures = '<i>' + "None" + '</i>'; }
  12824. x += addHtmlValue("User Consent", addLinkConditional(meshFeatures, 'p20editmeshconsent(1)', meshrights & 1));
  12825. }
  12826. if (currentMesh.mtype != 3) {
  12827. if ((userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.siteadmin & 1024) == 0)) {
  12828. // Display user notification
  12829. var meshNotify = 0, meshNotifyStr = [];
  12830. if (userinfo.links && userinfo.links[currentMesh._id] && userinfo.links[currentMesh._id].notify) { meshNotify |= userinfo.links[currentMesh._id].notify; }
  12831. if (userinfo.notify && userinfo.notify[currentMesh._id]) { meshNotify |= userinfo.notify[currentMesh._id]; }
  12832. if (meshNotify & 2) { meshNotifyStr.push("Connect"); }
  12833. if (meshNotify & 4) { meshNotifyStr.push("Disconnect"); }
  12834. if (meshNotify & 8) { meshNotifyStr.push("Intel&reg; AMT"); }
  12835. if ((features2 & 0x00004000) && (userinfo.emailVerified)) {
  12836. var xx = 0;
  12837. if (meshNotify & 16) { xx++; } if (meshNotify & 32) { xx++; } if (meshNotify & 64) { xx++; }
  12838. if (xx > 0) { meshNotifyStr.push(format("Email ({0})", xx)); }
  12839. }
  12840. if ((userinfo.msghandle != null) && ((features2 & 0x02000000) != 0)) {
  12841. var xx = 0;
  12842. if (meshNotify & 128) { xx++; } if (meshNotify & 256) { xx++; } if (meshNotify & 512) { xx++; }
  12843. if (xx > 0) { meshNotifyStr.push(format("Messaging ({0})", xx)); }
  12844. }
  12845. if (meshNotifyStr.length == 0) { meshNotifyStr.push('<i>' + "None" + '</i>'); }
  12846. x += addHtmlValue("Notifications", addLink(meshNotifyStr.join(', '), 'p20editMeshNotify()'));
  12847. }
  12848. }
  12849. // Display invitation codes
  12850. if ((features & 0x01000000) && (currentMesh.mtype == 2)) {
  12851. var inviteCodeStr = '<i>' + "None" + '</i>', icodes = false;
  12852. if (currentMesh.invite != null) { icodes = true; inviteCodeStr = currentMesh.invite.codes.join(', '); /* + ', ' + currentMesh.invite.flags;*/ }
  12853. x += addHtmlValue("Invite Codes", addLinkConditional(inviteCodeStr, 'p20editmeshInviteCode()', (meshrights & 1) || (icodes)));
  12854. }
  12855. // If the Intel AMT manager is active on the server, show the Intel AMT policy edit box.
  12856. if ((currentMesh.mtype < 3) && ((features2 & 1) != 0)) {
  12857. // Intel AMT setup
  12858. var intelAmtPolicy = "No Policy";
  12859. if (currentMesh.amt) {
  12860. if (currentMesh.amt.type == 1) { intelAmtPolicy = "Deactivate"; }
  12861. else if (currentMesh.amt.type == 2) {
  12862. intelAmtPolicy = "Simple Client Control Mode (CCM)";
  12863. if (currentMesh.amt.cirasetup == 2) { intelAmtPolicy += " + CIRA"; }
  12864. } else if (currentMesh.amt.type == 3) {
  12865. intelAmtPolicy = "Simple Admin Control Mode (ACM)";
  12866. if (currentMesh.amt.cirasetup == 2) { intelAmtPolicy += " + CIRA"; }
  12867. } else if (currentMesh.amt.type == 4) {
  12868. intelAmtPolicy = "Fully Automatic";
  12869. }
  12870. }
  12871. x += addHtmlValue("Intel&reg; AMT", addLinkConditional(intelAmtPolicy, 'p20editMeshAmt()', meshrights & 1));
  12872. }
  12873. // Display device group note support
  12874. if (meshrights & 1) { x += '<br><input type=button value=' + "Notes" + ' title="' + "View notes about this device group" + '" onclick=showNotes(false,"' + encodeURIComponentEx(currentMesh._id) + '") />'; }
  12875. x += '<br style=clear:both><br>';
  12876. if (meshrights & 2) {
  12877. x += '<a href=# onclick="return p20showAddMeshUserDialog()" style=cursor:pointer;margin-right:10px><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add Users" + '</a>';
  12878. if (usergroups != null) {
  12879. var userGroupCount = 0, newUserGroup = false;
  12880. for (var i in usergroups) {
  12881. if ((usergroups[i].membershipType != null) || (usergroups[i]._id.split('/')[1] != currentMesh._id.split('/')[1])) continue;
  12882. userGroupCount++;
  12883. if ((currentMesh.links == null) || (currentMesh.links[i] == null)) { newUserGroup = true; }
  12884. }
  12885. if ((userGroupCount > 0) && (newUserGroup)) { x += '<a href=# onclick="return p20showAddMeshUserDialog(2)" style=cursor:pointer;margin-right:10px><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add User Group" + '</a>'; }
  12886. }
  12887. }
  12888. //if (meshrights & 4) { }
  12889. if (currentMesh.mtype == 1) {
  12890. if (meshrights & 1) { x += '<a href=# style=cursor:pointer;margin-right:10px title="' + "Import Intel&reg; AMT devices." + '" onclick=\'return showAmtImport("' + currentMesh._id + '")\'><img src=images/icon-installmesh.png border=0 height=12 width=12> ' + "Import" + '</a>'; }
  12891. /*
  12892. if ((features & 1) == 0) { // If not WAN-Only
  12893. x += '<a href=# onclick=\'return addDeviceToMesh("' + currentMesh._id + '")\' style=cursor:pointer;margin-right:10px title="' + "Add a new Intel&reg; AMT computer that is located on the local network." + '"><img src=images/icon-installmesh.png border=0 height=12 width=12> ' + "Install local" + '</a>';
  12894. }
  12895. */
  12896. if ((currentMesh.amt != null) && (currentMesh.amt.type > 0)) { // CCM Deactivate, CCM or ACM activation
  12897. x += '<a href=# style=cursor:pointer;margin-right:10px title="' + "Perform Intel&reg; AMT activation and configuration." + '" onclick=\'return showAmtSetup("' + currentMesh._id + '")\'><img src=images/icon-installmesh.png border=0 height=12 width=12> ' + "Setup" + '</a>';
  12898. }
  12899. }
  12900. if ((currentMesh.mtype == 2) && ((userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.siteadmin & 4096) == 0))) {
  12901. x += '<a href=# onclick=\'return addAgentToMesh("' + currentMesh._id + '")\' style=cursor:pointer;margin-right:10px title="' + "Add a new computer to this device group by installing the mesh agent." + '"><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add Agent" + '</a>';
  12902. x += '<a href=# onclick=\'return inviteAgentToMesh("' + currentMesh._id + '")\' style=cursor:pointer;margin-right:10px title="' + "Invite someone to install the mesh agent on this device group." + '"><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Invite" + '</a>';
  12903. }
  12904. if ((currentMesh.mtype == 3) && ((userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.siteadmin & 4096) == 0))) {
  12905. x += '<a href=# onclick=\'return addLocalDeviceToMesh("' + currentMesh._id + '")\' style=cursor:pointer;margin-right:10px title="' + "Add device located on the local network." + '"><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add Device" + '</a>';
  12906. }
  12907. if (currentMesh.amt && (currentMesh.amt.type > 2) && ((userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.siteadmin & 4096) == 0))) { // ACM activation or Full Automatic
  12908. x += '<a href=# style=cursor:pointer;font-size:small title="' + "Switch Intel AMT to Admin Control Mode (ACM)." + '" onclick=\'return showAmtAcmSetup()\'><img src=images/icon-installmesh.png border=0 height=12 width=12> ' + "ACM" + '</a>';
  12909. }
  12910. 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><th scope=col style=text-align:left></th></tr>';
  12911. // Sort the users for this mesh
  12912. var count = 1, sortedusers = [];
  12913. for (var i in currentMesh.links) {
  12914. var uname = i.split('/')[2];
  12915. if (currentMesh.links[i].name) { uname = currentMesh.links[i].name; }
  12916. if (i == userinfo._id) { uname = userinfo.name; }
  12917. if ((usergroups != null) && (usergroups[i] != null)) { uname = usergroups[i].name; }
  12918. sortedusers.push({ id: i, name: uname, rights: currentMesh.links[i].rights });
  12919. }
  12920. sortedusers.sort(function(a, b) { if (a.name > b.name) return 1; if (a.name < b.name) return -1; return 0; });
  12921. // Display all users for this device group
  12922. for (var i in sortedusers) {
  12923. var trash = '', r = sortedusers[i].rights, rights = makeDeviceGroupRightsString(r), icon = 2;
  12924. if ((sortedusers[i].id != userinfo._id) && (meshrights == 0xFFFFFFFF || (((meshrights & 2) != 0)))) {
  12925. if ((meshrights == 0xFFFFFFFF) || (currentMesh.links[sortedusers[i].id].rights != 0xFFFFFFFF)) {
  12926. trash = '<a href=# onclick=\'return p20deleteUser(event,"' + encodeURIComponentEx(sortedusers[i].id) + '")\' title="' + "Remove user rights to this device group" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>';
  12927. }
  12928. rights = '<span tabindex=0 style=cursor:pointer onclick=p20viewuser("' + encodeURIComponentEx(sortedusers[i].id) + '") onkeypress="if (event.key==\'Enter\') p20viewuser(\'' + encodeURIComponentEx(sortedusers[i].id) + '\')">' + rights + ' <img class=hoverButton style=cursor:pointer src=images/link5.png></span>';
  12929. }
  12930. if (sortedusers[i].id.startsWith('ugrp/')) { icon = 4; }
  12931. var username = EscapeHtml(decodeURIComponent(sortedusers[i].name));
  12932. if ((usergroups != null) && sortedusers[i].id.startsWith('ugrp/')) { username = '<a tabindex=0 href=# onclick=\'gotoUserGroup("' + encodeURIComponentEx(sortedusers[i].id) + '");haltEvent(event);\'>' + username + '</a>'; }
  12933. if ((users != null) && sortedusers[i].id.startsWith('user/')) { username = '<a tabindex=0 href=# onclick=\'gotoUser("' + encodeURIComponentEx(sortedusers[i].id) + '");haltEvent(event);\'>' + username + '</a>'; }
  12934. x += '<tr style=' + (((count % 2) == 0) ? ';background-color:#DDD' : '') + '><td style=width:30%><div title="' + "User" + '" class=m' + icon + '></div><div>&nbsp;' + username + '<div></div></div></td><td style=width:70%><div style=float:right>' + trash + '</div><div>' + rights + '</div></td></tr>';
  12935. ++count;
  12936. }
  12937. x += '</tbody></table>';
  12938. // Show device shares
  12939. if ((deviceShares != null) && (deviceSharesNode == currentMesh._id) && (deviceShares.length > 0)) {
  12940. x += '<p></p><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>' + "Active Device Sharing" + '</th><th scope=col style=text-align:left><th scope=col style=text-align:left></th></tr>';
  12941. count = 1;
  12942. for (var i = 0; i < deviceShares.length; i++) {
  12943. var dshare = deviceShares[i], trash = '';
  12944. if (dshare.url != null) { trash += '<a href="' + dshare.url + '" rel="noreferrer noopener" target=_blank title="' + "Device Sharing Link" + '" style=cursor:pointer><img src=images/link2.png border=0 height=10 width=10></a> '; }
  12945. trash += '<a href=# onclick=\'return p30removeDeviceSharing(event,"' + encodeURIComponentEx(dshare.nodeid) + '","' + encodeURIComponentEx(dshare.publicid) + '","' + encodeURIComponentEx(dshare.guestName) + '")\' title="' + "Remove device sharing" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>';
  12946. var type = ''; if (dshare.p <= 7) { type = ['', "Terminal", "Desktop", "Desktop + Terminal", "Files", "Terminal + Files", "Desktop + Files", "Desktop + Terminal + Files"][dshare.p]; } else if (dshare.p == 8) { type = "HTTP/" + dshare.port; } else if (dshare.p == 16) { type = "HTTPS/" + dshare.port; }
  12947. var details = type;
  12948. if ((dshare.startTime != null) && (dshare.expireTime != null)) { details = format("{0}, {1} to {2}", type, printFlexDateTime(new Date(dshare.startTime)), printFlexDateTime(new Date(dshare.expireTime))); }
  12949. if ((dshare.startTime != null) && (dshare.duration != null)) {
  12950. if (dshare.duration < 2) {
  12951. details = format("{0}, {1} for {2} minute", type, printFlexDateTime(new Date(dshare.startTime)), dshare.duration);
  12952. } else {
  12953. details = format("{0}, {1} for {2} minutes", type, printFlexDateTime(new Date(dshare.startTime)), dshare.duration);
  12954. }
  12955. }
  12956. if (dshare.recurring == 1) { details += ", Reccuring daily"; }
  12957. if (dshare.recurring == 2) { details += ", Reccuring weekly"; }
  12958. if (((dshare.p & 2) != 0) && (dshare.viewOnly === true)) { details += ", View only desktop"; }
  12959. if (dshare.consent != null) {
  12960. if (dshare.consent == 0) { details += ", No Consent"; } else {
  12961. if ((dshare.consent & 0x0038) != 0) { details += ", Prompt for consent"; }
  12962. if ((dshare.consent & 0x0040) != 0) { details += ", Toolbar"; }
  12963. }
  12964. }
  12965. var guestName = EscapeHtml(dshare.guestName);
  12966. if (dshare.publicid.startsWith('AS:node/')) { guestName = '<i>' + "Agent Self-Share" + '</i>'; }
  12967. var node = getNodeFromId(dshare.nodeid);
  12968. if (node != null) {
  12969. var gray = ((node.conn > 0)?'':' gray');
  12970. var computer = '<div onclick=\'gotoDevice("' + node._id + '",10);haltEvent(event);\' style=float:left class="j' + node.icon + gray + '"></div>&nbsp;<a onclick=\'gotoDevice("' + node._id + '",10);haltEvent(event);\'>' + EscapeHtml(node.name) + '</a>';
  12971. x += '<tr ' + (((++count % 2) == 0) ? 'style=background-color:#DDD' : '') + '><td style=width:30%>' + computer + '<td style=width:30%><div class=m' + 2 + '></div><div>&nbsp;' + guestName + '<div></div></div></td><td style=width:70%><div style=float:right>' + trash + '</div><div>' + details + '</div></td></tr>';
  12972. }
  12973. }
  12974. x += '</tbody></table>';
  12975. } else {
  12976. // Request device sharing
  12977. if ((deviceSharesNode != currentMesh._id) && (deviceSharesReq != currentMesh._id)) {
  12978. deviceSharesReq = currentMesh._id;
  12979. meshserver.send({ action: 'deviceMeshShares', meshid: currentMesh._id });
  12980. }
  12981. }
  12982. // Display list of devices in this device group
  12983. count = 0;
  12984. nodes.sort(deviceSort);
  12985. var dllist = '';
  12986. if (currentMesh.mtype == 1) {
  12987. dllist += '<a onclick=meshDownloadDeviceList()><img title="' + "Download device list" + '" src="images/link4.png" /></a>';
  12988. if ((features & 1) == 0) { dllist += ' <a onclick=meshImportDeviceList()><img title="' + "Import device list" + '" src="images/link6.png" /></a>'; } // Show import only in LAN or Hybrid mode
  12989. }
  12990. var y = '<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>' + "Devices" + '</th><th scope=col style=text-align:right>' + dllist + '</th></tr>';
  12991. for (var i in nodes) {
  12992. var node = nodes[i], gray = ((node.conn > 0)?'':' gray');
  12993. if (currentMesh._id != node.meshid) continue;
  12994. y += '<tr style=' + (((count % 2) == 0) ? ';background-color:#DDD' : '') + '><td style=width:30%><div onclick=\'gotoDevice("' + node._id + '",10);haltEvent(event);\' style=float:left class="j' + node.icon + gray + '"></div>&nbsp;<a onclick=\'gotoDevice("' + node._id + '",10);haltEvent(event);\'>' + EscapeHtml(node.name) + '</a></div></div></td><td style=width:70%><div style=float:right>' + PowerStateStr(node.pwr) + '&nbsp;</div><div>' + (node.osdesc ? EscapeHtml(node.osdesc) : '') + '</div></td></tr>';
  12995. ++count;
  12996. }
  12997. if (count == 0) { y += '<tr><td><i>' + "None" + '</i></td></tr>'; }
  12998. y += '</tbody></table>';
  12999. // If we are full administrator on this mesh, allow deletion of the mesh
  13000. if (meshrights == 0xFFFFFFFF) {
  13001. y += '<div style=font-size:small;text-align:right><span><a href=# onclick=p20showDeleteMeshDialog() style=cursor:pointer>' + "Delete Group" + '</a></span></div>';
  13002. }
  13003. QH('p20info', x);
  13004. QH('p20info2', y);
  13005. // Change the URL
  13006. var urlviewmode = '';
  13007. if (((features & 0x10000000) == 0) && (xxcurrentView >= 20) && (xxcurrentView <= 29) && (currentMesh != null)) {
  13008. urlviewmode = '?viewmode=' + xxcurrentView + '&gotomesh=' + currentMesh._id.split('/')[2];
  13009. for (var i in urlargs) { urlviewmode += ('&' + i + '=' + urlargs[i]); }
  13010. try { window.history.replaceState({}, document.title, window.location.pathname + urlviewmode); } catch (ex) { }
  13011. }
  13012. }
  13013. // Save the list of devices in a device group in the MeshCommander format
  13014. function meshDownloadDeviceList() {
  13015. if (xxdialogMode) return;
  13016. var r = { webappversion: '0.9.0', computers: [ ] };
  13017. for (var i in nodes) {
  13018. var node = nodes[i];
  13019. if ((currentMesh._id != node.meshid) || (node.intelamt == null)) continue;
  13020. var c = { name: node.name, host: node.host, user: node.intelamt.user, pass: '', tls: (node.intelamt.tls == 1)?1:0, ver: node.intelamt.ver, pstate: node.intelamt.state }
  13021. if (node.intelamt.state == 2) { c.pmode = 1; } // TODO
  13022. if (node.intelamt.realm) { c.digestrealm = node.intelamt.realm; }
  13023. if (node.intelamt.hash) { c.tlscerthash = node.intelamt.hash; }
  13024. r.computers.push(c);
  13025. }
  13026. saveAs(stringToUtf8BlobNoHeader(JSON.stringify(r, null, 2)), currentMesh.name + '.json');
  13027. }
  13028. // Import a list of devices into the Intel AMT only device group
  13029. function meshImportDeviceList() {
  13030. if (xxdialogMode) return;
  13031. var x = "Import a list of local Intel&reg; AMT devices in MeshCommander JSON format." + '<br /><br /><input style=width:370px type=file id=d4importFile accept=".json" onchange=meshImportDeviceListValidate() />';
  13032. setDialogMode(2, "Import Intel&reg; AMT Devices", 3, meshImportDeviceListEx, x);
  13033. QE('idx_dlgOkButton', false);
  13034. }
  13035. function meshImportDeviceListValidate() {
  13036. QE('idx_dlgOkButton', Q('d4importFile').value != null);
  13037. }
  13038. function meshImportDeviceListEx() {
  13039. var fr = new FileReader();
  13040. fr.onload = function (r) {
  13041. var j = null;
  13042. try { j = JSON.parse(r.target.result); } catch (ex) { setDialogMode(2, "Import Intel&reg; AMT Devices", 1, null, format("Invalid JSON file: {0}.", ex)); return; }
  13043. if ((typeof j == 'object') && (typeof j.webappversion == 'string') && (typeof j.computers == 'object') && (Array.isArray(j.computers))) {
  13044. var ok = 0;
  13045. for (var k in j.computers) {
  13046. var computer = j.computers[k];
  13047. if ((typeof computer.name == 'string') && (typeof computer.host == 'string') && (typeof computer.user == 'string') && (typeof computer.pass == 'string')) {
  13048. var cmd = { action: 'addamtdevice', meshid: currentMesh._id, devicename: computer.name, hostname: computer.host, amtusername: computer.user, amtpassword: computer.pass, amttls: (computer.tls == 1)?1:0 };
  13049. if (typeof computer.ver == 'string') { cmd.ver = computer.ver; }
  13050. if (typeof computer.pstate == 'number') { cmd.state = computer.pstate; }
  13051. if (typeof computer.digestrealm == 'string') { cmd.realm = computer.digestrealm; }
  13052. if (typeof computer.tlscerthash == 'string') { cmd.hash = computer.tlscerthash; }
  13053. meshserver.send(cmd);
  13054. ok++;
  13055. }
  13056. }
  13057. if (ok == 0) { setDialogMode(2, "User Account Import", 1, null, "Unable to import any device."); }
  13058. } else { setDialogMode(2, "Import Intel&reg; AMT Devices", 1, null, "Invalid JSON file format."); }
  13059. };
  13060. fr.readAsText(Q('d4importFile').files[0]);
  13061. }
  13062. function p20editMeshAmt() {
  13063. if (xxdialogMode) return;
  13064. var x = '', acmoption = '';
  13065. if ((features & 0x100000) != 0) { acmoption = '<option value=3>' + "Simple Admin Control Mode (ACM)" + '</option>'; }
  13066. x += addHtmlValue("Type", '<select id=dp20amtpolicy style=width:230px onchange=p20editMeshAmtChange()><option value=0>' + "No Policy" + '</option><option value=1>' + "Deactivate" + '</option><option value=2>' + "Simple Client Control Mode (CCM)" + '</option>' + acmoption + '<option value=4>' + "Fully Automatic" + '</option></select>');
  13067. x += '<div id=dp20amtpolicydiv></div>';
  13068. setDialogMode(2, "Intel&reg; AMT Policy", 3, p20editMeshAmtEx, x);
  13069. if (currentMesh.amt) { Q('dp20amtpolicy').value = currentMesh.amt.type; }
  13070. p20editMeshAmtChange();
  13071. // Set the current Intel AMT policy
  13072. if (currentMesh.amt && ((currentMesh.amt.type == 2) || (currentMesh.amt.type == 3))) {
  13073. Q('dp20amtpolicypass').value = currentMesh.amt.password;
  13074. if ((currentMesh.amt.type == 2) || (currentMesh.amt.type == 3)) {
  13075. if (currentMesh.amt.badpass != null) { Q('dp20amtbadpass').value = currentMesh.amt.badpass; }
  13076. if ((currentMesh.amt.type == 3) && (currentMesh.amt.ccm != null)) { Q('dp20amtccmmode').value = currentMesh.amt.ccm; }
  13077. }
  13078. if ((features & 0x400) == 0) { Q('dp20amtcira').value = currentMesh.amt.cirasetup; }
  13079. }
  13080. dp20amtValidatePolicy();
  13081. }
  13082. function p20editMeshAmtChange() {
  13083. var ptype = Q('dp20amtpolicy').value, x = '';
  13084. if ((ptype == 2) || (ptype == 3)) {
  13085. var keeppass = ((currentMesh.amt != null) && (currentMesh.amt.password == 1))?'<option value=1 selected>' + "Keep existing password" + '</option>':'';
  13086. x += addHtmlValue("Password", '<select id=dp20amtpass style=width:230px onchange=dp20amtValidatePolicy() onkeyup=dp20amtValidatePolicy()><option value=0>' + "Randomize password" + '</option>' + keeppass+ '<option value=2>' + "Select new password" + '</option></select>');
  13087. x += '<div id=dp20amtpassdiv style=display:none>';
  13088. x += addHtmlValue("New password*", '<input id=dp20amtpolicypass type=password style=width:230px maxlength=32 onchange=dp20amtValidatePolicy() onkeyup=dp20amtValidatePolicy() autocomplete=off />')
  13089. x += addHtmlValue("New password*", '<input id=dp20amtpolicypass2 type=password style=width:230px maxlength=32 onchange=dp20amtValidatePolicy() onkeyup=dp20amtValidatePolicy() autocomplete=off />')
  13090. x += '</div>';
  13091. if (ptype == 3) { x += addHtmlValue("CCM mode", '<select id=dp20amtccmmode style=width:230px onchange=dp20amtValidatePolicy() onkeyup=dp20amtValidatePolicy()><option value=0>' + "Don't change, keep CCM if setup" + '</option><option value=1>' + "Deactivate CCM if setup" + '</option><option value=2>' + "Activate to CCM, if ACM fails" + '</option></select>'); }
  13092. x += '<div id=dp20amtbadpassdiv style=display:none>';
  13093. x += addHtmlValue("Unknown password", '<select id=dp20amtbadpass style=width:230px><option value=0>' + "Do nothing" + '</option><option value=1>' + "If in CCM, reactivate Intel&reg; AMT" + '</option></select>');
  13094. x += '</div>';
  13095. if ((features & 0x400) == 0) { x += addHtmlValue('<span title="' + "Client Initiated Remote Access" + '">' + "CIRA setup" + '</span>', '<select id=dp20amtcira style=width:230px><option value=0>' + "Do nothing" + '</option><option value=1>' + "Don't connect to server" + '</option><option value=2>' + "Connect to server" + '</option></select>'); }
  13096. x += '<span id=dp10passNotify style="font-size:10px"> ' + "* 8-16 characters, 1 upper, 1 lower, 1 numeric, 1 non-alpha numeric." + '</span>';
  13097. if ((currentMesh.mtype == 2) && (ptype == 2)) { x += '<span style="font-size:10px"> ' + "This policy will not impact devices with Intel&reg; AMT in ACM mode." + '</span>'; }
  13098. }
  13099. if (ptype == 0) { x = '<table style=padding-top:4px><tr><td><img style=padding-right:8px src=images/rcheckbox60.png width=60 height=60><td>' + "When this policy is selected, Intel&reg; AMT is not managed by this server. Intel AMT can still be used by manually activating and configuring it." + '</table>'; }
  13100. if (ptype == 1) { x = '<table style=padding-top:4px><tr><td><img style=padding-right:8px src=images/rcheckbox60.png width=60 height=60><td>' + "When this policy is selected, any Intel&reg; AMT in Client Control Mode (CCM) will be deactivated. Other devices will have CIRA cleared and can still be managed manually." + '</table>'; }
  13101. if (ptype == 4) { x = '<table style=padding-top:4px><tr><td><img style=padding-right:8px src=images/checkbox60.png width=60 height=60><td>' + "This is the recommended policy. Intel&reg; AMT activation and management is completely automated and the server will attempt to make best possible use of hardware management." + '</table>'; }
  13102. QH('dp20amtpolicydiv', x);
  13103. setTimeout(dp20amtValidatePolicy, 500);
  13104. }
  13105. function dp20amtValidatePolicy() {
  13106. var ok = true, ptype = Q('dp20amtpolicy').value;
  13107. if (((ptype == 2) || (ptype == 3)) && (Q('dp20amtpass').value == 2)) {
  13108. var pass = Q('dp20amtpolicypass').value, pass2 = Q('dp20amtpolicypass2').value;
  13109. ok = ((pass === pass2) && passwordcheck(pass));
  13110. }
  13111. QE('idx_dlgOkButton', ok);
  13112. if ((ptype == 2) || (ptype == 3)) { QV('dp20amtpassdiv', Q('dp20amtpass').value == 2); }
  13113. QV('dp10passNotify', ((ptype == 2) || (ptype == 3)) && (Q('dp20amtpass').value == 2));
  13114. QV('dp20amtbadpassdiv', (ptype == 2) || ((ptype == 3) && (Q('dp20amtccmmode').value != 1)));
  13115. }
  13116. function p20editMeshAmtEx() {
  13117. var ptype = parseInt(Q('dp20amtpolicy').value), amtpolicy = { type: ptype };
  13118. var password = null;
  13119. if ((ptype == 2) || (ptype == 3)) {
  13120. if (Q('dp20amtpass').value == 0) { password = ''; } // Randomize
  13121. if (Q('dp20amtpass').value == 1) { password = null; } // Keep same
  13122. if (Q('dp20amtpass').value == 2) { password = Q('dp20amtpolicypass').value; } // Set new password
  13123. }
  13124. if (ptype == 2) { // CCM policy
  13125. amtpolicy = { type: ptype, password: password, badpass: parseInt(Q('dp20amtbadpass').value) };
  13126. if ((features & 0x400) == 0) { amtpolicy.cirasetup = parseInt(Q('dp20amtcira').value); } else { amtpolicy.cirasetup = 1; }
  13127. } else if (ptype == 3) { // ACM policy
  13128. amtpolicy = { type: ptype, password: password, badpass: parseInt(Q('dp20amtbadpass').value), ccm: parseInt(Q('dp20amtccmmode').value) };
  13129. if ((features & 0x400) == 0) { amtpolicy.cirasetup = parseInt(Q('dp20amtcira').value); } else { amtpolicy.cirasetup = 1; }
  13130. } else if (ptype == 4) { // Fully automatic policy
  13131. amtpolicy = { type: ptype };
  13132. }
  13133. meshserver.send({ action: 'meshamtpolicy', meshid: currentMesh._id, amtpolicy: amtpolicy });
  13134. }
  13135. function p20showDeleteMeshDialog() {
  13136. if (xxdialogMode) return false;
  13137. 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 />';
  13138. x += '<label><input id=p20check type=checkbox onchange=p20validateDeleteMeshDialog() />' + "Confirm" + '</label>';
  13139. setDialogMode(2, "Delete Group", 3, p20showDeleteMeshDialogEx, x);
  13140. p20validateDeleteMeshDialog();
  13141. return false;
  13142. }
  13143. function p20validateDeleteMeshDialog() {
  13144. QE('idx_dlgOkButton', Q('p20check').checked);
  13145. }
  13146. function p20showDeleteMeshDialogEx(buttons, tag) {
  13147. meshserver.send({ action: 'deletemesh', meshid: currentMesh._id, meshname: currentMesh.name });
  13148. }
  13149. function p20editmeshrelay() {
  13150. if (xxdialogMode) return;
  13151. // Look for all relay devices
  13152. var relayDevices = [];
  13153. 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); } } }
  13154. relayDevices.sort(nameSort);
  13155. if (relayDevices.length == 0) {
  13156. // Relay relay devices available
  13157. setDialogMode(2, "Edit Device Group", 1, null, "No relay devices available.");
  13158. } else {
  13159. var relayDevices2 = [];
  13160. for (var i in relayDevices) { relayDevices2.push('<option value="' + (relayDevices[i]._id + '"' + ((currentMesh.relayid == relayDevices[i]._id) ? ' selected' : '')) + '>' + EscapeHtml(relayDevices[i].name) + ' (' + EscapeHtml(relayDevices[i].meshnamel) + ')' + '</option>'); }
  13161. var x = addHtmlValue("Relay Device", '<div style=width:230px;margin:0;padding:0><select id=d2devrelay style=width:100%>' + relayDevices2.join('') + '</select></div>');
  13162. setDialogMode(2, "Edit Device Group", 3, p20editmeshrelayEx, x);
  13163. }
  13164. }
  13165. function p20editmeshrelayEx() {
  13166. meshserver.send({ action: 'editmesh', meshid: currentMesh._id, relayid: Q('d2devrelay').value });
  13167. }
  13168. function p20editmesh(focus) {
  13169. if (xxdialogMode) return;
  13170. var x = addHtmlValue("Name", '<input id=dp20meshname style=width:230px maxlength=128 onchange=p20editmeshValidate() onkeyup=p20editmeshValidate(event) />');
  13171. x += addHtmlValue("Description", '<div style=width:230px;margin:0;padding:0><textarea id=dp20meshdesc maxlength=1024 style=width:100%;resize:none></textarea></div>');
  13172. setDialogMode(2, "Edit Device Group", 3, p20editmeshEx, x);
  13173. Q('dp20meshname').value = currentMesh.name;
  13174. if (currentMesh.desc) Q('dp20meshdesc').value = currentMesh.desc;
  13175. p20editmeshValidate();
  13176. if (focus == 2) { Q('dp20meshdesc').focus(); } else { Q('dp20meshname').focus(); }
  13177. }
  13178. function p20editmeshEx() {
  13179. meshserver.send({ action: 'editmesh', meshid: currentMesh._id, meshname: Q('dp20meshname').value, desc: Q('dp20meshdesc').value });
  13180. }
  13181. function p20editmeshValidate(e) {
  13182. QE('idx_dlgOkButton', Q('dp20meshname').value.length > 0);
  13183. if (e && e.key == 'Enter') { Q('dp20meshdesc').focus(); }
  13184. }
  13185. // editType: 1 = currentMesh, 2 = currentUser, 3 = currentDevice
  13186. function p20editmeshconsent(editType) {
  13187. if (xxdialogMode) return;
  13188. var x = '', consent = 0, title = '';
  13189. if (editType == 1) { consent = (currentMesh.consent) ? currentMesh.consent : 0; title = "Edit Device Group User Consent"; }
  13190. if (editType == 2) { consent = (currentUser.consent) ? currentUser.consent : 0; title = "Edit User Consent"; }
  13191. if (editType == 3) { consent = (currentNode.consent) ? currentNode.consent : 0; title = "Edit Device User Consent"; }
  13192. if (editType == 4) { consent = (currentUserGroup.consent) ? currentUserGroup.consent : 0; title = "Edit User Group User Consent"; }
  13193. x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px"><b>' + "Desktop" + '</b></div>';
  13194. x += '<div><label><input type=checkbox id=d20flag1 ' + ((consent & 0x0001) ? 'checked' : '') + '>' + "Notify user" + '</label></div>';
  13195. x += '<div><label><input type=checkbox id=d20flag2 ' + ((consent & 0x0008) ? 'checked' : '') + '>' + "Prompt for user consent" + '</label></div>';
  13196. x += '<div><label><input type=checkbox id=d20flag7 ' + ((consent & 0x0040) ? 'checked' : '') + '>' + "Show connection toolbar" + '</label></div>';
  13197. x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px;margin-top:8px"><b>' + "Terminal" + '</b></div>';
  13198. x += '<div><label><input type=checkbox id=d20flag3 ' + ((consent & 0x0002) ? 'checked' : '') + '>' + "Notify user" + '</label></div>';
  13199. x += '<div><label><input type=checkbox id=d20flag4 ' + ((consent & 0x0010) ? 'checked' : '') + '>' + "Prompt for user consent" + '</label></div>';
  13200. x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px;margin-top:8px"><b>' + "Files" + '</b></div>';
  13201. x += '<div><label><input type=checkbox id=d20flag5 ' + ((consent & 0x0004) ? 'checked' : '') + '>' + "Notify user" + '</label></div>';
  13202. x += '<div><label><input type=checkbox id=d20flag6 ' + ((consent & 0x0020) ? 'checked' : '') + '>' + "Prompt for user consent" + '</label></div>';
  13203. setDialogMode(2, title, 3, p20editmeshconsentEx, x, editType);
  13204. if (serverinfo.consent) {
  13205. if (serverinfo.consent & 0x0001) { Q('d20flag1').checked = true; }
  13206. if (serverinfo.consent & 0x0008) { Q('d20flag2').checked = true; }
  13207. if (serverinfo.consent & 0x0002) { Q('d20flag3').checked = true; }
  13208. if (serverinfo.consent & 0x0010) { Q('d20flag4').checked = true; }
  13209. if (serverinfo.consent & 0x0004) { Q('d20flag5').checked = true; }
  13210. if (serverinfo.consent & 0x0020) { Q('d20flag6').checked = true; }
  13211. if (serverinfo.consent & 0x0040) { Q('d20flag7').checked = true; }
  13212. QE('d20flag1', !(serverinfo.consent & 0x0001));
  13213. QE('d20flag2', !(serverinfo.consent & 0x0008));
  13214. QE('d20flag3', !(serverinfo.consent & 0x0002));
  13215. QE('d20flag4', !(serverinfo.consent & 0x0010));
  13216. QE('d20flag5', !(serverinfo.consent & 0x0004));
  13217. QE('d20flag6', !(serverinfo.consent & 0x0020));
  13218. QE('d20flag7', !(serverinfo.consent & 0x0040));
  13219. }
  13220. }
  13221. function p20editmeshconsentEx(b, editType) {
  13222. var consent = 0;
  13223. if (Q('d20flag1').checked) { consent += 0x0001; }
  13224. if (Q('d20flag2').checked) { consent += 0x0008; }
  13225. if (Q('d20flag3').checked) { consent += 0x0002; }
  13226. if (Q('d20flag4').checked) { consent += 0x0010; }
  13227. if (Q('d20flag5').checked) { consent += 0x0004; }
  13228. if (Q('d20flag6').checked) { consent += 0x0020; }
  13229. if (Q('d20flag7').checked) { consent += 0x0040; }
  13230. if (editType == 1) { meshserver.send({ action: 'editmesh', meshid: currentMesh._id, consent: consent }); }
  13231. if (editType == 2) { meshserver.send({ action: 'edituser', id: currentUser._id, consent: consent }); }
  13232. if (editType == 3) { meshserver.send({ action: 'changedevice', nodeid: currentNode._id, consent: consent }); }
  13233. if (editType == 4) { meshserver.send({ action: 'editusergroup', ugrpid: currentUserGroup._id, consent: consent }); }
  13234. }
  13235. function p20editmeshfeatures() {
  13236. if (xxdialogMode) return;
  13237. var flags = (currentMesh.flags)?currentMesh.flags:0, x = '', expire = 0;
  13238. if ((typeof currentMesh.expireDevs == 'number') && (currentMesh.expireDevs > 0)) { expire = currentMesh.expireDevs; if (expire > 2000) { expire = 2000; } }
  13239. if ((serverinfo.devGroupSessionRecording == 1) && (currentMesh.mtype != 4)) {
  13240. x += '<div><label><input type=checkbox id=d20flag4 onchange=p20editmeshfeaturesValidate() ' + ((flags & 4) ? 'checked' : '') + '>' + "Record sessions" + '</label><br></div>';
  13241. }
  13242. if ((currentMesh.mtype == 2) || (currentMesh.mtype == 4)) {
  13243. x += '<div><label><input type=checkbox id=d20flag2 onchange=p20editmeshfeaturesValidate() ' + ((flags & 2) ? 'checked' : '') + '>' + ((currentMesh.mtype == 4)?"Sync server device name to port name":"Sync server device name to hostname") + '</label><br></div>';
  13244. if (currentMesh.mtype == 2) {
  13245. x += '<div style="margin-left:8px"><label><input type=checkbox id=d20flag8 onchange=p20editmeshfeaturesValidate() ' + ((flags & 8) ? 'checked' : '') + '>' + "Prefer value of --agentName" + '</label><br></div>';
  13246. x += '<div style="margin-left:8px"><label><input type=checkbox id=d20flag16 onchange=p20editmeshfeaturesValidate() ' + ((flags & 16) ? 'checked' : '') + '>' + "Allow to override server device name until next connection" + '</label><br></div>';
  13247. }
  13248. x += '<div><label><input type=checkbox id=d20flag1 onchange=p20editmeshfeaturesValidate() ' + ((flags & 1) ? 'checked' : '') + '>' + "Remove device on disconnect" + '</label><br></div>';
  13249. }
  13250. x += '<div><label><input type=checkbox id=d20expireDevice onchange=p20editmeshfeaturesValidate() ' + ((expire > 0) ? 'checked' : '') + '>' + "Automatically remove inactive devices" + '</label><br></div>';
  13251. x += '<div style=margin-left:20px id=d20expireDeviceDev>' + "Inactivate days until removal" + ' <label><input type=number inputmode=numeric onchange=p20editmeshfeaturesValidate() onkeyup=p20editmeshfeaturesValidate() onpaste=p20editmeshfeaturesValidate() onkeydown=p20editmeshfeaturesValidate() maxlength=4 min=1 max=2000 style=width:80px value=\"' + ((expire == 0)?30:expire) + '\" id=d20expireDeviceDays></label><br></div>';
  13252. if (serverinfo.autoremoveinactivedevices) {
  13253. if (serverinfo.autoremoveinactivedevices == 1) {
  13254. x += format('<span style=font-size:x-small;margin-left:6px>' + "By default, inactive devices will be removed after 1 day." + '</span>', serverinfo.autoremoveinactivedevices);
  13255. } else {
  13256. x += format('<span style=font-size:x-small;margin-left:6px>' + "By default, inactive devices will be removed after {0} days." + '</span>', serverinfo.autoremoveinactivedevices);
  13257. }
  13258. }
  13259. setDialogMode(2, "Edit Device Group Features", 3, p20editmeshfeaturesEx, x);
  13260. p20editmeshfeaturesValidate();
  13261. }
  13262. function p20editmeshfeaturesValidate() {
  13263. var flags = 0, ok = true;
  13264. if (((currentMesh.mtype == 2) || (currentMesh.mtype == 4)) && (Q('d20flag1').checked)) { flags += 1; }
  13265. if (currentMesh.mtype == 2) {
  13266. if (Q('d20flag2').checked) {
  13267. flags += 2;
  13268. if (event.currentTarget.id == 'd20flag2') { Q('d20flag8').checked = true; Q('d20flag16').checked = true; }
  13269. }
  13270. for (const flag of [8, 16]) {
  13271. const element = Q('d20flag' + flag);
  13272. if ((element.checked = element.checked && !(element.disabled = !(flags & 2)))) { flags += flag; }
  13273. }
  13274. }
  13275. QE('d20expireDevice', (flags & 1) == 0);
  13276. var x = ((flags & 1) == 0) && Q('d20expireDevice').checked;
  13277. QV('d20expireDeviceDev', x);
  13278. if (x) { var y = parseInt(Q('d20expireDeviceDays').value); if (isNaN(y) || (y < 1) || (y > 2000) || (y != Q('d20expireDeviceDays').value)) { ok = false; } }
  13279. QE('idx_dlgOkButton', ok);
  13280. }
  13281. function p20editmeshfeaturesEx() {
  13282. var flags = 0;
  13283. if ((currentMesh.mtype == 2) || (currentMesh.mtype == 4)) {
  13284. if (Q('d20flag1').checked) { flags += 1; }
  13285. if (Q('d20flag2').checked) { flags += 2; }
  13286. if (Q('d20flag8').checked) { flags += 8; }
  13287. if (Q('d20flag16').checked) { flags += 16; }
  13288. }
  13289. if ((serverinfo.devGroupSessionRecording == 1) && (currentMesh.mtype != 4)) { if (Q('d20flag4').checked) { flags += 4; } }
  13290. var expireDevs = 0;
  13291. if (((flags & 1) == 0) && Q('d20expireDevice').checked) { expireDevs = parseInt(Q('d20expireDeviceDays').value); }
  13292. meshserver.send({ action: 'editmesh', meshid: currentMesh._id, flags: flags, expireDevs: expireDevs });
  13293. }
  13294. function p20showAddMeshUserDialog(userid, selected) {
  13295. if (xxdialogMode) return false;
  13296. var x = '';
  13297. if ((userid == null) || (userid == 5)) {
  13298. if (selected == null) {
  13299. if (userid == null) { x += "Allow users to manage this device group and devices in this group."; } else { x += "Allow users to manage this device."; }
  13300. if (features & 0x00080000) { x += " Users need to login to this server once before they can be added to a device group." }
  13301. x += '<br /><br />';
  13302. }
  13303. x += '<div style=\'position:relative\'>';
  13304. x += addHtmlValue("User Identifiers", '<input id=dp20username style=width:230px maxlength=256 onchange=p20validateAddMeshUserDialog() onkeyup=p20validateAddMeshUserDialog() placeholder="user1, user2, user3" />');
  13305. x += '<div id=dp20usersuggest class=suggestionBox style=\'top:30px;left:130px;display:none\'></div>';
  13306. x += '</div><br>';
  13307. } else if (userid == 1) {
  13308. var y = '';
  13309. if (selected == null) {
  13310. var omeshs = getOrderedList(meshes, 'name');
  13311. for (var i in omeshs) { if ((currentUser.links == null) || (currentUser.links[omeshs[i]._id] == null)) { y += '<option value=' + encodeURIComponentEx(omeshs[i]._id) + '>' + EscapeHtml(omeshs[i].name) + '</option>'; } }
  13312. } else {
  13313. y += '<option value=' + selected + '>' + EscapeHtml(meshes[decodeURIComponent(selected)].name) + '</option>';
  13314. }
  13315. x += addHtmlValue("Device Group", '<div style=width:230px;margin:0;padding:0><select onchange=p20validateAddMeshUserDialog() id=dp2groupid style=width:100%' + (selected?' disabled':'') + '>' + y + '</select></div>');
  13316. } else if (userid == 2) {
  13317. if (usergroups == null) return;
  13318. var y = '';
  13319. var ousergroups = getOrderedList(usergroups, 'name');
  13320. for (var i in ousergroups) {
  13321. if (currentMesh._id.split('/')[1] != ousergroups[i]._id.split('/')[1]) continue;
  13322. if ((currentMesh.links == null) || (currentMesh.links[ousergroups[i]._id] == null)) { y += '<option value=' + encodeURIComponentEx(ousergroups[i]._id) + '>' + EscapeHtml(ousergroups[i].name) + '</option>'; }
  13323. }
  13324. x += addHtmlValue("User Group", '<div style=width:230px;margin:0;padding:0><select onchange=p20validateAddMeshUserDialog() id=dp2groupid style=width:100%>' + y + '</select></div>');
  13325. } else if (userid == 6) {
  13326. if (usergroups == null) return;
  13327. var y = '';
  13328. if (selected == null) {
  13329. var ousergroups = getOrderedList(usergroups, 'name');
  13330. for (var i in ousergroups) {
  13331. if ((ousergroups[i].membershipType != null) || (currentNode._id.split('/')[1] != ousergroups[i]._id.split('/')[1])) continue;
  13332. if ((currentNode.links == null) || (currentNode.links[ousergroups[i]._id] == null)) { y += '<option value=' + encodeURIComponentEx(ousergroups[i]._id) + '>' + EscapeHtml(ousergroups[i].name) + '</option>'; }
  13333. }
  13334. } else {
  13335. y += '<option value=' + selected + '>' + EscapeHtml(usergroups[decodeURIComponent(selected)].name) + '</option>';
  13336. }
  13337. x += addHtmlValue("User Group", '<div style=width:230px;margin:0;padding:0><select onchange=p20validateAddMeshUserDialog() id=dp2groupid style=width:100%' + (selected?' disabled':'') + '>' + y + '</select></div>');
  13338. } else if (userid == 3) {
  13339. var y = '';
  13340. if (selected) { selected = decodeURIComponent(selected); }
  13341. var omeshs = getOrderedList(meshes, 'name');
  13342. for (var i in omeshs) { if ((selected != null) || (currentUserGroup.links == null) || (currentUserGroup.links[omeshs[i]._id] == null)) { y += '<option value=' + encodeURIComponentEx(omeshs[i]._id) + ((selected == omeshs[i]._id)?' selected':' ') + '>' + EscapeHtml(omeshs[i].name) + '</option>'; } }
  13343. x += addHtmlValue("Device Group", '<div style=width:230px;margin:0;padding:0><select onchange=p20validateAddMeshUserDialog(' + userid + ') id=dp2groupid style=width:100%>' + y + '</select></div>');
  13344. } else if ((userid == 4) || (userid == 7)) {
  13345. var y = '', selectedMeshId = null, selectedNode = null;
  13346. if (selected != null) { selectedNode = getNodeFromId(decodeURIComponent(selected)); if (selectedNode != null) { selectedMeshId = selectedNode.meshid; } }
  13347. var omeshs = getOrderedList(meshes, 'name');
  13348. for (var i in omeshs) {
  13349. if ((omeshs[i].links[userinfo._id] != null) && (omeshs[i].links[userinfo._id].rights & 7)) { // Only show device groups that we have user administrator for.
  13350. if (selectedMeshId == null) { selectedMeshId = omeshs[i]._id; } y += '<option value=' + encodeURIComponentEx(omeshs[i]._id) + ((selectedMeshId == omeshs[i]._id)?' selected':' ') + '>' + EscapeHtml(omeshs[i].name) + '</option>';
  13351. }
  13352. }
  13353. x += addHtmlValue("Device Group", '<div style=width:230px;margin:0;padding:0><select onchange=p20changeMeshAddMeshUserDialog(' + userid + ') id=dp2meshid style=width:100%>' + y + '</select></div>');
  13354. y = '';
  13355. var onodes = getOrderedList(nodes, 'name');
  13356. for (var i in onodes) { if (onodes[i].meshid == selectedMeshId) { y += '<option value=' + encodeURIComponentEx(onodes[i]._id) + ((selectedNode == onodes[i])?' selected':' ') + '>' + EscapeHtml(onodes[i].name) + '</option>'; } }
  13357. x += addHtmlValue("Device", '<div style=width:230px;margin:0;padding:0><select onchange=p20validateAddMeshUserDialog(' + userid + ') id=dp2nodeid style=width:100%>' + y + '</select></div>');
  13358. } else {
  13359. userid = decodeURIComponent(userid);
  13360. var uname = userid.split('/')[2];
  13361. if (users && users[userid]) { uname = users[userid].name; }
  13362. if (usergroups && usergroups[userid]) { uname = usergroups[userid].name; }
  13363. if (userinfo._id == userid) { uname = userinfo.name; }
  13364. if (userid.startsWith('ugrp/')) {
  13365. x += format("Group permissions for {0}.", uname) + '<br /><br />';
  13366. } else {
  13367. x += format("Group permissions for user {0}.", uname) + '<br /><br />';
  13368. }
  13369. }
  13370. var urights = -1, meshRightsActive = ((userid != 4) && (userid != 5) && (userid != 6) && (userid != 7));
  13371. x += '<div style="height:120px;overflow-y:scroll;border:1px solid gray;resize:vertical;">';
  13372. if (meshRightsActive) {
  13373. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20fulladmin>' + "Full Administrator" + '</label><br>';
  13374. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20editmesh>' + "Edit Device Group" + '</label><br>';
  13375. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20manageusers>' + "Manage Device Group Users" + '</label><br>';
  13376. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20managecomputers>' + "Manage Device Group Computers" + '</label><br>';
  13377. }
  13378. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20remotecontrol>' + "Remote Control & Relay" + '</label><br>';
  13379. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20remoteview style=margin-left:12px>' + "Remote View Only" + '</label><br>';
  13380. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20remotelimitedinput style=margin-left:12px>' + "Limited Input Only" + '</label><br>';
  13381. if (serverinfo.guestdevicesharing !== false) { x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20guestshare style=margin-left:12px>' + "Guest Sharing" + '</label><br>'; }
  13382. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20nodesktop style=margin-left:12px>' + "No Desktop Access" + '</label><br>';
  13383. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20noterminal style=margin-left:12px>' + "No Terminal Access" + '</label><br>';
  13384. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20nofiles style=margin-left:12px>' + "No File Access" + '</label><br>';
  13385. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20noamt style=margin-left:12px>' + "No Intel&reg; AMT" + '</label><br>';
  13386. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20meshagentconsole>' + "Mesh Agent Console" + '</label><br>';
  13387. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20meshserverfiles>' + "Server Files" + '</label><br>';
  13388. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20wakedevices>' + "Wake Devices" + '</label><br>';
  13389. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20editnotes>' + "Edit Device Notes" + '</label><br>';
  13390. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20limitevents>' + "Show Only Own Events" + '</label><br>';
  13391. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20chatnotify>' + "Chat & Notify" + '</label><br>';
  13392. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20uninstall>' + "Uninstall Agent / Delete Device" + '</label><br>';
  13393. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20commands>' + "Remote Commands" + '</label><br>';
  13394. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20resetoff>' + "Reset / Power Off" + '</label><br>';
  13395. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20details>' + "Device Details" + '</label><br>';
  13396. x += '<label><input type=checkbox onchange=p20validateAddMeshUserDialog() id=p20relay>' + "Use as Relay" + '</label><br>';
  13397. x += '</div>';
  13398. if (userid == null) {
  13399. setDialogMode(2, "Add Users to Device Group", 3, p20showAddMeshUserDialogEx, x);
  13400. QE('p20fulladmin', GetMeshRights(currentMesh) == 0xFFFFFFFF);
  13401. Q('dp20username').focus();
  13402. } else if (userid === 1) {
  13403. setDialogMode(2, (selected == null)?"Add Device Group Permissions":"Edit Device Group Permissions", selected?7:3, p20showAddMeshUserDialogEx, x, userid);
  13404. if (selected != null) { urights = meshes[decodeURIComponent(selected)].links[currentUser._id].rights; }
  13405. if (urights == 0xFFFFFFFF) { Q('p20fulladmin').checked = true; urights = -1; }
  13406. } else if (userid === 2) {
  13407. setDialogMode(2, "Add User Group", 3, p20showAddMeshUserDialogEx, x, userid);
  13408. QE('p20fulladmin', GetMeshRights(currentMesh) == 0xFFFFFFFF);
  13409. } else if (userid === 3) {
  13410. setDialogMode(2, (selected == null)?"Add Device Group":"Edit Device Group", selected?7:3, p20showAddMeshUserDialogEx, x, userid);
  13411. QE('dp2groupid', selected == null);
  13412. if (selected != null) { urights = currentUserGroup.links[decodeURIComponent(selected)].rights; }
  13413. if (urights == 0xFFFFFFFF) { Q('p20fulladmin').checked = true; urights = -1; }
  13414. } else if (userid === 4) {
  13415. setDialogMode(2, (selected == null)?"Add Device Permissions":"Edit Device Permissions", selected?7:3, p20showAddMeshUserDialogEx, x, userid);
  13416. QE('dp2meshid', selected == null);
  13417. QE('dp2nodeid', selected == null);
  13418. } else if (userid === 7) {
  13419. setDialogMode(2, (selected == null)?"Add Device Permissions":"Edit Device Permissions", selected?7:3, p20showAddMeshUserDialogEx, x, userid);
  13420. QE('dp2meshid', selected == null);
  13421. QE('dp2nodeid', selected == null);
  13422. if (selected != null) { urights = currentUserGroup.links[decodeURIComponent(selected)].rights; QE('dp20username', false); }
  13423. } else if (userid === 5) {
  13424. setDialogMode(2, selected?"Edit User Device Permissions":"Add User Device Permissions", selected?7:3, p20showAddMeshUserDialogEx, x, userid);
  13425. if (selected != null) {
  13426. selected = decodeURIComponent(selected);
  13427. if ((users != null) && (users[selected] != null)) { Q('dp20username').value = users[selected].name; } else { Q('dp20username').value = selected.split('/')[2]; }
  13428. urights = currentNode.links[selected].rights;
  13429. QE('dp20username', false);
  13430. }
  13431. Q('dp20username').focus();
  13432. } else if (userid === 6) {
  13433. setDialogMode(2, selected?"Edit User Group Device Permissions":"Add User Group Device Permissions", selected?7:3, p20showAddMeshUserDialogEx, x, userid);
  13434. if (selected != null) { urights = currentNode.links[decodeURIComponent(selected)].rights; }
  13435. } else {
  13436. if (userid.startsWith('ugrp/')) {
  13437. setDialogMode(2, "Edit Device Group Permissions", 7, p20showAddMeshUserDialogEx, x, userid);
  13438. } else {
  13439. setDialogMode(2, "Edit User Device Group Permissions", 7, p20showAddMeshUserDialogEx, x, userid);
  13440. }
  13441. var cmeshrights = GetMeshRights(currentMesh), urights = GetMeshRights(currentMesh, userid);
  13442. if (urights == 0xFFFFFFFF) { Q('p20fulladmin').checked = true; urights = -1; }
  13443. QE('p20fulladmin', GetMeshRights(currentMesh) == 0xFFFFFFFF);
  13444. }
  13445. if (urights != -1) {
  13446. if (meshRightsActive) {
  13447. if (urights & 1) { Q('p20editmesh').checked = true; }
  13448. if (urights & 2) { Q('p20manageusers').checked = true; }
  13449. if (urights & 4) { Q('p20managecomputers').checked = true; }
  13450. }
  13451. if (urights & 8) {
  13452. Q('p20remotecontrol').checked = true;
  13453. if (urights & 65536) { Q('p20nodesktop').checked = true; }
  13454. if (urights & 256) { Q('p20remoteview').checked = true; }
  13455. if ((urights & 524288) && (serverinfo.guestdevicesharing !== false)) { Q('p20guestshare').checked = true; }
  13456. if (urights & 512) { Q('p20noterminal').checked = true; }
  13457. if (urights & 1024) { Q('p20nofiles').checked = true; }
  13458. if (urights & 2048) { Q('p20noamt').checked = true; }
  13459. if (urights & 4096) { Q('p20remotelimitedinput').checked = true; }
  13460. }
  13461. if (urights & 16) { Q('p20meshagentconsole').checked = true; }
  13462. if (urights & 32) { Q('p20meshserverfiles').checked = true; }
  13463. if (urights & 64) { Q('p20wakedevices').checked = true; }
  13464. if (urights & 128) { Q('p20editnotes').checked = true; }
  13465. if (urights & 8192) { Q('p20limitevents').checked = true; }
  13466. if (urights & 16384) { Q('p20chatnotify').checked = true; }
  13467. if (urights & 32768) { Q('p20uninstall').checked = true; }
  13468. if (urights & 131072) { Q('p20commands').checked = true; }
  13469. if (urights & 262144) { Q('p20resetoff').checked = true; }
  13470. if ((urights & 524288) && (serverinfo.guestdevicesharing !== false)) { Q('p20guestshare').checked = true; }
  13471. if (urights & 1048576) { Q('p20details').checked = true; }
  13472. if (urights & 2097152) { Q('p20relay').checked = true; }
  13473. }
  13474. p20validateAddMeshUserDialog(userid);
  13475. return false;
  13476. }
  13477. function p20changeMeshAddMeshUserDialog(userid) {
  13478. var y = '', meshid = decodeURIComponent(Q('dp2meshid').value);
  13479. for (var i in nodes) { if (nodes[i].meshid == meshid) { y += '<option value=' + encodeURIComponentEx(nodes[i]._id) + '>' + EscapeHtml(nodes[i].name) + '</option>'; } }
  13480. QH('dp2nodeid', y);
  13481. p20validateAddMeshUserDialog(userid);
  13482. }
  13483. function p20setname(name) {
  13484. name = decodeURIComponent(name);
  13485. var xusers = Q('dp20username').value.split(',');
  13486. for (var i in xusers) { xusers[i] = xusers[i].trim(); }
  13487. xusers[xusers.length - 1] = name;
  13488. Q('dp20username').value = xusers.join(', ');
  13489. p20validateAddMeshUserDialog();
  13490. return false;
  13491. }
  13492. function p20validateAddMeshUserDialog(updateId) {
  13493. var ok = true;
  13494. if (updateId === 4) {
  13495. // Update user device rights
  13496. var devrights = 0, nodeid = decodeURIComponent(Q('dp2nodeid').value);
  13497. if ((nodeid != '') && (currentUser.links != null) && (currentUser.links[nodeid] != null)) { devrights = currentUser.links[nodeid].rights; }
  13498. Q('p20remotecontrol').checked = ((devrights & 8) != 0);
  13499. Q('p20meshagentconsole').checked = ((devrights & 16) != 0);
  13500. Q('p20meshserverfiles').checked = ((devrights & 32) != 0);
  13501. Q('p20wakedevices').checked = ((devrights & 64) != 0);
  13502. Q('p20editnotes').checked = ((devrights & 128) != 0);
  13503. Q('p20remoteview').checked = ((devrights & 256) != 0);
  13504. Q('p20noterminal').checked = ((devrights & 512) != 0);
  13505. Q('p20nofiles').checked = ((devrights & 1024) != 0);
  13506. Q('p20noamt').checked = ((devrights & 2048) != 0);
  13507. Q('p20remotelimitedinput').checked = ((devrights & 4096) != 0);
  13508. Q('p20limitevents').checked = ((devrights & 8192) != 0);
  13509. Q('p20chatnotify').checked = ((devrights & 16384) != 0);
  13510. Q('p20uninstall').checked = ((devrights & 32768) != 0);
  13511. Q('p20nodesktop').checked = ((devrights & 65536) != 0);
  13512. Q('p20commands').checked = ((devrights & 131072) != 0);
  13513. Q('p20resetoff').checked = ((devrights & 262144) != 0);
  13514. if (serverinfo.guestdevicesharing !== false) { Q('p20guestshare').checked = ((devrights & 524288) != 0); }
  13515. Q('p20details').checked = ((devrights & 1048576) != 0);
  13516. Q('p20relay').checked = ((devrights & 2097152) != 0);
  13517. ok = (nodeid != '');
  13518. }
  13519. /*
  13520. var meshrights = null;
  13521. if ((xxdialogTag === 1) || (xxdialogTag === 3)) {
  13522. meshrights = meshes[decodeURIComponent(Q('dp2groupid').value)].links[userinfo._id].rights;
  13523. //meshrights = GetMeshRights(decodeURIComponent(Q('dp2groupid').value));
  13524. } else {
  13525. meshrights = currentMesh.links[userinfo._id].rights;
  13526. //meshrights = GetMeshRights(currentMesh);
  13527. }
  13528. */
  13529. if (Q('dp20username')) {
  13530. var xusers = Q('dp20username').value.split(',');
  13531. for (var i in xusers) {
  13532. var xuser = xusers[i] = xusers[i].trim();
  13533. if (xuser.length == 0) { ok = false; } else if (xuser.indexOf('"') >= 0) { ok = false; }
  13534. }
  13535. // Fill the suggestion box
  13536. var showsuggestbox = false, exactMatch = false;
  13537. if (users != null) {
  13538. var lastuser = xusers[xusers.length - 1].trim(), lastuserl = lastuser.toLowerCase(), matchingUsers = [];
  13539. if (lastuser.length > 0) {
  13540. for (var i in users) {
  13541. var userSplit = users[i]._id.split('/');
  13542. if ((currentMesh != null) && (currentMesh.domain != userSplit[1])) continue;
  13543. if ((currentNode != null) && (currentNode.domain != userSplit[1])) continue;
  13544. if (userSplit[2] === lastuserl) { exactMatch = true; break; }
  13545. if (users[i].name.toLowerCase().indexOf(lastuserl) >= 0) { matchingUsers.push([users[i]._id, users[i].name]); if (matchingUsers.length >= 8) break; }
  13546. }
  13547. if ((exactMatch == false) && (matchingUsers.length > 0)) {
  13548. var x = '';
  13549. for (var i in matchingUsers) {
  13550. var sid = matchingUsers[i][0], sname = matchingUsers[i][1];
  13551. if (sid.split('/')[2] == sname.toLowerCase()) {
  13552. x += '<div class=suggestionBoxItem onclick=\'p20setname("' + encodeURIComponentEx(sid.split('/')[2]) + '")\'>' + EscapeHtml(sname) + '</div>';
  13553. } else {
  13554. x += '<div class=suggestionBoxItem onclick=\'p20setname("' + encodeURIComponentEx(sid.split('/')[2]) + '")\'><div>' + EscapeHtml(sname) + '</div><div class=suggestionBoxSubItem>' + EscapeHtml(sid.split('/')[2]) + '</div></div>';
  13555. }
  13556. }
  13557. QH('dp20usersuggest', x);
  13558. showsuggestbox = true;
  13559. }
  13560. }
  13561. }
  13562. QV('dp20usersuggest', showsuggestbox);
  13563. }
  13564. QE('idx_dlgOkButton', ok);
  13565. var nc;
  13566. if (Q('p20fulladmin') != null) {
  13567. nc = !Q('p20fulladmin').checked;
  13568. //QE('p20fulladmin', meshrights == 0xFFFFFFFF);
  13569. //QE('p20editmesh', nc && (meshrights == 0xFFFFFFFF));
  13570. //QE('p20fulladmin', nc);
  13571. QE('p20editmesh', nc);
  13572. QE('p20manageusers', nc);
  13573. QE('p20managecomputers', nc);
  13574. } else {
  13575. nc = (nodeid != '');
  13576. }
  13577. QE('p20remotecontrol', nc);
  13578. QE('p20meshagentconsole', nc);
  13579. QE('p20meshserverfiles', nc);
  13580. QE('p20wakedevices', nc);
  13581. QE('p20editnotes', nc);
  13582. QE('p20limitevents', nc);
  13583. QE('p20remoteview', nc && Q('p20remotecontrol').checked);
  13584. if (serverinfo.guestdevicesharing !== false) { QE('p20guestshare', nc && Q('p20remotecontrol').checked && (Q('p20remoteview').checked || !Q('p20remotelimitedinput').checked)); }
  13585. QE('p20remotelimitedinput', nc && Q('p20remotecontrol').checked && !Q('p20remoteview').checked);
  13586. QE('p20nodesktop', nc && Q('p20remotecontrol').checked);
  13587. QE('p20noterminal', nc && Q('p20remotecontrol').checked);
  13588. QE('p20nofiles', nc && Q('p20remotecontrol').checked);
  13589. QE('p20noamt', nc && Q('p20remotecontrol').checked);
  13590. QE('p20chatnotify', nc);
  13591. QE('p20uninstall', nc);
  13592. QE('p20commands', nc);
  13593. QE('p20resetoff', nc);
  13594. QE('p20details', nc);
  13595. QE('p20relay', nc);
  13596. }
  13597. function p20showAddMeshUserDialogEx(b, t) {
  13598. // Get the currently selected rights
  13599. var meshadmin = 0;
  13600. if ((Q('p20fulladmin') != null) && (Q('p20fulladmin').checked == true)) { meshadmin = 0xFFFFFFFF; } else {
  13601. if (Q('p20fulladmin') != null) {
  13602. if (Q('p20editmesh').checked == true) meshadmin += 1;
  13603. if (Q('p20manageusers').checked == true) meshadmin += 2;
  13604. if (Q('p20managecomputers').checked == true) meshadmin += 4;
  13605. }
  13606. if (Q('p20remotecontrol').checked == true) meshadmin += 8;
  13607. if (Q('p20meshagentconsole').checked == true) meshadmin += 16;
  13608. if (Q('p20meshserverfiles').checked == true) meshadmin += 32;
  13609. if (Q('p20wakedevices').checked == true) meshadmin += 64;
  13610. if (Q('p20editnotes').checked == true) meshadmin += 128;
  13611. if (Q('p20remoteview').checked == true) meshadmin += 256;
  13612. if (Q('p20nodesktop').checked == true) meshadmin += 65536;
  13613. if (Q('p20noterminal').checked == true) meshadmin += 512;
  13614. if (Q('p20nofiles').checked == true) meshadmin += 1024;
  13615. if (Q('p20noamt').checked == true) meshadmin += 2048;
  13616. if ((Q('p20remotelimitedinput').checked == true) && (!Q('p20remoteview').checked)) meshadmin += 4096;
  13617. if (Q('p20limitevents').checked == true) meshadmin += 8192;
  13618. if (Q('p20chatnotify').checked == true) meshadmin += 16384;
  13619. if (Q('p20uninstall').checked == true) meshadmin += 32768;
  13620. if (Q('p20commands').checked == true) meshadmin += 131072;
  13621. if (Q('p20resetoff').checked == true) meshadmin += 262144;
  13622. if ((serverinfo.guestdevicesharing !== false) && (Q('p20guestshare').checked == true) && (Q('p20remoteview').checked || (!Q('p20remoteview').checked && !Q('p20remotelimitedinput').checked))) meshadmin += 524288;
  13623. if (Q('p20details').checked == true) meshadmin += 1048576;
  13624. if (Q('p20relay').checked == true) meshadmin += 2097152;
  13625. }
  13626. // Clean up incorrect rights. If Remote Control is not selected, remove flags that don't make sense.
  13627. if ((meshadmin & 8) == 0) {
  13628. // Remove 256, 512, 1024, 2048, 4096, 65536
  13629. if (meshadmin & 256) { meshadmin -= 256; }
  13630. if (meshadmin & 512) { meshadmin -= 512; }
  13631. if (meshadmin & 1024) { meshadmin -= 1024; }
  13632. if (meshadmin & 2048) { meshadmin -= 2048; }
  13633. if (meshadmin & 4096) { meshadmin -= 4096; }
  13634. if (meshadmin & 65536) { meshadmin -= 65536; }
  13635. }
  13636. // Send the action to the server
  13637. if (t === 1) {
  13638. // Add current user to device group
  13639. var meshid = decodeURIComponent(Q('dp2groupid').value), mesh = meshes[meshid];
  13640. if (mesh != null) { meshserver.send({ action: 'addmeshuser', meshid: meshid, meshname: mesh.name, userids: [ currentUser._id ], meshadmin: meshadmin, remove: (b == 2) }); }
  13641. } else if (t === 2) {
  13642. // Add user group to device group
  13643. var ugrpid = decodeURIComponent(Q('dp2groupid').value), mesh = meshes[currentMesh._id];
  13644. if (mesh != null) { meshserver.send({ action: 'addmeshuser', meshid: currentMesh._id, meshname: currentMesh.name, userid: ugrpid, meshadmin: meshadmin, remove: (b == 2) }); }
  13645. } else if (t === 3) {
  13646. // Add device group to current user group
  13647. var meshid = decodeURIComponent(Q('dp2groupid').value), mesh = meshes[meshid];
  13648. if (mesh != null) { meshserver.send({ action: 'addmeshuser', meshid: meshid, meshname: mesh.name, userids: [ currentUserGroup._id ], meshadmin: meshadmin, remove: (b == 2) }); }
  13649. } else if (t === 4) {
  13650. // Add current user to device
  13651. var nodeid = decodeURIComponent(Q('dp2nodeid').value), node = getNodeFromId(nodeid);
  13652. if (node != null) { meshserver.send({ action: 'adddeviceuser', nodeid: nodeid, nodename: node.name, userids: [ currentUser._id ], rights: meshadmin, remove: (b == 2) }); }
  13653. } else if (t === 5) {
  13654. // Add users to device
  13655. var users = Q('dp20username').value.split(','), users2 = [];
  13656. for (var i in users) { users2.push(users[i].trim()); }
  13657. meshserver.send({ action: 'adddeviceuser', nodeid: currentNode._id, nodename: currentNode.name, usernames: users2, rights: meshadmin, remove: (b == 2) });
  13658. } else if (t === 6) {
  13659. // Add user group to device
  13660. var ugrpid = decodeURIComponent(Q('dp2groupid').value);
  13661. if (currentNode != null) { meshserver.send({ action: 'adddeviceuser', nodeid: currentNode._id, nodename: currentNode.name, userids: [ ugrpid ], rights: meshadmin, remove: (b == 2) }); }
  13662. } else if (t === 7) {
  13663. // Add current user group to device
  13664. var nodeid = decodeURIComponent(Q('dp2nodeid').value), node = getNodeFromId(nodeid);
  13665. if (node != null) { meshserver.send({ action: 'adddeviceuser', nodeid: nodeid, nodename: node.name, userids: [ currentUserGroup._id ], rights: meshadmin, remove: (b == 2) }); }
  13666. } else {
  13667. // Add user to device group
  13668. if (t == null) {
  13669. var users = Q('dp20username').value.split(','), users2 = [];
  13670. for (var i in users) { users2.push(users[i].trim()); }
  13671. meshserver.send({ action: 'addmeshuser', meshid: currentMesh._id, meshname: currentMesh.name, usernames: users2, meshadmin: meshadmin, remove: (b == 2) });
  13672. } else {
  13673. meshserver.send({ action: 'addmeshuser', meshid: currentMesh._id, meshname: currentMesh.name, userids: [ t ], meshadmin: meshadmin, remove: (b == 2) });
  13674. }
  13675. }
  13676. }
  13677. function p20viewuser(userid) {
  13678. if (xxdialogMode) return;
  13679. var xuserid = decodeURIComponent(userid);
  13680. var cmeshrights = GetMeshRights(currentMesh), meshrights = GetMeshRights(currentMesh, xuserid);
  13681. if (((userinfo._id) != xuserid) && (cmeshrights == 0xFFFFFFFF || (((cmeshrights & 2) != 0) && (meshrights != 0xFFFFFFFF)))) {
  13682. p20showAddMeshUserDialog(userid);
  13683. } else {
  13684. var r = [];
  13685. if (meshrights == 0xFFFFFFFF) r.push("Full Administrator (all rights)"); else {
  13686. if ((meshrights & 1) != 0) r.push("Edit Device Group");
  13687. if ((meshrights & 2) != 0) r.push("Manage Device Group Users");
  13688. if ((meshrights & 4) != 0) r.push("Manage Device Group Computers");
  13689. if ((meshrights & 8) != 0) r.push("Remote Control");
  13690. if ((meshrights & 16) != 0) r.push("Agent Console");
  13691. if ((meshrights & 32) != 0) r.push("Server Files");
  13692. if ((meshrights & 64) != 0) r.push("Wake Devices");
  13693. if ((meshrights & 128) != 0) r.push("Edit Notes");
  13694. if (((meshrights & 8) != 0) && (meshrights & 256) != 0) r.push("Remote View Only");
  13695. if (((meshrights & 8) != 0) && (meshrights & 65536) != 0) r.push("No Desktop");
  13696. if (((meshrights & 8) != 0) && (meshrights & 512) != 0) r.push("No Terminal");
  13697. if (((meshrights & 8) != 0) && (meshrights & 1024) != 0) r.push("No Files");
  13698. if (((meshrights & 8) != 0) && (meshrights & 2048) != 0) r.push("No Intel&reg; AMT");
  13699. if (((meshrights & 8) != 0) && ((meshrights & 4096) != 0) && ((meshrights & 256) == 0)) r.push("Limited Input");
  13700. if ((meshrights & 8192) != 0) r.push("Self Events Only");
  13701. if ((meshrights & 16384) != 0) r.push("Chat & Notify");
  13702. if ((meshrights & 32768) != 0) r.push("Uninstall");
  13703. if ((meshrights & 131072) != 0) r.push("Commands");
  13704. if ((meshrights & 262144) != 0) r.push("Reset/Off");
  13705. if ((meshrights & 524288) != 0) r.push("Sharing");
  13706. if ((meshrights & 1048576) != 0) r.push("Details");
  13707. if ((meshrights & 2097152) != 0) r.push("Relay");
  13708. }
  13709. if (r.length == 0) { r.push("No Rights"); }
  13710. var uname = xuserid.split('/')[2];
  13711. if (users && users[xuserid]) { uname = users[xuserid].name; }
  13712. if (userinfo._id == xuserid) { uname = userinfo.name; }
  13713. var buttons = 1, x = addHtmlValue("User Name", EscapeHtml(decodeURIComponent(uname)));
  13714. if (xuserid.split('/')[2] != uname) { x += addHtmlValue("User Identifier", EscapeHtml(xuserid.split('/')[2])); }
  13715. x += addHtmlValue("Permissions", r.join(", "));
  13716. if (((userinfo._id) != xuserid) && (cmeshrights == 0xFFFFFFFF || (((cmeshrights & 2) != 0) && (meshrights != 0xFFFFFFFF)))) buttons += 4;
  13717. setDialogMode(2, "Device Group User", buttons, p20viewuserEx, x, xuserid);
  13718. }
  13719. }
  13720. function p20viewuserEx(button, userid) {
  13721. if (button != 2) return;
  13722. var uname = userid.split('/')[2];
  13723. if (users && users[userid]) { uname = users[userid].name; }
  13724. if (usergroups && usergroups[userid]) { uname = usergroups[userid].name; }
  13725. if (userinfo._id == userid) { uname = userinfo.name; }
  13726. if (userid.startsWith('user/')) { setDialogMode(2, "Remove User Permissions", 3, p20viewuserEx2, format("Confirm removal of rights for user \"{0}\"?", EscapeHtml(decodeURIComponent(uname))), userid); }
  13727. if (userid.startsWith('ugrp/')) { setDialogMode(2, "Remove User Group Permissions", 3, p20viewuserEx2, format("Confirm removal of rights for user group \"{0}\"?", EscapeHtml(decodeURIComponent(uname))), userid); }
  13728. }
  13729. function p20deleteUser(e, userid) { haltEvent(e); p20viewuserEx(2, decodeURIComponent(userid)); return false; }
  13730. function p20viewuserEx2(button, userid) { meshserver.send({ action: 'removemeshuser', meshid: currentMesh._id, meshname: currentMesh.name, userid: userid }); }
  13731. function p20editmeshInviteCode() {
  13732. if (xxdialogMode) return false;
  13733. var meshrights = GetMeshRights(currentMesh);
  13734. var servername = serverinfo.name;
  13735. 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.
  13736. var url;
  13737. if (serverinfo.https == true) {
  13738. var portStr = (serverinfo.port == 443) ? '' : (':' + serverinfo.port);
  13739. url = 'https://' + servername + portStr + domainUrl + 'invite' + ((urlargs.key)?('?key=' + urlargs.key):'');
  13740. } else {
  13741. var portStr = (serverinfo.port == 80) ? '' : (':' + serverinfo.port);
  13742. url = 'http://' + servername + portStr + domainUrl + 'invite' + ((urlargs.key)?('?key=' + urlargs.key):'');
  13743. }
  13744. if (meshrights & 1) {
  13745. // We can edit the mesh invite codes
  13746. var x = "When enabled, invitation codes can be used by anyone to join devices to this device group using the following public link:" + '<br /><br />';
  13747. x += '<div style=width:100%;text-align:center><a rel="noreferrer noopener" target=_blank href="' + url + '">' + url + '</a></div><br />';
  13748. x += '<div style=margin-bottom:5px><label><input id=agentJoinCheck type=checkbox onclick=p20editmeshInviteCodeValidate() />' + "Enable Invite Codes" + '</label></div>';
  13749. x += addHtmlValue("Invite Codes", '<input id=agentInviteCode style=width:236px onkeyup=p20editmeshInviteCodeValidate() placeholder="code1, code2, code3" />');
  13750. x += addHtmlValue("Agents", '<select id=agentType style=width:236px onchange=p20editmeshInviteCodeValidate()><option value=0>' + "All Available Agents" + '</option><option value=1>' + "Windows MeshAgent" + '</option><option value=2>' + "Linux MeshAgent" + '</option><option value=4>' + "MacOS MeshAgent" + '</option><option value=8>' + "MeshCentral Assistant" + '</option></select>');
  13751. x += '<div id=d2agentInstallTypeDiv>';
  13752. x += addHtmlValue("Installation Type", '<select id=agentInviteType style=width:236px><option value=0>' + "Background and interactive" + '</option><option value=2>' + "Background only" + '</option><option value=1>' + "Interactive only" + '</option></select>');
  13753. x += '</div>';
  13754. setDialogMode(2, "Invite Codes", 3, p20editmeshInviteCodeEx, x);
  13755. if (currentMesh.invite != null) {
  13756. Q('agentJoinCheck').checked = true;
  13757. Q('agentInviteCode').value = currentMesh.invite.codes.join(', ');
  13758. if (currentMesh.invite.ag) { Q('agentType').value = currentMesh.invite.ag; }
  13759. Q('agentInviteType').value = (currentMesh.invite.flags & 3);
  13760. }
  13761. p20editmeshInviteCodeValidate();
  13762. } else {
  13763. // View codes only
  13764. var x = "Invitation codes can be used by anyone to join devices to this device group using the following public link:" + '<br /><br />';
  13765. x += '<div style=width:100%;text-align:center><a rel="noreferrer noopener" target=_blank href="' + url + '">' + url + '</a></div><br />';
  13766. x += addHtmlValue("Invite Codes", currentMesh.invite.codes.join(', '));
  13767. x += addHtmlValue("Installation Type", ["Background and interactive", "Interactive only", "Background only"][currentMesh.invite.flags & 3]);
  13768. setDialogMode(2, "Invite Codes", 1, null, x);
  13769. }
  13770. }
  13771. function p20editmeshInviteCodeValidate() {
  13772. var ok = true, codes = Q('agentInviteCode').value.split(',');
  13773. for (var i in codes) { codes[i] = codes[i].trim(); if (codes[i] == '') { ok = false; } }
  13774. QE('agentInviteCode', Q('agentJoinCheck').checked);
  13775. QE('agentType', Q('agentJoinCheck').checked);
  13776. QE('agentInviteType', Q('agentJoinCheck').checked);
  13777. QE('idx_dlgOkButton', (Q('agentJoinCheck').checked == false) || (ok));
  13778. QV('d2agentInstallTypeDiv', parseInt(Q('agentType').value) < 2);
  13779. }
  13780. function p20editmeshInviteCodeEx() {
  13781. if (Q('agentJoinCheck').checked == true) {
  13782. var codes = Q('agentInviteCode').value.split(',');
  13783. for (var i in codes) { codes[i] = codes[i].trim(); }
  13784. meshserver.send({ action: 'editmesh', meshid: currentMesh._id, invite: { codes: codes, flags: parseInt(Q('agentInviteType').value), ag: parseInt(Q('agentType').value) } });
  13785. } else {
  13786. meshserver.send({ action: 'editmesh', meshid: currentMesh._id, invite: '*' });
  13787. }
  13788. }
  13789. function p20editMeshNotify() {
  13790. if (xxdialogMode) return false;
  13791. var meshNotify = 0;
  13792. var emailNotify = ((features2 & 0x00004000) && (userinfo.emailVerified));
  13793. if (userinfo.links && userinfo.links[currentMesh._id] && userinfo.links[currentMesh._id].notify) { meshNotify |= userinfo.links[currentMesh._id].notify; }
  13794. if (userinfo.notify && userinfo.notify[currentMesh._id]) { meshNotify |= userinfo.notify[currentMesh._id]; }
  13795. //var x = "Notification settings must also be turned on in account settings." + '<br /><br />';
  13796. var x = '<div style="border-bottom: 1px solid #888;margin-bottom:3px">' + "Web Page Notifications" + '</div>';
  13797. x += '<div><label><input id=p20notifyIntelDeviceConnect type=checkbox />' + "Device connections" + '</label></div>';
  13798. x += '<div><label><input id=p20notifyIntelDeviceDisconnect type=checkbox />' + "Device disconnections" + '</label></div>';
  13799. if (currentMesh.mtype < 3) {
  13800. x += '<div><label><input id=p20notifyIntelAmtKvmActions type=checkbox />' + "Intel&reg; AMT desktop and serial events" + '</label></div>';
  13801. }
  13802. if (emailNotify) {
  13803. x += '<br /><div style="border-bottom: 1px solid #888;margin-bottom:3px">' + "Email Notifications" + '</div>';
  13804. x += '<div><label><input id=p20enotifyIntelDeviceConnect type=checkbox />' + "Device connections" + '</label></div>';
  13805. x += '<div><label><input id=p20enotifyIntelDeviceDisconnect type=checkbox />' + "Device disconnections" + '</label></div>';
  13806. x += '<div><label><input id=p20enotifyIntelDeviceHelp type=checkbox />' + "Help requests" + '</label></div>';
  13807. }
  13808. if ((userinfo.msghandle != null) && ((features2 & 0x02000000) != 0)) {
  13809. x += '<br /><div style="border-bottom: 1px solid #888;margin-bottom:3px">' + "Messaging Notifications" + '</div>';
  13810. x += '<div><label><input id=p20emsgDeviceConnect type=checkbox />' + "Device connections" + '</label></div>';
  13811. x += '<div><label><input id=p20emsgDeviceDisconnect type=checkbox />' + "Device disconnections" + '</label></div>';
  13812. x += '<div><label><input id=p20emsgDeviceHelp type=checkbox />' + "Help requests" + '</label></div>';
  13813. }
  13814. setDialogMode(2, "Notification Settings", 3, p20editMeshNotifyEx, x, emailNotify);
  13815. Q('p20notifyIntelDeviceConnect').checked = (meshNotify & 2);
  13816. Q('p20notifyIntelDeviceDisconnect').checked = (meshNotify & 4);
  13817. if (currentMesh.mtype < 3) { Q('p20notifyIntelAmtKvmActions').checked = (meshNotify & 8); }
  13818. if (emailNotify) {
  13819. Q('p20enotifyIntelDeviceConnect').checked = (meshNotify & 16);
  13820. Q('p20enotifyIntelDeviceDisconnect').checked = (meshNotify & 32);
  13821. Q('p20enotifyIntelDeviceHelp').checked = (meshNotify & 64);
  13822. }
  13823. if ((userinfo.msghandle != null) && ((features2 & 0x02000000) != 0)) {
  13824. Q('p20emsgDeviceConnect').checked = (meshNotify & 128);
  13825. Q('p20emsgDeviceDisconnect').checked = (meshNotify & 256);
  13826. Q('p20emsgDeviceHelp').checked = (meshNotify & 512);
  13827. }
  13828. return false;
  13829. }
  13830. function p20editMeshNotifyEx(b, emailNotify) {
  13831. var meshNotify = 0;
  13832. meshNotify += Q('p20notifyIntelDeviceConnect').checked ? 2 : 0;
  13833. meshNotify += Q('p20notifyIntelDeviceDisconnect').checked ? 4 : 0;
  13834. if (currentMesh.mtype < 3) { meshNotify += Q('p20notifyIntelAmtKvmActions').checked ? 8 : 0; }
  13835. if (emailNotify) {
  13836. meshNotify += Q('p20enotifyIntelDeviceConnect').checked ? 16 : 0;
  13837. meshNotify += Q('p20enotifyIntelDeviceDisconnect').checked ? 32 : 0;
  13838. meshNotify += Q('p20enotifyIntelDeviceHelp').checked ? 64 : 0;
  13839. }
  13840. if ((userinfo.msghandle != null) && ((features2 & 0x02000000) != 0)) {
  13841. meshNotify += Q('p20emsgDeviceConnect').checked ? 128 : 0;
  13842. meshNotify += Q('p20emsgDeviceDisconnect').checked ? 256 : 0;
  13843. meshNotify += Q('p20emsgDeviceHelp').checked ? 512 : 0;
  13844. }
  13845. meshserver.send({ action: 'changemeshnotify', meshid: currentMesh._id, notify: meshNotify });
  13846. }
  13847. //
  13848. // Mesh Summary
  13849. //
  13850. function setupMeshSummaryStats() {
  13851. window.meshPowerChart = new Chart(document.getElementById('meshPowerChart').getContext('2d'), {
  13852. type: 'doughnut',
  13853. data: { datasets: [{ data: [0, 0], backgroundColor: ['#20F', '#40D', '#60B', '#809', '#A07', '#C05'] }] },
  13854. options: { shadowColor: 'blue', responsive: true, plugins: { legend: { display: false } }, animation: { animateScale: true, animateRotate: true }, layout: { padding: { left: 10, right: 10, top: 10, bottom: 10 } } }
  13855. });
  13856. window.meshOsChart = new Chart(document.getElementById('meshOsChart').getContext('2d'), {
  13857. type: 'doughnut',
  13858. data: { datasets: [{ data: [0, 0], backgroundColor: ['#20F', '#30E', '#40D', '#50C', '#60B', '#70A', '#809', '#908', '#A07', '#B06', '#C05'] }] },
  13859. options: { responsive: true, plugins: { legend: { display: false } }, animation: { animateScale: true, animateRotate: true }, layout: { padding: { left: 10, right: 10, top: 10, bottom: 10 } } }
  13860. });
  13861. window.meshConnChart = new Chart(document.getElementById('meshConnChart').getContext('2d'), {
  13862. type: 'doughnut',
  13863. data: { datasets: [{ data: [0, 0], backgroundColor: ['#20F', '#40D', '#60B', '#809', '#A07', '#C05'] }], labels: ["Not Connected", "Agent", "Intel&reg; AMT", "Agent + Intel&reg; AMT"] },
  13864. options: { responsive: true, plugins: { legend: { display: false } }, animation: { animateScale: true, animateRotate: true }, layout: { padding: { left: 10, right: 10, top: 10, bottom: 10 } } }
  13865. });
  13866. window.meshSecurityChart = new Chart(document.getElementById('meshSecurityChart').getContext('2d'), {
  13867. type: 'doughnut',
  13868. data: { datasets: [{ data: [0, 0], backgroundColor: ['#20F', '#40D', '#60B', '#809', '#A07', '#C05'] }], labels: ["OK", "Anti-virus not active", "No automatic update", "Firewall not active", "Multiple Issues"] },
  13869. options: { responsive: true, plugins: { legend: { display: false } }, animation: { animateScale: true, animateRotate: true }, layout: { padding: { left: 10, right: 10, top: 10, bottom: 10 } } }
  13870. });
  13871. }
  13872. function p21updateMesh() {
  13873. if (currentMesh == null) return;
  13874. // Add device group name
  13875. var meshrights = GetMeshRights(currentMesh), mname = EscapeHtml(currentMesh.name);
  13876. if (mname.length == 0) { mname = '<i>' + "None" + '</i>'; }
  13877. if ((meshrights & 1) != 0) { mname = '<span tabindex=0 title="' + "Click here to edit the device group name" + '" onclick=p20editmesh(1) onkeyup="if (event.key == \'Enter\') p20editmesh(1)" style=cursor:pointer>' + mname + ' <img class=hoverButton src="images/link5.png" /></span>'; }
  13878. QH('p21meshName', mname);
  13879. // Update charts
  13880. var power = {};
  13881. var conn = {};
  13882. var agentTypes = {};
  13883. var powerStates = {};
  13884. var connectivityStates = [ 0, 0, 0, 0 ]; // None, Agent, AMT, Agent + AMT
  13885. var securityStates = [ 0, 0, 0, 0, 0 ]; // OK, no AV, no firewall, no update, many Issues
  13886. var showAgents = false;
  13887. var showPower = false;
  13888. var showConn = false;
  13889. var showSecurity = false;
  13890. for (var i in nodes) {
  13891. if (nodes[i].meshid == currentMesh._id) {
  13892. if (nodes[i].agent) { showAgents = true; if (agentTypes[nodes[i].agent.id] == null) { agentTypes[nodes[i].agent.id] = 1; } else { agentTypes[nodes[i].agent.id]++; } }
  13893. if (nodes[i].pwr) { showPower = true; if (powerStates[nodes[i].pwr] == null) { powerStates[nodes[i].pwr] = 1; } else { powerStates[nodes[i].pwr]++; } }
  13894. if (nodes[i].conn == 0) { showConn = true; connectivityStates[0]++; }
  13895. else if ((nodes[i].conn & 6) != 0) { showConn = true; if ((nodes[i].conn & 1) != 0) { connectivityStates[3]++; } else { connectivityStates[2]++; } }
  13896. else if ((nodes[i].conn & 1) != 0) { showConn = true; connectivityStates[1]++; }
  13897. if (nodes[i].wsc) {
  13898. showSecurity = true;
  13899. var sok = 0;
  13900. if (nodes[i].wsc.antiVirus == 'OK') { sok++; }
  13901. if (nodes[i].wsc.autoUpdate == 'OK') { sok++; }
  13902. if (nodes[i].wsc.firewall == 'OK') { sok++; }
  13903. if (sok == 3) { securityStates[0]++; }
  13904. else if (sok < 2) { securityStates[4]++; }
  13905. else if (nodes[i].wsc.antiVirus != 'OK') { securityStates[1]++; }
  13906. else if (nodes[i].wsc.autoUpdate != 'OK') { securityStates[2]++; }
  13907. else if (nodes[i].wsc.firewall != 'OK') { securityStates[3]++; }
  13908. }
  13909. }
  13910. }
  13911. var agentsData = [], agentsLabels = [], powerData = [], powerLabels = [], xagentsStr = (currentMesh.mtype == 3) ? agentsStrNoAgent : agentsStr;
  13912. for (var i in agentTypes) { agentsData.push(agentTypes[i]); agentsLabels.push(xagentsStr[i]); }
  13913. for (var i in powerStates) { powerData.push(powerStates[i]); powerLabels.push(powerStatetable[i]); }
  13914. window.meshPowerChart.config.data.datasets[0].data = powerData;
  13915. window.meshPowerChart.config.data.labels = powerLabels;
  13916. window.meshPowerChart.update();
  13917. if (currentMesh.mtype >= 2) {
  13918. window.meshOsChart.config.data.datasets[0].data = agentsData;
  13919. window.meshOsChart.config.data.labels = agentsLabels;
  13920. window.meshOsChart.update();
  13921. }
  13922. window.meshConnChart.config.data.datasets[0].data = connectivityStates;
  13923. window.meshConnChart.update();
  13924. window.meshSecurityChart.config.data.datasets[0].data = securityStates;
  13925. window.meshSecurityChart.update();
  13926. // Update tables
  13927. var x = '', count = 0;
  13928. if (powerData.length > 0) {
  13929. var xpowerStates = [];
  13930. for (var i in powerStates) { xpowerStates.push([powerStatetable[i], powerStates[i]]); }
  13931. xpowerStates.sort(function(a, b){ return -(a[1]-b[1]) });
  13932. x += '<table style="margin-top:10px;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 colspan=2 style=text-align:left;width:430px>' + "Power States" + '</th></tr>';
  13933. for (var i in xpowerStates) { x += '<tr style=' + (((++count % 2) == 0) ? 'background-color:#DDD' : '') + '><td style=text-align:right;width:60px>' + xpowerStates[i][1] + ' <td> ' + xpowerStates[i][0] + '</tr>'; }
  13934. x += '</tbody></table>';
  13935. }
  13936. if ((agentsData.length > 0) && (currentMesh.mtype >= 2)) {
  13937. count = 0;
  13938. var xagentTypes = [];
  13939. for (var i in agentTypes) { xagentTypes.push([xagentsStr[i], agentTypes[i]]); }
  13940. xagentTypes.sort(function(a, b){ return -(a[1]-b[1]) });
  13941. x += '<table style="margin-top:10px;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 colspan=2 style=text-align:left;width:430px>' + "Agent Types" + '</th></tr>';
  13942. for (var i in xagentTypes) { x += '<tr style=' + (((++count % 2) == 0) ? 'background-color:#DDD' : '') + '><td style=text-align:right;width:60px>' + xagentTypes[i][1] + '<td> ' + xagentTypes[i][0] + '</tr>'; }
  13943. x += '</tbody></table>';
  13944. }
  13945. if (showConn) {
  13946. count = 0;
  13947. var xconnectivityStates = [];
  13948. for (var i = 0; i < 4; i++) { xconnectivityStates.push([["Not Connected", "Agent", "Intel&reg; AMT", "Agent + Intel&reg; AMT"][i], connectivityStates[i]]); }
  13949. xconnectivityStates.sort(function(a, b){ return -(a[1]-b[1]) });
  13950. x += '<table style="margin-top:10px;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 colspan=2 style=text-align:left;width:430px>' + "Connectivity" + '</th></tr>';
  13951. for (var i = 0; i < 4; i++) { if (xconnectivityStates[i][1] > 0) { x += '<tr style=' + (((++count % 2) == 0) ? 'background-color:#DDD' : '') + '><td style=text-align:right;width:60px>' + xconnectivityStates[i][1] + '<td> ' + xconnectivityStates[i][0] + '</tr>'; } }
  13952. x += '</tbody></table>';
  13953. }
  13954. if (showSecurity) {
  13955. count = 0;
  13956. var xsecurityStates = [];
  13957. for (var i = 0; i < 4; i++) {
  13958. xsecurityStates.push([[
  13959. '<img src="images/link2.png" style=cursor:pointer onclick=\'return p21setDevicesFilter("wsc:ok")\' width=10 height=10 /> ' + "OK",
  13960. '<img src="images/link2.png" style=cursor:pointer onclick=\'return p21setDevicesFilter("wsc:noav")\' width=10 height=10 /> ' + "Anti-virus not active",
  13961. '<img src="images/link2.png" style=cursor:pointer onclick=\'return p21setDevicesFilter("wsc:noupdate")\' width=10 height=10 /> ' + "No automatic update",
  13962. '<img src="images/link2.png" style=cursor:pointer onclick=\'return p21setDevicesFilter("wsc:nofirewall")\' width=10 height=10 /> ' + "Firewall not active",
  13963. '<img src="images/link2.png" style=cursor:pointer onclick=\'return p21setDevicesFilter("wsc:any")\' width=10 height=10 /> ' + "Multiple Issues"
  13964. ][i], securityStates[i]]);
  13965. }
  13966. xsecurityStates.sort(function(a, b){ return -(a[1]-b[1]) });
  13967. x += '<table style="margin-top:10px;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 colspan=2 style=text-align:left;width:430px>' + "Security" + '</th></tr>';
  13968. for (var i = 0; i < 4; i++) { if (xsecurityStates[i][1] > 0) { x += '<tr style=' + (((++count % 2) == 0) ? 'background-color:#DDD' : '') + '><td style=text-align:right;width:60px>' + xsecurityStates[i][1] + '<td> ' + xsecurityStates[i][0] + '</tr>'; } }
  13969. x += '</tbody></table>';
  13970. }
  13971. if (x == '') { x = '<i>' + "No devices in this device group." + '</i>'; }
  13972. QH('p21info', x);
  13973. // Only show the OS chart if the mesh is agent type.
  13974. var graphsCount = ((showPower)?1:0) + (((currentMesh.mtype >= 2) && showAgents)?1:0) + ((showConn)?1:0) + ((showSecurity)?1:0);
  13975. QS('meshPowerChartDiv')['display'] = (showPower)?'inline-block':'none';
  13976. QS('meshOsChartDiv')['display'] = ((currentMesh.mtype >= 2) && showAgents)?'inline-block':'none';
  13977. QS('meshConnChartDiv')['display'] = (showConn)?'inline-block':'none';
  13978. QS('meshSecurityChartDiv')['display'] = (showSecurity)?'inline-block':'none';
  13979. QS('meshPowerChartDiv')['width'] = (graphsCount > 3)?'23%':'31%';
  13980. QS('meshOsChartDiv')['width'] = (graphsCount > 3)?'23%':'31%';
  13981. QS('meshConnChartDiv')['width'] = (graphsCount > 3)?'23%':'31%';
  13982. QS('meshSecurityChartDiv')['width'] = (graphsCount > 3)?'23%':'31%';
  13983. }
  13984. function p21setDevicesFilter(filter) { go(1); Q('KvmSearchInput').value = Q('SearchInput').value = filter; mainUpdate(5); }
  13985. //
  13986. // MY FILES
  13987. //
  13988. var filetreelinkpath;
  13989. var filetreelocation = [];
  13990. function updateFiles() {
  13991. QV('MainMenuMyFiles', ((features & 8) == 0));
  13992. if ((features & 8) != 0) return; // If running on a server without files, exit now.
  13993. var html1 = '', html2 = '', displayPath = '<a href=# style=cursor:pointer onclick="return p5folderup(0)">' + "Root" + '</a>', fullPath = 'Root', publicPath, filetreex = filetree, folderdepth = 1;
  13994. // Navigate to path location, build the paths at the same time
  13995. var filetreelocation2 = [], oldlinkpath = filetreelinkpath, checkedBoxes = [], checkboxes = document.getElementsByName('fc');
  13996. for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { checkedBoxes.push(checkboxes[i].value) }; } // Save all existing checked boxes
  13997. filetreelinkpath = '';
  13998. for (var i in filetreelocation) {
  13999. if ((filetreex.f != null) && (filetreex.f[filetreelocation[i]] != null)) {
  14000. filetreelocation2.push(filetreelocation[i]);
  14001. fullPath += ' / ' + filetreelocation[i];
  14002. if ((folderdepth == 1)) {
  14003. var sp = filetreelocation[i].split('/');
  14004. publicPath = window.location.origin + domainUrl + sp[0] + 'files/' + sp[2];
  14005. //if (filetreelocation[i] === userinfo._id) { filetreelinkpath += 'self'; } else { filetreelinkpath += (sp[0] + '/' + sp[2]); }
  14006. filetreelinkpath += filetreelocation[i];
  14007. } else {
  14008. if (filetreelinkpath != '') { filetreelinkpath += '/' + filetreelocation[i]; if (folderdepth > 2) { publicPath += '/' + filetreelocation[i]; } }
  14009. }
  14010. filetreex = filetreex.f[filetreelocation[i]];
  14011. displayPath += ' / <a href=# style=cursor:pointer onclick="return p5folderup(' + folderdepth + ')">' + EscapeHtml(filetreex.n != null?filetreex.n:filetreelocation[i]) + '</a>';
  14012. folderdepth++;
  14013. } else {
  14014. break;
  14015. }
  14016. }
  14017. filetreelocation = filetreelocation2; // In case we could not go down the full path, we set the new path location here.
  14018. var publicfolder = fullPath.toLowerCase().startsWith('root / ' + userinfo._id + ' / public');
  14019. // Sort the files
  14020. var filetreexx = p5sort_files(filetreex.f);
  14021. // Display all files and folders at this location
  14022. for (var i in filetreexx) {
  14023. // Figure out the name and shortname
  14024. var f = filetreexx[i], name = f.n, shortname;
  14025. // if (name.length > 70) { shortname = '<span title="' + EscapeHtml(name) + '">' + EscapeHtml(name.substring(0, 70)) + "..." + '</span>'; } else { shortname = EscapeHtml(name); }
  14026. // Removed redundant filename length check because we handle it in the CSS
  14027. shortname = EscapeHtml(name);
  14028. // Figure out the date
  14029. var fdatestr = '';
  14030. if (f.d != null) { var fdate = new Date(f.d), fdatestr = printDateTime(fdate) + '&nbsp;'; }
  14031. // Figure out the size
  14032. var fsize = '';
  14033. if (f.s != null) { fsize = getFileSizeStr(f.s); }
  14034. var h = '';
  14035. if (f.t < 3 || f.t == 4) {
  14036. var right = (f.t == 1 || f.t == 4)?p5getQuotabar(f):'', title = '';
  14037. 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 title="' + title + '">' + right + '</span><span title="' + shortname + '"><div class=fileIcon' + f.t + ' onclick=p5folderset("' + encodeURIComponentEx(f.nx) + '")></div><a href=# style=cursor:pointer onclick=\'return p5folderset("' + encodeURIComponentEx(f.nx) + '")\'>' + shortname + '</a></span></div>';
  14038. } else {
  14039. var link = shortname, publiclink = '', loginkey = (urlargs.key)?('&key=' + urlargs.key):'';
  14040. if (publicfolder) { publiclink = '<img src="images/link2.png" style=cursor:pointer title="' + "Display public link" + '" onclick=\'return p5showPublicLink("' + (publicPath + '/' + encodeURIComponentEx(f.nx)) + '?download=1' + loginkey + '")\' width=10 height=10 /> <img src="images/link4.png" title="' + "Copy link to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(publicPath + '/' + encodeURIComponentEx(f.nx) + '?download=1' + loginkey) + '") width=10 height=10>'; }
  14041. if (f.s > 0) { link = publiclink + '<a onclick=downloadFile("downloadfile.ashx?link=' + encodeURIComponentEx(filetreelinkpath + '/' + f.nx) + '")>' + shortname + '</a>'; }
  14042. 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 class=fsize>' + fdatestr + '</span><span style=float:right>' + fsize + '</span><span title="' + shortname + '"><div class=fileIcon' + f.t + '></div>' + link + '</span></div>';
  14043. }
  14044. if (f.t < 3) { html1 += h; } else { html2 += h; }
  14045. }
  14046. //if (f.parent == null) { }
  14047. QH('p5rightOfButtons', p5getQuotabar(filetreex));
  14048. QH('p5files', html1 + html2);
  14049. QH('p5currentpath', displayPath);
  14050. QE('p5FolderUp', filetreelocation.length != 0);
  14051. QV('p5PublicShare', publicfolder);
  14052. // Re-check all boxes if needed
  14053. if (oldlinkpath == filetreelinkpath) {
  14054. checkboxes = document.getElementsByName('fc');
  14055. for (var i = 0; i < checkboxes.length; i++) { checkboxes[i].checked = (checkedBoxes.indexOf(checkboxes[i].value) >= 0); }
  14056. }
  14057. p5setActions();
  14058. }
  14059. function getNiceSize(bytes) {
  14060. if (bytes <= 0) return "Storage limit exceed";
  14061. if (bytes < 2048) return format("{0} bytes remaining", bytes);
  14062. if (bytes < 2097152) return format("{0} kilobytes remaining", Math.round(bytes / 1024));
  14063. if (bytes < 2147483648) return format("{0} megabytes remaining", Math.round(bytes / 1024 / 1024));
  14064. return format("{0} gigabytes remaining", Math.round(bytes / 1024 / 1024 / 1024));
  14065. }
  14066. function getNiceSize2(bytes) {
  14067. if (bytes <= 0) return "None";
  14068. if (bytes < 2048) return format("{0} b", bytes);
  14069. if (bytes < 2097152) return format("{0} Kb", Math.round(bytes / 1024));
  14070. if (bytes < 2147483648) return format("{0} Mb", Math.round(bytes / 1024 / 1024));
  14071. return format("{0} Gb", Math.round(bytes / 1024 / 1024 / 1024));
  14072. }
  14073. function getNiceSize3(bytes) {
  14074. if (bytes <= 0) return "None";
  14075. if (bytes < 2048) return format("{0} b", bytes);
  14076. if (bytes < 2097152) return format("{0} Kb", round(bytes / 1024, 1));
  14077. if (bytes < 2147483648) return format("{0} Mb", round(bytes / 1024 / 1024, 1));
  14078. return format("{0} Gb", round(bytes / 1024 / 1024 / 1024, 1));
  14079. }
  14080. function getNetworkSpeed(bitsPerSecond) {
  14081. if (bitsPerSecond <= 0) return "0 bps";
  14082. if (bitsPerSecond < 1000) return format("{0} bps", bitsPerSecond);
  14083. if (bitsPerSecond < 1000000) return format("{0} Kbps", Math.round(bitsPerSecond / 1000));
  14084. if (bitsPerSecond < 1000000000) return format("{0} Mbps", Math.round(bitsPerSecond / 1000000));
  14085. return format("{0} Gbps", (bitsPerSecond / 1000000000).toFixed(1));
  14086. }
  14087. function p5getQuotabar(f) {
  14088. while (f.t > 1 && f.t != 4) { f = f.parent; }
  14089. if ((f.t != 1 && f.t != 4) || (f.maxbytes == null)) return '';
  14090. var tf = Math.floor(f.s / 1024), tq = (f.maxbytes - f.s);
  14091. var title;
  14092. if (f.c > 1) { title = format("{0}k in {1} files. {2}k maximum", tf, f.c, (Math.floor(f.maxbytes / 1024 / 1024))); } else { title = format("{0}k in 1 file. {1}k maximum", tf, (Math.floor(f.maxbytes / 1024 / 1024))); }
  14093. return '<span title="' + title + '">' + getNiceSize(tq) + ' <progress style=height:10px;width:100px value=' + f.s + ' max=' + f.maxbytes + ' /></span>';
  14094. }
  14095. function p5showPublicLink(u) {
  14096. setDialogMode(2, "Public Link", 1, null, '<input type=text style=width:350px value="' + u + '" readonly /> <img src="images/link4.png" title="' + "Copy link to clipboard" + '" style="cursor:pointer" onclick=copyTextToClip2("' + encodeURIComponentEx(u) + '") width=10 height=10>');
  14097. return false;
  14098. }
  14099. var sortorder;
  14100. function p5sort_filename(a, b) { if (a.ln > b.ln) return (1 * sortorder); if (a.ln < b.ln) return (-1 * sortorder); return 0; }
  14101. function p5sort_timestamp(a, b) { if (a.d > b.d) return (1 * sortorder); if (a.d < b.d) return (-1 * sortorder); return 0; }
  14102. function p5sort_bysize(a, b) { if (a.s == b.s) return p5sort_filename(a, b); return (((a.s - b.s)) * sortorder); }
  14103. function p5sort_files(files) {
  14104. var r = [], sortselection = Q('p5sortdropdown').value;
  14105. 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]); }
  14106. sortorder = 1;
  14107. if (sortselection > 3) { sortorder = -1; sortselection -= 3; }
  14108. if (sortselection == 1) { r.sort(p5sort_filename); }
  14109. else if (sortselection == 2) { r.sort(p5sort_bysize); }
  14110. else if (sortselection == 3) { r.sort(p5sort_timestamp); }
  14111. return r;
  14112. }
  14113. function p5setActions() {
  14114. 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)
  14115. QE('p5DeleteFileButton', (cc > 0) && (filetreelocation.length > 0));
  14116. QE('p5ViewFileButton', (cc == 1) && (sfc == 1) && (filetreelocation.length > 0));
  14117. QE('p5NewFolderButton', filetreelocation.length > 0);
  14118. QE('p5UploadButton', filetreelocation.length > 0);
  14119. QE('p5DownloadButton', (cc > 0) && (cc == sfc) && (filetreelocation.length > 0));
  14120. QE('p5RenameFileButton', (cc == 1) && (filetreelocation.length > 0));
  14121. QE('p5SelectAllButton', tc > 0);
  14122. Q('p5SelectAllButton').value = (cc > 0 ? "Select None" : "Select All");
  14123. QE('p5CutButton', (sfc > 0) && (cc == sfc));
  14124. QE('p5CopyButton', (sfc > 0) && (cc == sfc));
  14125. QE('p5PasteButton', (p5clipboard != null) && (p5clipboard.length > 0) && (filetreelocation.length > 0));
  14126. }
  14127. 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; }
  14128. 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; }
  14129. function getFileCount() { var cc = 0; var checkboxes = document.getElementsByName('fc'); return checkboxes.length; }
  14130. function p5selectallfile() { var nv = (getFileSelCount() == 0), checkboxes = document.getElementsByName('fc'); for (var i = 0; i < checkboxes.length; i++) { checkboxes[i].checked = nv; } p5setActions(); }
  14131. 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; }
  14132. function getFileSizeStr(size) { if (typeof size != 'number') { size = 0; } if (size == 1) return "1 byte"; return format("{0} bytes", size); }
  14133. function p5folderup(x) { if (x == null) { filetreelocation.pop(); } else { while (filetreelocation.length > x) { filetreelocation.pop(); } } updateFiles(); return false; }
  14134. function p5folderset(x) { filetreelocation.push(decodeURIComponent(x)); updateFiles(); return false; }
  14135. function p5createfolder() { setDialogMode(2, "New Folder", 3, p5createfolderEx, '<input type=text id=p5renameinput maxlength=64 onkeyup=p5fileNameCheck(event) style=width:100% />'); focusTextBox('p5renameinput'); p5fileNameCheck(); }
  14136. function p5createfolderEx() { meshserver.send({ action: 'fileoperation', fileop: 'createfolder', path: filetreelocation, newfolder: Q('p5renameinput').value}); }
  14137. 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)); }
  14138. 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 }); }
  14139. 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(); }
  14140. function p5renamefileEx(b, t) { t.newname = Q('p5renameinput').value; meshserver.send(t); }
  14141. function p5fileNameCheck(e) { var x = isFilenameValid(Q('p5renameinput').value); QE('idx_dlgOkButton', x); if ((x == true) && (e && e.keyCode == 13)) { dialogclose(1); } }
  14142. 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] != '.'); } })();
  14143. 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="' + encodeURIComponentEx(filetreelinkpath) + '" /><input type=file name=files id=p5uploadinput style=width:100% multiple=multiple onchange="p5updateUploadDialogOk(\'p5uploadinput\')" /><input type=hidden name=authCookie value=' + authCookie + ' /><input type=submit id=p5loginSubmit style=display:none /><span id=p5confirmOverwriteSpan style=display:none><br /><label><input type=checkbox id=p5confirmOverwrite onchange="p5updateUploadDialogOk(\'p5uploadinput\')" />' + "Confirm overwrite?" + '</label></span></form>'); p5updateUploadDialogOk('p5uploadinput'); }
  14144. function p5uploadFileEx() { Q('p5loginSubmit').click(); }
  14145. function p5GetFileInfo(name) { var filetreex = filetree; for (var i in filetreelocation) { if ((filetreex.f != null) && (filetreex.f[filetreelocation[i]] != null)) { filetreex = filetreex.f[filetreelocation[i]]; } } return filetreex.f[name]; }
  14146. function p5updateUploadDialogOk() {
  14147. // Check if these are files we can upload, remove all folders.
  14148. var xallfiles = Q('p5uploadinput').files, files = [];
  14149. for (var i in xallfiles) { if ((xallfiles[i].size != null) && (xallfiles[i].size != 0)) { files.push(xallfiles[i]); } }
  14150. // Check if these files are duplicates of existing files.
  14151. var filetreex = filetree, allfiles = [], overWriteCount = 0;
  14152. for (var i in filetreelocation) {
  14153. if ((filetreex.f != null) && (filetreex.f[filetreelocation[i]] != null)) { filetreex = filetreex.f[filetreelocation[i]]; }
  14154. }
  14155. QE('idx_dlgOkButton', xallfiles.length > 0);
  14156. if (xallfiles.length > 0) {
  14157. if (filetreex.f != null) {
  14158. for (var i in filetreex.f) { allfiles.push(i); }
  14159. for (var i = 0; i < xallfiles.length; i++) {
  14160. if (allfiles.indexOf(xallfiles[i].name) >= 0) { overWriteCount++; } // TODO: If the server is Windows, we need to lowercase both names.
  14161. }
  14162. }
  14163. QV('p5confirmOverwriteSpan', overWriteCount > 0);
  14164. if (overWriteCount > 0) {
  14165. QE('idx_dlgOkButton', Q('p5confirmOverwrite').checked);
  14166. } else {
  14167. Q('p5confirmOverwrite').checked = false;
  14168. QE('idx_dlgOkButton', true);
  14169. }
  14170. }
  14171. }
  14172. function p5viewfile() {
  14173. var checkboxes = document.getElementsByName('fc');
  14174. for (var i = 0; i < checkboxes.length; i++) {
  14175. if (checkboxes[i].checked) {
  14176. var f = p5GetFileInfo(checkboxes[i].value); if (f == null) return;
  14177. if (f.s <= 204800) { meshserver.send({ action: 'fileoperation', fileop: 'get', path: filetreelocation, file: checkboxes[i].value, tag: 'edit' }); } else { messagebox("File Editor", "Only files less than 200k can be edited."); }
  14178. return;
  14179. }
  14180. }
  14181. }
  14182. var p5clipboard = null, p5clipboardFolder = null, p5clipboardCut = 0;
  14183. function p5copyFile(cut) {
  14184. var checkboxes = document.getElementsByName('fc'); p5clipboard = []; p5clipboardCut = cut, p5clipboardFolder = Clone(filetreelocation);
  14185. for (var i = 0; i < checkboxes.length; i++) { if ((checkboxes[i].checked) && (checkboxes[i].attributes.file.value == '3')) { p5clipboard.push(checkboxes[i].value); } }
  14186. p5updateClipview();
  14187. }
  14188. 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); }
  14189. 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(); } }
  14190. 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(); }
  14191. function p5clearClip() { p5clipboard = null; p5clipboardFolder = null; p5clipboardCut = 0; p5updateClipview(); return false; }
  14192. function p5fileDragDrop(e) {
  14193. if (xxdialogMode) return;
  14194. haltEvent(e);
  14195. QV('bigfail', false);
  14196. QV('bigok', false);
  14197. //QV('p5fileCatchAllInput', false);
  14198. // Check if these are files we can upload, remove all folders.
  14199. if (e.dataTransfer == null) return;
  14200. var files = [];
  14201. for (var i in e.dataTransfer.files) { if ((e.dataTransfer.files[i].size != null) && (e.dataTransfer.files[i].size != 0)) { files.push(e.dataTransfer.files[i]); } }
  14202. if (files.length == 0) return;
  14203. // Check if these files are duplicates of existing files.
  14204. var filetreex = filetree, allfiles = [], overWriteCount = 0;
  14205. for (var i in filetreelocation) {
  14206. if ((filetreex.f != null) && (filetreex.f[filetreelocation[i]] != null)) { filetreex = filetreex.f[filetreelocation[i]]; }
  14207. }
  14208. if (filetreex.f != null) {
  14209. for (var i in filetreex.f) { allfiles.push(i); }
  14210. for (var i = 0; i < e.dataTransfer.files.length; i++) {
  14211. if (allfiles.indexOf(e.dataTransfer.files[i].name) >= 0) { overWriteCount++; } // TODO: If the server is Windows, we need to lowercase both names.
  14212. }
  14213. }
  14214. if (overWriteCount == 0) {
  14215. // If no overwrite, go ahead with upload
  14216. p5PerformUpload(1, e.dataTransfer.files);
  14217. } else {
  14218. // Otherwise, prompt for confirmation
  14219. setDialogMode(2, "Upload File", 3, p5PerformUpload, format((overWriteCount == 1)?"Upload will overwrite 1 file. Continue?":"Upload will overwrite {0} files. Continue?", overWriteCount), e.dataTransfer.files);
  14220. }
  14221. }
  14222. function p5PerformUpload(b, files) {
  14223. // For Chrome & Firefox
  14224. var error = 0;
  14225. p5uploadFile(); // Display the the dialog box
  14226. try { Q('p5uploadinput').files = files; } catch (ex) { error = 1; } // Set the files in the dialog box
  14227. if (error == 0) { p5uploadFileEx(); } // Press the submit button
  14228. setDialogMode(0); // Close the dialog box
  14229. // For IE browser - This will not work with very large files
  14230. if (error == 1) {
  14231. if (filetreelocation.length == 0) return;
  14232. var names = [], sizes = [], types = [], datas = [], readercount = files.length, totalSize = 0;
  14233. for (var i = 0; i < files.length; i++) { totalSize += files[i].size; }
  14234. if (totalSize > 1300000) { p5uploadFile(); return; } // File is too large, not sure what the real maximum is.
  14235. for (var i = 0; i < files.length; i++) {
  14236. var reader = new FileReader(), file = files[i];
  14237. names.push(file.name);
  14238. sizes.push(file.size);
  14239. types.push(file.type);
  14240. reader.onload = function (event) {
  14241. datas.push(event.target.result);
  14242. if (--readercount == 0) {
  14243. Q('p5fileDragName').value = names.join('*');
  14244. Q('p5fileDragSize').value = sizes.join('*');
  14245. Q('p5fileDragType').value = types.join('*');
  14246. Q('p5fileDragData').value = datas.join('*'); // This will not work for large files, there is a limit on the data size in a field.
  14247. Q('p5fileDragLink').value = encodeURIComponentEx(filetreelinkpath);
  14248. Q('p5fileDragAuthCookie').value = authCookie;
  14249. Q('p5loginSubmit2').click();
  14250. }
  14251. }
  14252. reader.readAsDataURL(file);
  14253. }
  14254. }
  14255. }
  14256. var p5dragtimer = null;
  14257. function p5fileDragOver(e) {
  14258. if (xxdialogMode) return;
  14259. haltEvent(e);
  14260. if (p5dragtimer != null) { clearTimeout(p5dragtimer); p5dragtimer = null; }
  14261. var ac = true; // TODO: Set to true if we can accept the file
  14262. if (filetreelocation.length == 0) { ac = false; }
  14263. QV('bigok', ac);
  14264. QV('bigfail', !ac);
  14265. //QV('p5fileCatchAllInput', ac);
  14266. }
  14267. function p5fileDragLeave(e) {
  14268. if (xxdialogMode) return;
  14269. haltEvent(e);
  14270. if (e.target.id != 'p5filetable') {
  14271. QV('bigfail', false);
  14272. QV('bigok', false);
  14273. //QV('p5fileCatchAllInput', false);
  14274. } else {
  14275. p5dragtimer = setTimeout(function () { QV('bigfail',false); QV('bigok',false); p5dragtimer=null; }, 10);
  14276. }
  14277. }
  14278. function p5downloadButton() {
  14279. var checkboxes = document.getElementsByName('fc');
  14280. for (var i = 0; i < checkboxes.length; i++) {
  14281. if (checkboxes[i].checked) {
  14282. var link = 'downloadfile.ashx?link=' + encodeURIComponentEx(filetreelinkpath + '/' + checkboxes[i].value);
  14283. downloadFile(link);
  14284. }
  14285. }
  14286. }
  14287. /*
  14288. function p5fileCatchAllInputChanged(e) {
  14289. p5fileDragLeave(e);
  14290. Q('p5fileDragLink2').value = encodeURIComponentEx(filetreelinkpath);
  14291. Q('p5fileCatchAllSubmit').click();
  14292. }
  14293. */
  14294. //
  14295. // MY EVENTS
  14296. //
  14297. // Events
  14298. var eventsMessageId = {
  14299. 1: "Account login",
  14300. 2: "Account logout",
  14301. 3: "Changed language from {1} to {2}",
  14302. 4: "Joined desktop multiplex session", // No longer in use, replaced with 143
  14303. 5: "Left the desktop multiplex session", // No longer in use, replaced with 144
  14304. 6: "Started desktop multiplex session", // No longer in use, replaced with 145
  14305. 7: "Finished recording session, {0} second(s)", // No longer in use, replaced with 146
  14306. 8: "Closed desktop multiplex session, {0} second(s)", // No longer in use, replaced with 147
  14307. 9: "Ended relay session \"{0}\" from {1} to {2}, {3} second(s)",
  14308. 10: "Ended terminal session \"{0}\" from {1} to {2}, {3} second(s)",
  14309. 11: "Ended desktop session \"{0}\" from {1} to {2}, {3} second(s)",
  14310. 12: "Ended file management session \"{0}\" from {1} to {2}, {3} second(s)",
  14311. 13: "Started relay session \"{0}\" from {1} to {2}",
  14312. 14: "Started terminal session \"{0}\" from {1} to {2}",
  14313. 15: "Started desktop session \"{0}\" from {1} to {2}",
  14314. 16: "Started file management session \"{0}\" from {1} to {2}",
  14315. 17: "Processing console command: \"{0}\"",
  14316. 18: "Displaying message box, title=\"{0}\", message=\"{1}\"",
  14317. 19: "Killing process {0} ({1})",
  14318. 20: "Opening: {0}",
  14319. 21: "Getting clipboard content, {0} byte(s)",
  14320. 22: "Setting clipboard content, {0} byte(s)",
  14321. 23: "Attempting Intel(R) AMT ACM mode activation",
  14322. 24: "Running commands",
  14323. 25: "Performing power action={0}, forced={1}",
  14324. 26: "Displaying toast message, title=\"{0}\", message=\"{1}\"",
  14325. 27: "Local user accepted remote terminal request",
  14326. 28: "Local user rejected remote terminal request",
  14327. 29: "Remote Desktop Connection forcefully closed by local user",
  14328. 30: "Starting remote desktop after local user accepted",
  14329. 31: "Remote Desktop Connection Bar Activated/Updated",
  14330. 32: "Remote Desktop Connection Bar Failed or Not Supported",
  14331. 33: "Remote Desktop Connection forcefully closed by local user", // No longer used, 29 must be used instead.
  14332. 34: "Failed to start remote desktop after local user rejected",
  14333. 35: "Started remote desktop with toast notification",
  14334. 36: "Started remote desktop without notification",
  14335. 37: "Remote Desktop Connection Bar Activated/Updated", // No longer used, 31 must be used instead.
  14336. 38: "Remote Desktop Connection Bar Failed or not Supported", // No longer used, 33 must be used instead.
  14337. 39: "Remote Desktop Connection forcefully closed by local user", // No longer used, 29 must be used instead.
  14338. 40: "Starting remote files after local user accepted",
  14339. 41: "Failed to start remote files after local user rejected",
  14340. 42: "Started remote files with toast notification",
  14341. 43: "Started remote files without notification",
  14342. 44: "Create folder: \"{0}\"",
  14343. 45: "Delete: \"{0}\"",
  14344. 46: "Delete recursive: \"{0}\", {1} element(s) removed",
  14345. 47: "Delete: \"{0}\", {1} element(s) removed",
  14346. 48: "Rename: \"{0}\" to \"{1}\"",
  14347. 49: "Download: \"{0}\"",
  14348. 50: "Upload: \"{0}\"",
  14349. 51: "Copy: \"{0}\" to \"{1}\"",
  14350. 52: "Move: \"{0}\" to \"{1}\"",
  14351. 53: "Locking remote user out of desktop",
  14352. 54: "Agent closed session with {0}% agent to server compression. Sent: {1}, Compressed: {2}",
  14353. 55: "Created device group: {0}",
  14354. 56: "Device group undeleted: {0}",
  14355. 57: "Added device {0} to device group {1}",
  14356. 58: "Device requested Intel(R) AMT ACM activation, FQDN: {0}",
  14357. 59: "Changed device {0} from group {1}: {2}",
  14358. 60: "Removed user device rights for {0}",
  14359. 61: "Changed user device rights for {0}",
  14360. 62: "Removed user {0} from user group {1}",
  14361. 63: "Account removed",
  14362. 64: "Account created, username is {0}",
  14363. 65: "Account created, email is {0}",
  14364. 66: "Account changed: {0}",
  14365. 67: "User group membership changed: {0}",
  14366. 68: "Added user group {0} to device group {1}",
  14367. 69: "User group created: {0}",
  14368. 70: "Removed user group {0} from device group {1}",
  14369. 71: "Added user {0} to user group {1}",
  14370. 72: "Removed user {0} from user group {1}",
  14371. 73: "Device group notification changed",
  14372. 74: "Account password changed: {0}",
  14373. 75: "Changed account credentials",
  14374. 76: "Device group created: {0}",
  14375. 77: "Device group deleted: {0}",
  14376. 78: "Device group membership changed: {0}",
  14377. 79: "User group changed: {0}",
  14378. 80: "Added user {0} to user group {1}",
  14379. 81: "Removed user device rights for {0}",
  14380. 82: "Changed user device rights for {0}",
  14381. 83: "Removed user {0} from device group {1}",
  14382. 84: "Added device {0} to device group {1}",
  14383. 85: "Moved device {0} to group {1}",
  14384. 86: "Removed user device rights for {0}",
  14385. 87: "Removed device {0} from device group {1}",
  14386. 88: "Enabled email two-factor authentication",
  14387. 89: "Disabled email two-factor authentication",
  14388. 90: "Added authentication application",
  14389. 91: "Removed authentication application",
  14390. 92: "New 2FA backup codes generated",
  14391. 93: "2FA backup codes cleared",
  14392. 94: "Removed security key",
  14393. 95: "Added security key",
  14394. 96: "Verified phone number of user {0}",
  14395. 97: "Removed phone number of user {0}",
  14396. 98: "Help Requested, user: {0}, details: {1}",
  14397. 99: "Running commands as user",
  14398. 100: "Running commands as user if possible",
  14399. 101: "Added device share {0} from {1} to {2}",
  14400. 102: "Removed device share {0}",
  14401. 103: "Batch upload of {0} file(s) to folder {1}",
  14402. 104: "Automated download of agent core dump file: \"{0}\"",
  14403. 105: "Upload: \"{0}\", Size: {1}",
  14404. 106: "Download: \"{0}\", Size: {1}",
  14405. 107: "Account login from {0}, {1}, {2}",
  14406. 108: "User login attempt with incorrect 2nd factor from {0}, {1}, {2}",
  14407. 109: "User login attempt on locked account from {0}, {1}, {2}",
  14408. 110: "Invalid user login attempt from {0}, {1}, {2}",
  14409. 111: "Device requested Intel(R) AMT ACM TLS activation, FQDN: {0}",
  14410. 112: "Ended messenger session \"{0}\" from {1} to {2}, {3} second(s)",
  14411. 113: "Added push notification authentication device",
  14412. 114: "Removed push notification authentication device",
  14413. 115: "Added login token",
  14414. 116: "Removed login token",
  14415. 117: "This is an old agent version, consider updating.",
  14416. 118: "This agent has an outstated certificate validation mechanism, consider updating.",
  14417. 119: "This agent is using insecure tunnels, consider updating.",
  14418. 120: "Started local relay session \"{0}\", protocol {1} to {2}",
  14419. 121: "Ended local relay session \"{0}\", protocol {1} to {2}, {3} second(s)",
  14420. 122: "Left the desktop multiplex session after {0} second(s).", // No longer in use, replaced with 144
  14421. 123: "Left Web-SSH session \"{1}\" after {0} second(s).",
  14422. 124: "Left Web-SFTP session \"{1}\" after {0} second(s).",
  14423. 125: "Left Web-RDP session \"{1}\" after {0} second(s).",
  14424. 126: "Left Web-VNC session after {0} second(s).",
  14425. 127: "Changed account display name to {0}.",
  14426. 128: "Account created, name is {0}.",
  14427. 129: "Removed account display name.",
  14428. 130: "User notifications changed",
  14429. 131: "Added device share {0} with unlimited time.",
  14430. 132: "Turn on.",
  14431. 133: "Turn off.",
  14432. 134: "Forcibly disconnected desktop session of user {0}",
  14433. 135: "Forcibly disconnected terminal session of user {0}",
  14434. 136: "Forcibly disconnected files session of user {0}",
  14435. 137: "Forcibly disconnected routing session of user {0}",
  14436. 138: "Added device share {0} recurring daily.",
  14437. 139: "Added device share {0} recurring weekly.",
  14438. 140: "Changed device {0} from group {1}: {2}",
  14439. 141: "Intel(r) AMT policy change",
  14440. 142: "Device group {0} was changed: {1}",
  14441. 143: "Joined desktop multiplex session \"{0}\"",
  14442. 144: "Left the desktop multiplex session \"{0}\" after {1} second(s).",
  14443. 145: "Started desktop multiplex session \"{0}\"",
  14444. 146: "Finished recording session \"{0}\", {1} second(s)",
  14445. 147: "Closed desktop multiplex session \"{0}\", {1} second(s)",
  14446. 148: "Started Web-SSH session \"{0}\".",
  14447. 149: "Started Web-SFTP session \"{0}\".",
  14448. 150: "Started Web-RDP session \"{0}\".",
  14449. 151: "Started Web-VNC session \"{0}\".", // Not in use yet
  14450. 152: "No longer a relay for \"{0}\".",
  14451. 153: "Is a relay for \"{0}\".",
  14452. 154: "Account changed to sync with LDAP data.",
  14453. 155: "Denied user login from {0}, {1}, {2}",
  14454. 156: "Verified messaging account of user {0}",
  14455. 157: "Removed messaging account of user {0}",
  14456. 158: "Displaying alert box, title=\"{0}\", message=\"{1}\"",
  14457. 159: "Device Powered On",
  14458. 160: "Enabled Duo two-factor authentication",
  14459. 161: "Disabled Duo two-factor authentication",
  14460. 162: "Started messenger session \"{0}\" from {1} to {2}",
  14461. 163: "Removed device {0} from user group {1}",
  14462. 164: "Create file: \"{0}\""
  14463. };
  14464. var eventsShortMessageId = {
  14465. 1: "Group name",
  14466. 2: "Description",
  14467. 3: "Flags",
  14468. 4: "Consent",
  14469. 5: "Auto-Remove",
  14470. 6: "Invite code",
  14471. 7: "Relay device"
  14472. }
  14473. // Highlights the device being hovered
  14474. function eventMouseHover(e, over) {
  14475. e.children[1].classList.remove('g1s');
  14476. e.children[2].classList.remove('style10s');
  14477. //e.children[2].style['background-color'] = ((over == 0) ? '#c9c9c9' : '#b9b9b9');
  14478. e.children[3].classList.remove('g2s');
  14479. if (over == 1) { e.children[1].classList.add('g1s'); e.children[2].classList.add('style10s'); e.children[3].classList.add('g2s'); }
  14480. }
  14481. function eventsUpdate() {
  14482. var x = '', dateHeader = null;
  14483. for (var i in events) {
  14484. var event = events[i], time = new Date(event.time);
  14485. if (event.msg) {
  14486. if (event.h == null) { event.h = Math.random(); }
  14487. if (printDate(time) != dateHeader) {
  14488. if (dateHeader != null) x += '</table>';
  14489. dateHeader = printDate(time);
  14490. x += '<table class=p3eventsTable cellpadding=0 cellspacing=0><tr><td colspan=4 class=DevSt>' + dateHeader + '</td></tr>';
  14491. }
  14492. var icon = 'si3';
  14493. if (event.etype == 'ugrp') icon = 'm4';
  14494. if (event.etype == 'user') icon = 'm2';
  14495. if (event.etype == 'server') icon = 'si3';
  14496. var msg;
  14497. if ((event.msgid == null) || (eventsMessageId[event.msgid] == null)) {
  14498. if (typeof event.msg == 'string') { msg = EscapeHtml(event.msg).split('(R)').join('&reg;'); } else { msg = ''; }
  14499. } else {
  14500. msg = eventsMessageId[event.msgid];
  14501. if (event.msgArgs != null) {
  14502. for (var i in event.msgArgs) {
  14503. var xx = event.msgArgs[i];
  14504. if (Array.isArray(xx)) {
  14505. var x2 = [];
  14506. for (var j in xx) { if ((typeof xx[j] == 'number') && (eventsShortMessageId[xx[j]])) { x2.push(eventsShortMessageId[xx[j]]); } else { x2.push(xx[j]); } }
  14507. xx = x2.join(", ");
  14508. } else {
  14509. if ((typeof xx == 'string') && (xx.indexOf('DATETIME:') == 0)) { xx = printFlexDateTime(new Date(parseInt(xx.substring(9)))); }
  14510. }
  14511. msg = msg.split('{' + i + '}').join(xx);
  14512. }
  14513. }
  14514. msg = EscapeHtml(msg).split('(R)').join('&reg;');
  14515. }
  14516. if (event.nodeid) {
  14517. var node = getNodeFromId(event.nodeid);
  14518. if (node != null) {
  14519. icon = 'si' + node.icon;
  14520. msg = '<a href=# onclick=\'gotoDevice("' + event.nodeid + '",10);haltEvent(event);\'>' + EscapeHtml(node.name) + '</a> &rarr; ' + msg;
  14521. }
  14522. }
  14523. if (event.username) {
  14524. var guestname = '';
  14525. if (event.guestname) { guestname = ' / <span title="' + "This is a guest sharing session" + '">' + EscapeHtml(event.guestname) + '</span>'; }
  14526. if ((userinfo.siteadmin & 2) && (event.userid)) {
  14527. msg = '<a href=# onclick=\'gotoUser("' + encodeURIComponentEx(event.userid) + '");haltEvent(event);\'>' + EscapeHtml(event.username) + '</a>' + guestname + ' &rarr; ' + msg;
  14528. } else {
  14529. msg = EscapeHtml(event.username) + guestname + ' &rarr; ' + msg;
  14530. }
  14531. }
  14532. if (event.remoteaddr) { msg += ' (' + event.remoteaddr + ')'; }
  14533. if (event.etype == 'relay' || event.action == 'relaylog') icon = 'relayIcon16';
  14534. x += '<tr onclick=showEventDetails(' + event.h + ',2) onmouseover=eventMouseHover(this,1) onmouseout=eventMouseHover(this,0) style=cursor:pointer><td style=width:18px><div class=' + icon + '></div></td><td class=g1>&nbsp;</td><td class=style10>' + printTime(time) + ' - ' + msg + '</td><td class=g2>&nbsp;</td></tr><tr style=height:2px></tr>';
  14535. }
  14536. }
  14537. if (dateHeader != null) x += '</table>';
  14538. if (x == '') x = '<br><i>' + "No Events Found" + '</i><br><br>';
  14539. QH('p3events', x);
  14540. }
  14541. function refreshEvents() {
  14542. if (p3filterevents.value != "") {
  14543. meshserver.send({ action: 'events', limit: parseInt(p3limitdropdown.value), filter: p3filterevents.value });
  14544. } else {
  14545. meshserver.send({ action: 'events', limit: parseInt(p3limitdropdown.value) });
  14546. }
  14547. }
  14548. function p3showReportDialog() {
  14549. if (xxdialogMode) return;
  14550. var x = '<div style=float:right><img src=\'images/report60.png\' height=74 width=60 style=padding-top:2px /></div><div>';
  14551. x += addHtmlValue("Report Type", '<select id=d2reportType style=width:160px onchange=p3showReportValidation()><option value=1>' + "All Events" + '</option>');
  14552. x += addHtmlValue("Time Span", '<select id=d2reportSpan style=width:160px onchange=p3showReportValidation()><option value=1>' + "All Available" + '</option><option value=2>' + "One Day" + '</option></select>');
  14553. x += '<div id=d2daySelector>' + addHtmlValue("Report Day", '<input type=date id=d2reportDay style=width:160px onchange=p3showReportValidation()></input>') + '</div>';
  14554. x += addHtmlValue("Group By", '<select id=d2reportGroupBy style=width:160px onchange=p3showReportValidation()><option value=1>' + "None" + '</option><option value=2>' + "Device Group" + '</option><option value=3>' + "User" + '</option>');
  14555. x += addHtmlValue("Format", '<select id=d2reportFormat style=width:160px onchange=p3showReportValidation()><option value=1>' + "CSV" + '</option><option value=2>' + "JSON" + '</option>');
  14556. x += '</div>';
  14557. setDialogMode(2, "Download Report", 3, null, x);
  14558. p3showReportValidation();
  14559. Q('d2reportType').focus();
  14560. }
  14561. function p3showReportValidation() {
  14562. var reportType = Q('d2reportType').value;
  14563. var reportSpan = Q('d2reportSpan').value;
  14564. var reportDay = Q('d2reportDay').value;
  14565. var reportGroupBy = Q('d2reportGroupBy').value;
  14566. var reportFormat = Q('d2reportFormat').value;
  14567. QV('d2daySelector', reportSpan == 2);
  14568. //console.log(reportType, reportSpan, reportDay, reportGroupBy, reportFormat);
  14569. var ok = true;
  14570. if ((reportSpan == 2) && (reportDay == '')) ok = false;
  14571. QE('idx_dlgOkButton', ok);
  14572. }
  14573. function p3showDownloadEventsDialog(mode) {
  14574. if (xxdialogMode) return;
  14575. var x = "Download the list of events with one of the file formats below." + '<br /><br />';
  14576. x += addHtmlValue("CSV Format", '<a href=# style=cursor:pointer onclick="return p3downloadEventsDialogCSV(' + mode + ')">' + "eventslist.csv" + '</a>');
  14577. x += addHtmlValue("JSON Format", '<a href=# style=cursor:pointer onclick="return p3downloadEventsDialogJSON(' + mode + ')">' + "eventslist.json" + '</a>');
  14578. setDialogMode(2, "Event List Export", 1, null, x, mode);
  14579. }
  14580. function p3downloadEventsDialogCSV(mode) {
  14581. var csv, eventList;
  14582. if (mode == 1) { eventList = currentDeviceEvents; }
  14583. if (mode == 2) { eventList = events; }
  14584. if (mode == 3) { eventList = currentUserEvents; }
  14585. csv = "utc, time, type, action, user, device, message" + '\r\n';
  14586. for (var i in eventList) {
  14587. var nodename = '';
  14588. if (eventList[i].nodeid) { var node = getNodeFromId(eventList[i].nodeid); if (node && node.name) { nodename = node.name; } }
  14589. csv += '"' + eventList[i].time + '","' + printDateTime(new Date(eventList[i].time)) + '","' + eventList[i].etype + '","' + ((eventList[i].action != null) ? eventList[i].action : '') + '","' + ((eventList[i].username != null) ? eventList[i].username : '') + '","' + EscapeHtml(nodename) + '","' + ((eventList[i].msg != null) ? eventList[i].msg : '').split(',').join(' -') + '"\r\n';
  14590. }
  14591. saveAs(stringToUtf8Blob(csv), "eventslist.csv");
  14592. return false;
  14593. }
  14594. function p3downloadEventsDialogJSON(mode) {
  14595. var r = [], eventList;
  14596. if (mode == 1) { eventList = currentDeviceEvents; }
  14597. if (mode == 2) { eventList = events; }
  14598. if (mode == 3) { eventList = currentUserEvents; }
  14599. for (var i in eventList) { r.push(events[i]); }
  14600. saveAs(new Blob([JSON.stringify(r, null, 2)], { type: 'application/octet-stream' }), "eventslist.json");
  14601. return false;
  14602. }
  14603. //
  14604. // MY USERS
  14605. //
  14606. function updateUsers() {
  14607. QV('UserNewAccountButton', ((features & 4) == 0) && (serverinfo.domainauth == false));
  14608. if ((users == null) || ((features & 4) != 0)) { QH('p3users', ''); return; }
  14609. // Get the number of users we can display
  14610. var displayUsersLimit = ((userViewSettings != null) && (userViewSettings.noViewLimit)) ? 100000 : 100;
  14611. // Sort the list of user id's
  14612. var sortedUsers = [], maxUsers = displayUsersLimit, hiddenUsers = 0;
  14613. for (var i in users) { sortedUsers.push(users[i]); }
  14614. sortedUsers.sort(nameSort);
  14615. // Get search
  14616. var userSearch = Q('UserSearchInput').value.toLowerCase();
  14617. var emailSearch = userSearch;
  14618. if (userSearch.startsWith('email:')) { userSearch = null; emailSearch = emailSearch.substring(6); }
  14619. else if (userSearch.startsWith('name:')) { emailSearch = null; userSearch = userSearch.substring(5); }
  14620. else if (userSearch.startsWith('e:')) { userSearch = null; emailSearch = emailSearch.substring(2); }
  14621. else if (userSearch.startsWith('n:')) { emailSearch = null; userSearch = userSearch.substring(2); }
  14622. // Display the users using the sorted list
  14623. var x = '<table class=p3usersTable cellpadding=0 cellspacing=0>', addHeader = true;
  14624. x += '<th>' + "Name" + '<th style=width:80px>' + "Device Groups" + '<th style=width:120px>' + nobreak("Last Access") + '<th style=width:120px>' + "Permissions";
  14625. // Save the list of currently checked users
  14626. var checkedUserids = [], elements = document.getElementsByClassName('UserCheckbox');
  14627. for (var i=0;i<elements.length;i++) { if (elements[i].checked) { checkedUserids.push(elements[i].value); } }
  14628. // Online users
  14629. for (var i in sortedUsers) {
  14630. var user = sortedUsers[i], sessions = null;
  14631. if (wssessions != null) { sessions = wssessions[user._id]; }
  14632. if ((sessions != null) &&
  14633. ((userSearch != null) && ((userSearch == '') || (user.name.toLowerCase().indexOf(userSearch) >= 0)) ||
  14634. ((emailSearch != null) && ((user.email != null) && (user.email.toLowerCase().indexOf(emailSearch) >= 0))))
  14635. ) {
  14636. if (maxUsers > 0) {
  14637. if (addHeader) { x += '<tr><td class=userTableHeader colspan=4>' + "Online Users"; addHeader = false; }
  14638. x += addUserHtml(user, sessions);
  14639. maxUsers--;
  14640. } else {
  14641. hiddenUsers++;
  14642. }
  14643. }
  14644. }
  14645. addHeader = true;
  14646. // Offline users
  14647. for (var i in sortedUsers) {
  14648. var user = sortedUsers[i], sessions = null;
  14649. if (wssessions != null) { sessions = wssessions[user._id]; }
  14650. if ((sessions == null) &&
  14651. ((userSearch != null) && ((userSearch == '') || (user.name.toLowerCase().indexOf(userSearch) >= 0)) ||
  14652. ((emailSearch != null) && ((user.email != null) && (user.email.toLowerCase().indexOf(emailSearch) >= 0))))
  14653. ) {
  14654. if (maxUsers > 0) {
  14655. if (addHeader) { x += '<tr><td class=userTableHeader colspan=4>' + "Offline Users"; addHeader = false; }
  14656. x += addUserHtml(user, sessions);
  14657. maxUsers--;
  14658. } else {
  14659. hiddenUsers++;
  14660. }
  14661. }
  14662. }
  14663. x += '</table>';
  14664. if (hiddenUsers == 1) { x += '<br />' + "1 more user not shown, use search box to look for users..." + '<br />'; }
  14665. else if (hiddenUsers > 1) { x += '<br />' + format("{0} more users not shown, use search box to look for users...", hiddenUsers) + '<br />'; }
  14666. if (maxUsers == displayUsersLimit) { x += '<br />' + "No users found." + '<br />'; }
  14667. QH('p3users', x);
  14668. // Re-check userid's
  14669. elements = document.getElementsByClassName('UserCheckbox');
  14670. var eself = encodeURIComponentEx(userinfo._id);
  14671. for (var i=0;i<elements.length;i++) { elements[i].checked = ((checkedUserids.indexOf(elements[i].value) >= 0) && (elements[i].value != eself)); }
  14672. p3updateInfo();
  14673. // Update current user panel if needed
  14674. if ((currentUser != null) && (xxcurrentView == 30)) { gotoUser(encodeURIComponentEx(currentUser._id),true); }
  14675. }
  14676. function addUserHtml(user, sessions) {
  14677. var x = '', gray = ' gray', icon = 'm2', msg = '', self = (user._id != userinfo._id), lastAccess = '', permissions = '';
  14678. if (sessions != null) {
  14679. gray = '';
  14680. if (self) {
  14681. msg = '<span style=float:right;margin-top:1px;margin-right:4px title=' + "Chat" + '><a href=# onclick=userChat(event,"' + encodeURIComponentEx(user._id) + '","' + encodeURIComponentEx(user.name) + '")><img src=\'images/icon-chat.png\' height=16 width=16 style=padding-top:2px /></a></span>';
  14682. msg += '<span style=float:right;margin-top:1px;margin-left:4px;margin-right:4px title=Notify><a href=# onclick=\'return showUserAlertDialog(event,"' + encodeURIComponentEx(user._id) + '")\'><img src=\'images/icon-notify.png\' height=16 width=16 style=padding-top:2px /></a></span>';
  14683. }
  14684. if (sessions == 1) { lastAccess += nobreak("1 session"); } else { lastAccess += nobreak(format("{0} sessions", sessions)); }
  14685. } else {
  14686. if (user.access) { lastAccess += '<span title="' + format("Last access: {0}", printDateTime(new Date(user.access * 1000))) + '">' + printDate(new Date(user.access * 1000)) + '</span>'; }
  14687. else if (user.login) { lastAccess += '<span title="' + format("Last login: {0}", printDateTime(new Date(user.login * 1000))) + '">' + printDate(new Date(user.login * 1000)) + '</span>'; }
  14688. }
  14689. if (self) { permissions += '<a href=# style=cursor:pointer onclick=\'return showUserAdminDialog(event,"' + encodeURIComponentEx(user._id) + '")\'>'; }
  14690. if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { permissions += "Locked" + ',&nbsp;'; }
  14691. permissions += '<span title=\'' + "Server Permissions" + '\'>';
  14692. var urights = user.siteadmin & (0xFFFFFFFF - 1248);
  14693. if ((user.siteadmin == null) || (urights == 0)) {
  14694. permissions += "User";
  14695. } else if (urights == 8) {
  14696. permissions += "User + Files";
  14697. } else if (user.siteadmin == 0xFFFFFFFF) {
  14698. permissions += "Administrator";
  14699. } else if ((urights & 2) != 0) {
  14700. permissions += "Manager";
  14701. } else {
  14702. permissions += "Partial";
  14703. }
  14704. if ((user.siteadmin != null) && (user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & (64 + 128 + 1024)) != 0)) { permissions += '*'; }
  14705. permissions += '</span>';
  14706. //if ((user.quota != null) && ((user.siteadmin & 8) != 0)) { msg += ", " + (user.quota / 1024) + " k"; }
  14707. if (self) { permissions += '</a>'; }
  14708. var groups = 0
  14709. if (user.links) { for (var i in user.links) { if (i.startsWith('mesh/')) { groups++; } } }
  14710. var username = EscapeHtml(user.name), emailVerified = '';
  14711. if (serverinfo.emailcheck == true) { emailVerified = ((user.emailVerified != true) ? ' <b style=color:red title="' + "Email is not verified" + '">&#x2717;</b>' : ' <b style=color:green title="' + "Email is verified" + '">&#x2713</b>'); }
  14712. if (user.email != null) {
  14713. if (((features & 0x200000) == 0) || (user.email.toLowerCase() != user.name.toLowerCase())) {
  14714. // Username & email are different
  14715. username += ', <a href="mailto:' + EscapeHtml(user.email) + '" \'>' + EscapeHtml(user.email) + '</a>' + emailVerified;
  14716. } else {
  14717. // Username & email are the same
  14718. username += ' <a href="mailto:' + EscapeHtml(user.email) + '" \'><img src="images/mail12.png" height=9 width=12 title="' + "Send email to user" + '" style="margin-top:2px" /></a>' + emailVerified;
  14719. }
  14720. }
  14721. // If we are a cross-domain administrator, add the domain.
  14722. if ((serverinfo.crossDomain != null)) {
  14723. var userdomain = user._id.split('/')[1];
  14724. if (userdomain != '') { username += ', <span style=color:#26F>' + userdomain + '</span>'; }
  14725. }
  14726. if ((user.otpsecret > 0) || (user.otphkeys > 0) || ((user.otpekey == 1) && (features & 0x00800000)) || (user.otpduo == 1) || ((user.phone != null) && (features & 0x04000000))) { username += ' <img src="images/key12.png" height=12 width=11 title="' + "2nd factor authentication enabled" + '" style="margin-top:2px" />'; }
  14727. if (user.phone != null) { username += ' <img src="images/phone12.png" height=12 width=7 title="' + "Verified phone number" + '" style="margin-top:2px" />'; }
  14728. if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { username += ' <img src="images/padlock12.png" height=12 width=8 title="' + "Account is locked" + '" style="margin-top:2px" />'; }
  14729. if ((user.msghandle != null) && (features2 & 0x02000000)) { username += ' <img src="images/messaging12.png" height=12 width=12 title="' + "Verified messaging account" + '" style="margin-top:2px" />'; }
  14730. x += '<tr tabindex=0 onmouseover=userMouseHover(this,1) onmouseout=userMouseHover(this,0) onkeypress="if (event.key==\'Enter\') gotoUser(\'' + encodeURIComponentEx(user._id) + '\')"><td>';
  14731. x += '<div class=bar>';
  14732. x += '<div class=baricon><input class=UserCheckbox value=' + encodeURIComponentEx(user._id) + ' onclick=p3updateInfo() type=checkbox' + ((user._id == userinfo._id)?' disabled':'') + '></div><div style=cursor:pointer onclick=gotoUser("' + encodeURIComponentEx(user._id) + '",false,event)>';
  14733. x += '<div class=baricon><div class="' + icon + gray + '"></div></div>';
  14734. x += '<div class=g1></div><div class=g2></div><div>';
  14735. x += '<div><span style=line-height:24px>' + username + '</span>' + msg + '</div></div><td style=text-align:center>' + groups + '<td style=text-align:center>' + lastAccess + '<td style=text-align:center>' + permissions;
  14736. return x;
  14737. }
  14738. // Called when a user checkbox is clicked
  14739. function p3updateInfo() {
  14740. var elements = document.getElementsByClassName('UserCheckbox'), checkcount = 0;
  14741. for (var i=0;i<elements.length;i++) { if (elements[i].checked === true) checkcount++; }
  14742. QE('UsersGroupActionButton', checkcount > 0);
  14743. Q('UsersSelectAllButton').value = (checkcount > 0)?"Select None":"Select All";
  14744. }
  14745. // Called to select all or unselect all users
  14746. function p3usersSelectallButtonFunction() {
  14747. var eself = encodeURIComponentEx(userinfo._id);
  14748. var elements = document.getElementsByClassName('UserCheckbox'), checkcount = 0;
  14749. for (var i=0;i<elements.length;i++) { if (elements[i].checked === true) checkcount++; }
  14750. for (var i=0;i<elements.length;i++) { elements[i].checked = (checkcount == 0) && (elements[i].value != eself); }
  14751. p3updateInfo();
  14752. }
  14753. // Called to perform a group action on many users
  14754. function p3usersGroupActionFunction() {
  14755. var elements = document.getElementsByClassName('UserCheckbox'), checkcount = 0;
  14756. for (var i=0;i<elements.length;i++) { if (elements[i].checked === true) checkcount++; }
  14757. if (checkcount == 0) return;
  14758. var optionalActions = '';
  14759. if (serverinfo.emailcheck) { optionalActions += '<option value=4>' + "Validate Email" + '</option><option value=5>' + "Invalidate Email" + '</option>'; }
  14760. var x = "Select an operation to perform on all selected users." + '<br /><br />';
  14761. x += addHtmlValue("Operation", '<select style=width:240px id=d3groupop><option value=1>' + "Lock account" + '</option><option value=2>' + "Unlock account" + '</option>' + optionalActions + '<option value=3>' + "Delete account" + '</option></select>');
  14762. setDialogMode(2, "Group Action", 3, p3usersGroupActionFunctionEx, x);
  14763. }
  14764. function p3usersGroupActionFunctionEx() {
  14765. var elements = document.getElementsByClassName('UserCheckbox'), userids = [];
  14766. for (var i=0;i<elements.length;i++) { if (elements[i].checked === true) { userids.push(decodeURIComponent(elements[i].value)); } }
  14767. var op = Q('d3groupop').value;
  14768. if (op == 1) {
  14769. // Lock accounts
  14770. for (var i in userids) {
  14771. var user = users[userids[i]], siteadmin = (user.siteadmin == null)?0:user.siteadmin;
  14772. if ((siteadmin & 32) == 0) { siteadmin += 32; meshserver.send({ action: 'edituser', id: user._id, siteadmin: siteadmin }); }
  14773. }
  14774. } else if (op == 2) {
  14775. // Unlock accounts
  14776. for (var i in userids) {
  14777. var user = users[userids[i]], siteadmin = (user.siteadmin == null)?0:user.siteadmin;
  14778. if ((siteadmin & 32) != 0) { siteadmin -= 32; meshserver.send({ action: 'edituser', id: user._id, siteadmin: siteadmin }); }
  14779. }
  14780. } else if (op == 3) {
  14781. // Delete accounts, ask for confirmation
  14782. var x = "Confirm delete selected account(s)?" + '<br /><br />';
  14783. x += '<label><input id=d3check type=checkbox onchange=p3usersGroupActionFunctionDelCheck() />' + "Confirm" + '</label>';
  14784. setDialogMode(2, "Delete Accounts", 3, p3groupActionFunctionDelExec, x);
  14785. QE('idx_dlgOkButton', false);
  14786. } else if (op == 4) {
  14787. // Validate emails
  14788. for (var i in userids) {
  14789. var user = users[userids[i]];
  14790. if (user.emailVerified !== true) { meshserver.send({ action: 'edituser', id: user._id, emailVerified: true }); }
  14791. }
  14792. } else if (op == 5) {
  14793. // Invalidate emails
  14794. for (var i in userids) {
  14795. var user = users[userids[i]];
  14796. if (user.emailVerified === true) { meshserver.send({ action: 'edituser', id: user._id, emailVerified: false }); }
  14797. }
  14798. }
  14799. }
  14800. function p3usersGroupActionFunctionDelCheck() { QE('idx_dlgOkButton', Q('d3check').checked); }
  14801. // Delete a batch of user accounts
  14802. function p3groupActionFunctionDelExec(b) {
  14803. var elements = document.getElementsByClassName('UserCheckbox'), userids = [];
  14804. for (var i=0;i<elements.length;i++) { if (elements[i].checked === true) { userids.push(decodeURIComponent(elements[i].value)); } }
  14805. for (var i in userids) { var user = users[userids[i]]; meshserver.send({ action: 'deleteuser', userid: user._id, username: user.name }); }
  14806. }
  14807. // Highlights the user being hovered
  14808. function userMouseHover(element, over) {
  14809. var e = element.children[0].children[0].children[1];
  14810. e.children[1].classList.remove('g1s');
  14811. e.children[2].classList.remove('g2s');
  14812. element.children[0].children[0].classList.remove('sbar');
  14813. if (over == 1) {
  14814. e.children[1].classList.add('g1s');
  14815. e.children[2].classList.add('g2s');
  14816. element.children[0].children[0].classList.add('sbar');
  14817. }
  14818. }
  14819. // Highlights the user being hovered
  14820. function userMouseHover2(element, over) {
  14821. var e = element.children[0].children[0];
  14822. e.children[2].classList.remove('g1s');
  14823. e.children[3].classList.remove('g2s');
  14824. element.children[0].children[0].classList.remove('sbar');
  14825. if (over == 1) {
  14826. e.children[2].classList.add('g1s');
  14827. e.children[3].classList.add('g2s');
  14828. element.children[0].children[0].classList.add('sbar');
  14829. }
  14830. }
  14831. function userChat(e, userid, name) {
  14832. if (xxdialogMode) return;
  14833. haltEvent(e);
  14834. var url = '/messenger?id=meshmessenger/' + userid + '/' + encodeURIComponentEx(userinfo._id) + '&title=' + name;
  14835. if ((authCookie != null) && (authCookie != '')) { url += '&auth=' + authCookie; }
  14836. if (urlargs.key) { url += '&key=' + urlargs.key; }
  14837. safeNewWindow(url, 'meshmessenger:' + userid);
  14838. meshserver.send({ action: 'meshmessenger', userid: decodeURIComponent(userid) });
  14839. return false;
  14840. }
  14841. function altUserChat(e, userid, name, i) {
  14842. if (xxdialogMode) return;
  14843. haltEvent(e);
  14844. var url = serverinfo.altmessenging[i].url;
  14845. var ruserid = decodeURIComponent(userid);
  14846. var userid1 = encodeURIComponentEx(ruserid.split('/')[2]); // userid
  14847. var userid2 = encodeURIComponentEx(ruserid.split('/').join('-')); // user-domain-userid
  14848. var userid3 = userid1, userid4 = userid2;
  14849. var ruser = users[ruserid];
  14850. if ((ruser != null) && (ruser.realname != null)) {
  14851. userid3 = encodeURIComponentEx(ruser.realname.split(' ').join('')); // real name with no empty spaces
  14852. userid4 = encodeURIComponentEx(ruser.realname.split(' ').join('-')); // real name with - instead of spaces
  14853. }
  14854. url = url.split('{0}').join(userid1).split('{1}').join(userid2).split('{2}').join(userid3).split('{3}').join(userid4);
  14855. if (urlargs.key) { url += '&key=' + urlargs.key; }
  14856. safeNewWindow(url, 'altmessenger:' + userid);
  14857. meshserver.send({ action: 'notifyuser', userid: decodeURIComponent(userid), msg: serverinfo.altmessenging[i].name, msgid: 11, url: url });
  14858. return false;
  14859. }
  14860. function showSendSMS(userid) {
  14861. if (xxdialogMode) return;
  14862. setDialogMode(2, "Send SMS", 3, showSendSMSEx, '<textarea id=d2smsText maxlength=160 style=background-color:#fcf3cf;width:100%;height:100px;resize:none onKeyUp=showSendSMSValidate()></textarea><span style=font-size:10px><span>', userid);
  14863. Q('d2smsText').focus();
  14864. showSendSMSValidate();
  14865. }
  14866. function showSendSMSValidate() { QE('idx_dlgOkButton', Q('d2smsText').value.length > 0); }
  14867. function showSendSMSEx(b, tag) { if (Q('d2smsText').value.length > 0) { meshserver.send({ action: 'smsuser', userid: decodeURIComponent(tag), msg: Q('d2smsText').value }); } }
  14868. function showSendMessage(userid) {
  14869. if (xxdialogMode) return;
  14870. setDialogMode(2, "Send Message", 3, showSendMessageEx, '<textarea id=d2smsText maxlength=160 style=background-color:#fcf3cf;width:100%;height:100px;resize:none onKeyUp=showSendSMSValidate()></textarea><span style=font-size:10px><span>', userid);
  14871. Q('d2smsText').focus();
  14872. showSendSMSValidate();
  14873. }
  14874. function showSendMessageEx(b, tag) { if (Q('d2smsText').value.length > 0) { meshserver.send({ action: 'msguser', userid: decodeURIComponent(tag), msg: Q('d2smsText').value }); } }
  14875. function showSendEmail(userid) {
  14876. if (xxdialogMode) return;
  14877. var x = '<input id=d2emailSubject style=background-color:#fcf3cf;width:100% placeholder="' + "Subject" + '"></input>';
  14878. x += '<textarea id=d2emailText maxlength=10000 style="background-color:#fcf3cf;width:100%;height:200px;resize:none;overflow-y:auto" onKeyUp=showSendEmailValidate()></textarea>';
  14879. setDialogMode(2, "Send Email", 3, showSendEmailEx, x, userid);
  14880. Q('d2emailSubject').focus();
  14881. showSendEmailValidate();
  14882. }
  14883. function showSendEmailValidate() { QE('idx_dlgOkButton', (Q('d2emailSubject').value.length > 0) && (Q('d2emailText').value.length > 0)); }
  14884. function showSendEmailEx(b, tag) { if (Q('d2emailText').value.length > 0) { meshserver.send({ action: 'emailuser', userid: decodeURIComponent(tag), subject: Q('d2emailSubject').value, msg: Q('d2emailText').value }); } }
  14885. function showUserAlertDialog(e, userid) {
  14886. if (xxdialogMode) return;
  14887. haltEvent(e);
  14888. var x = '<div style=margin-bottom:6px>' + "Send a text notification to this user." + '</div><textarea id=d2notifyText maxlength=2048 style="width:100%;height:184px;resize:none;background-color:#fcf3cf"></textarea>';
  14889. x += '<select style=width:100% id=broadcastMessageMaxTime>';
  14890. x += '<option value=0>' + "Show message until dismissed by user" + '</option>';
  14891. x += '<option value=10>' + "Show for 10 seconds" + '</option>';
  14892. x += '<option value=60>' + "Show for 1 minute" + '</option>';
  14893. x += '<option value=300>' + "Show for 5 minutes" + '</option>';
  14894. x += '</select>';
  14895. setDialogMode(2, format("Notify {0}", EscapeHtml(users[decodeURIComponent(userid)].name)), 3, showUserAlertDialogEx, x, userid);
  14896. Q('d2notifyText').focus();
  14897. return false;
  14898. }
  14899. function showUserAlertDialogEx(button, userid) {
  14900. meshserver.send({ action: 'notifyuser', userid: decodeURIComponent(userid), msg: Q('d2notifyText').value, maxtime: parseInt(Q('broadcastMessageMaxTime').value) });
  14901. }
  14902. function onUsersViewSettings() {
  14903. if (xxdialogMode) return;
  14904. // Use defaults if needed
  14905. if (userViewSettings == null) { userViewSettings = {}; }
  14906. // Display the dialog box
  14907. var x = '';
  14908. x += '<label><input id=d2c1 type=checkbox' + (userViewSettings.noViewLimit?'':' checked') + '>' + "Only display first 100 users" + '</label><br />';
  14909. setDialogMode(2, "Users View", 3, onUsersViewSettingsEx, x);
  14910. }
  14911. function onUsersViewSettingsEx() {
  14912. userViewSettings.noViewLimit = !Q('d2c1').checked;
  14913. putstore('_usersViewSettings', JSON.stringify(userViewSettings));
  14914. mainUpdate(16384); // Update users
  14915. }
  14916. function p4batchAccountCreate() {
  14917. if (xxdialogMode) return;
  14918. var x = "Create many accounts at once by importing a JSON or CSV file" + '<br /><br />';
  14919. x += "JSON file format is as follows:" + '<br />';
  14920. x += '<pre>[\r\n {"user":"x1","pass":"x","email":"x1@x"},\r\n {"user":"x2","pass":"x","resetNextLogin":true}\r\n]</pre><br />';
  14921. x += "CSV file format is as follows:" + '<br />';
  14922. x += '<pre>user,pass,email,resetNextLogin\r\nx1,x,x1@x,\r\nx2,x,,true\r\n</pre><br />';
  14923. x += '<input style=width:370px type=file id=d4importFile accept=".json,.csv" onchange=p4batchAccountCreateValidate() />';
  14924. setDialogMode(2, "User Account Import", 3, p4batchAccountCreateEx, x);
  14925. QE('idx_dlgOkButton', false);
  14926. }
  14927. function p4batchAccountCreateValidate() {
  14928. QE('idx_dlgOkButton', Q('d4importFile').value != null);
  14929. }
  14930. function p4batchAccountCreateEx() {
  14931. var fr = new FileReader();
  14932. fr.onload = function (r) {
  14933. var j = null;
  14934. if (Q('d4importFile').value.endsWith('.csv')) {
  14935. const csvData = r.target.result;
  14936. const lines = csvData.split('\n');
  14937. const headers = lines[0].trim().split(',');
  14938. const jsonArray = [];
  14939. for (let i = 1; i < lines.length; i++) {
  14940. const currentLine = lines[i].trim();
  14941. if (currentLine === "") continue; // Skip blank lines
  14942. const lineArray = currentLine.split(',');
  14943. // Check if any non-empty field exists
  14944. let hasNonEmptyField = false;
  14945. for (let j = 0; j < lineArray.length; j++) {
  14946. if (lineArray[j].trim() !== "") {
  14947. hasNonEmptyField = true;
  14948. break;
  14949. }
  14950. }
  14951. // If no non-empty field exists, skip adding this object
  14952. if (!hasNonEmptyField) continue;
  14953. const obj = {};
  14954. for (let j = 0; j < headers.length; j++) {
  14955. // Only include columns with non-empty headers and non-empty values
  14956. const value = lineArray[j].trim();
  14957. if (headers[j].trim() !== "" && value !== "") {
  14958. // Convert values "true" to true
  14959. obj[headers[j]] = value.toLowerCase() === "true" ? true : value;
  14960. }
  14961. }
  14962. jsonArray.push(obj);
  14963. }
  14964. j = jsonArray;
  14965. if ((j != null) && (Array.isArray(j))) {
  14966. var ok = true;
  14967. for (var i in j) {
  14968. if ((typeof j[i].user != 'string') || (j[i].user.length < 1) || (j[i].user.length > 64)) { ok = false; }
  14969. if ((typeof j[i].pass != 'string') || (j[i].pass.length < 1) || (j[i].pass.length > 256)) { ok = false; }
  14970. if (checkPasswordRequirements(j[i].pass, passRequirements) == false) { ok = false; }
  14971. if ((j[i].email != null) && ((typeof j[i].email != 'string') || (j[i].email.length < 1) || (j[i].email.length > 128))) { ok = false; }
  14972. }
  14973. if (ok == false) { setDialogMode(2, "User Account Import", 1, null, "Invalid CSV file format."); } else { meshserver.send({ action: 'adduserbatch', users: j }); }
  14974. } else { setDialogMode(2, "User Account Import", 1, null, "Invalid CSV file format."); }
  14975. } else {
  14976. try { j = JSON.parse(r.target.result); } catch (ex) { setDialogMode(2, "User Account Import", 1, null, format("Invalid JSON file: {0}.", ex)); return; }
  14977. if ((j != null) && (Array.isArray(j))) {
  14978. var ok = true;
  14979. for (var i in j) {
  14980. if ((typeof j[i].user != 'string') || (j[i].user.length < 1) || (j[i].user.length > 64)) { ok = false; }
  14981. if ((typeof j[i].pass != 'string') || (j[i].pass.length < 1) || (j[i].pass.length > 256)) { ok = false; }
  14982. if (checkPasswordRequirements(j[i].pass, passRequirements) == false) { ok = false; }
  14983. if ((j[i].email != null) && ((typeof j[i].email != 'string') || (j[i].email.length < 1) || (j[i].email.length > 128))) { ok = false; }
  14984. }
  14985. if (ok == false) { setDialogMode(2, "User Account Import", 1, null, "Invalid JSON file format."); } else { meshserver.send({ action: 'adduserbatch', users: j }); }
  14986. } else { setDialogMode(2, "User Account Import", 1, null, "Invalid JSON file format."); }
  14987. }
  14988. };
  14989. fr.readAsText(Q('d4importFile').files[0]);
  14990. }
  14991. function p4downloadUserInfo() {
  14992. if (xxdialogMode) return;
  14993. var x = "Download the list of users with one of the file formats below." + '<br /><br />';
  14994. x += addHtmlValue("CSV Format", '<a href=# style=cursor:pointer onclick=\'return p4downloadUserInfoCSV()\'>' + "userlist.csv" + '</a>');
  14995. x += addHtmlValue("JSON Format", '<a href=# style=cursor:pointer onclick=\'return p4downloadUserInfoJSON()\'>' + "userlist.json" + '</a>');
  14996. setDialogMode(2, "User List Export", 1, null, x);
  14997. }
  14998. function p4downloadUserInfoCSV() {
  14999. var csv = "id,name,email,creation,lastlogin,groups,authfactors,siteadmin,useradmin,locked" + '\r\n';
  15000. for (var i in users) {
  15001. var multiFactor = false, factors = [];
  15002. if ((users[i].otpsecret > 0) || (users[i].otphkeys > 0)) {
  15003. multiFactor = true;
  15004. if (users[i].otpsecret > 0) { factors.push('AuthApp'); }
  15005. if (users[i].otphkeys > 0) { factors.push('SecurityKey'); }
  15006. if (users[i].otpkeys > 0) { factors.push('BackupCodes'); }
  15007. }
  15008. csv += '"' + users[i]._id + '","' + users[i].name + '","' + (users[i].email ? users[i].email : '') + '","' + (users[i].creation ? new Date(users[i].creation * 1000) : '') + '","' + (users[i].login ? new Date(users[i].login * 1000) : '') + '","' + (users[i].groups ? users[i].groups.join(',') : '') + '","' + ((multiFactor ? factors.join(',') : '') + '"');
  15009. csv += ',' + ((users[i].siteadmin == 0xFFFFFFFF) ? '1' : '0'); // site admin
  15010. csv += ',' + (((users[i].siteadmin & 2) != 0) ? '1' : '0'); // user admin
  15011. csv += ',' + (((users[i].siteadmin & 32) != 0) ? '1' : '0'); // locked
  15012. csv += '\r\n';
  15013. }
  15014. saveAs(stringToUtf8Blob(csv), "userlist.csv");
  15015. return false;
  15016. }
  15017. function p4downloadUserInfoJSON() {
  15018. var r = []
  15019. for (var i in users) { r.push(users[i]); }
  15020. saveAs(new Blob([JSON.stringify(r, null, 2)], { type: 'application/octet-stream' }), "userlist.json");
  15021. return false;
  15022. }
  15023. function showUserBroadcastDialog(targetid) {
  15024. if (xxdialogMode) return;
  15025. var x = '<div style=margin-bottom:6px>' + "Broadcast a message to all connected users." + '</div><textarea id=broadcastMessage style="width:100%;height:184px;resize:none;background-color:#fcf3cf" value="" maxlength=2048 /></textarea>';
  15026. x += '<select style=width:100% id=broadcastMessageMaxTime>';
  15027. x += '<option value=0>' + "Show message until dismissed by user" + '</option>';
  15028. x += '<option value=10>' + "Show for 10 seconds" + '</option>';
  15029. x += '<option value=60>' + "Show for 1 minute" + '</option>';
  15030. x += '<option value=300>' + "Show for 5 minutes" + '</option>';
  15031. x += '</select>';
  15032. setDialogMode(2, "Broadcast Message", 3, showUserBroadcastDialogEx, x, targetid?decodeURIComponent(targetid):null);
  15033. Q('broadcastMessage').focus();
  15034. }
  15035. function showUserBroadcastDialogEx(b, targetid) {
  15036. meshserver.send({ action: 'userbroadcast', msg: Q('broadcastMessage').value, target: targetid, maxtime: parseInt(Q('broadcastMessageMaxTime').value) });
  15037. }
  15038. function showCreateNewAccountDialog() {
  15039. if (xxdialogMode) return;
  15040. var x = '';
  15041. if (serverinfo.crossDomain) {
  15042. var y = '<select style=width:240px id=p4domain>';
  15043. for (var i in serverinfo.crossDomain) { y += '<option value=' + i + '>' + ((serverinfo.crossDomain[i] == '')?"Default":EscapeHtml(serverinfo.crossDomain[i])) + '</option>'; }
  15044. y += '</select>';
  15045. x += addHtmlValue("Domain", y);
  15046. }
  15047. if ((features & 0x200000) == 0) { x += addHtmlValue('<span id=p4hname>' + "Username" + '</span>', '<input id=p4name maxlength=64 autocomplete=username onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />'); }
  15048. x += addHtmlValue('<span id=p4hemail>' + "Email" + '</span>', '<input id=p4email type=email maxlength=256 autocomplete="email" inputmode="email" onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />');
  15049. x += addHtmlValue('<span id=p4hp1>' + "Password" + '</span>', '<input id=p4pass1 type=password maxlength=256 autocomplete="new-password" onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />');
  15050. x += addHtmlValue('<span id=p4hp2>' + "Password" + '</span>', '<input id=p4pass2 type=password maxlength=256 autocomplete="new-password" onchange=showCreateNewAccountDialogValidate() onkeyup=showCreateNewAccountDialogValidate() />');
  15051. x += '<div><label><input id=p4randomPassword onchange=showCreateNewAccountDialogValidate() type=checkbox />' + "Randomize the password." + '</label></div>';
  15052. x += '<div><label><input id=p4removeEvents onchange=showCreateNewAccountDialogValidate() type=checkbox />' + "Remove all previous events for this userid." + '</label></div>';
  15053. x += '<div><label><input id=p4resetNextLogin onchange=showCreateNewAccountDialogValidate() type=checkbox />' + "Force password reset on next login." + '</label></div>';
  15054. if (serverinfo.emailcheck) {
  15055. x += '<div><label><input id=p4verifiedEmail onchange=showCreateNewAccountDialogValidate() type=checkbox />' + "Email is verified." + '</label></div>';
  15056. x += '<div><label><input id=p4invitationEmail type=checkbox title=' + "Email verified and forced password reset required." + ' />' + "Send invitation email." + '</label></div>';
  15057. }
  15058. if (passRequirements) {
  15059. var r = [], rc = 0;
  15060. for (var i in passRequirements) { if ((i != 'reset') && (i != 'hint')) { r.push(i + ':' + passRequirements[i]); rc++; } }
  15061. if (rc > 0) { x += '<div style=font-size:x-small;padding:6px>' + format("Requirements: {0}.", r.join(', ')) + '</div>'; }
  15062. }
  15063. setDialogMode(2, "Create Account", 3, showCreateNewAccountDialogEx, x);
  15064. showCreateNewAccountDialogValidate();
  15065. if ((features & 0x200000) == 0) { Q('p4name').focus(); } else { Q('p4email').focus(); }
  15066. }
  15067. function showCreateNewAccountDialogValidate() {
  15068. var emailok = validateEmail(Q('p4email').value);
  15069. var nameok = true;
  15070. var passok = (Q('p4pass1').value.length > 0 && Q('p4pass1').value == Q('p4pass2').value && checkPasswordRequirements(Q('p4pass1').value, passRequirements));
  15071. if ((features & 0x200000) == 0) {
  15072. nameok = (!Q('p4name') || ((Q('p4name').value.length > 0) && (Q('p4name').value.indexOf(' ') == -1)));
  15073. QS('p4hname').color = nameok?'black':'#7b241c';
  15074. }
  15075. QS('p4hemail').color = emailok?'black':'#7b241c';
  15076. if (serverinfo.emailcheck) {
  15077. QE('p4verifiedEmail', emailok);
  15078. QE('p4invitationEmail', emailok && Q('p4resetNextLogin').checked && Q('p4verifiedEmail').checked);
  15079. if (emailok == false) { Q('p4verifiedEmail').checked = false; }
  15080. if ((Q('p4resetNextLogin').checked == false) || (Q('p4verifiedEmail').checked == false)) { Q('p4invitationEmail').checked = false; }
  15081. }
  15082. QE('p4pass1', !Q('p4randomPassword').checked);
  15083. QE('p4pass2', !Q('p4randomPassword').checked);
  15084. QS('p4hp1').color = (passok || Q('p4randomPassword').checked)?'black':'#7b241c';
  15085. QS('p4hp2').color = (passok || Q('p4randomPassword').checked)?'black':'#7b241c';
  15086. var ok = nameok & emailok;
  15087. if (Q('p4randomPassword').checked == false) { ok &= passok; }
  15088. QE('idx_dlgOkButton', ok);
  15089. }
  15090. function showCreateNewAccountDialogEx() {
  15091. var username = ((features & 0x200000) == 0) ? Q('p4name').value : Q('p4email').value; // Username is email address
  15092. var x = { action: 'adduser', username: username, email: Q('p4email').value, pass: Q('p4pass1').value, resetNextLogin: Q('p4resetNextLogin').checked, randomPassword: Q('p4randomPassword').checked, removeEvents: Q('p4removeEvents').checked };
  15093. if (serverinfo.emailcheck) {
  15094. x.emailVerified = Q('p4verifiedEmail').checked;
  15095. x.emailInvitation = Q('p4invitationEmail').checked;
  15096. }
  15097. if (serverinfo.crossDomain) { x.domain = serverinfo.crossDomain[parseInt(Q('p4domain').value)]; }
  15098. meshserver.send(x);
  15099. }
  15100. function showUserGroupDialog(e, userid) {
  15101. if (xxdialogMode) return;
  15102. haltEvent(e);
  15103. userid = decodeURIComponent(userid);
  15104. var user = users[userid.toLowerCase()], groups = "";
  15105. if (user.groups != null) { groups = user.groups.join(', ') }
  15106. var x = "Enter a comma seperate list of administrative realms names." + '<br /><br />';
  15107. x += addHtmlValue("Realms", '<input id=dp4usergroups style=width:230px value="' + groups + '" placeholder="' + "Name1, Name2, Name3" + '" maxlength=256 onchange=p4validateUserGroups() onkeyup=p4validateUserGroups() />');
  15108. setDialogMode(2, "Administrative Realms", 3, showUserGroupDialogEx, x, user);
  15109. focusTextBox('dp4usergroups');
  15110. p4validateUserGroups();
  15111. return false;
  15112. }
  15113. function p4validateUserGroups() {
  15114. var groups = Q('dp4usergroups').value;
  15115. var k = 0, i = groups.indexOf('"') + groups.indexOf('/') + groups.indexOf('>') + groups.indexOf('<') + groups.indexOf('\'');
  15116. var g = groups.split(',');
  15117. for (var j in g) { if (g[j].trim().length == 0) k++; }
  15118. QE('idx_dlgOkButton', (groups == '') || ((i == -5) && (k < 1)));
  15119. }
  15120. function showUserGroupDialogEx(event, user) {
  15121. var groups = Q('dp4usergroups').value, g = groups.split(','), g2 = [];
  15122. for (var j in g) { var x = g[j].trim(); if (x.length > 0) { g2.push(x); } }
  15123. meshserver.send({ action: 'edituser', id: user._id, groups: g2 });
  15124. }
  15125. function showUserAdminDialog(e, userid) {
  15126. if (xxdialogMode) return;
  15127. haltEvent(e);
  15128. userid = decodeURIComponent(userid);
  15129. var user = users[userid];
  15130. if (user == null) return;
  15131. var uself = (userinfo._id == user._id);
  15132. var x = '';
  15133. x += '<label><input type=checkbox onchange=showUserAdminDialogValidate() id=ua_fileaccess>' + "Server Files" + '</label>, <input type=number onchange=showUserAdminDialogValidate() maxlength=10 id=ua_fileaccessquota>' + "k max, blank for default" + '<br><hr/>';
  15134. x += '<label><input type=checkbox onchange=showUserAdminDialogValidate() id=ua_fulladmin>' + "Full Administrator" + '</label><br>';
  15135. x += '<label><input type=checkbox onchange=showUserAdminDialogValidate() id=ua_serverbackup>' + "Server Backup" + '</label><br>';
  15136. x += '<label><input type=checkbox onchange=showUserAdminDialogValidate() id=ua_serverrestore>' + "Server Restore" + '</label><br>';
  15137. x += '<label><input type=checkbox onchange=showUserAdminDialogValidate() id=ua_serverupdate>' + "Server Updates" + '</label><br></div>';
  15138. x += '<div id=d2MngUsr><label><input type=checkbox onchange=showUserAdminDialogValidate() id=ua_manageusers>' + "Manage Users" + '</label><br></div>';
  15139. x += '<div id=d2MngGrp><label><input type=checkbox onchange=showUserAdminDialogValidate() id=ua_manageusergroups>' + "Manage User Groups" + '</label><br></div>';
  15140. x += '<div id=d2MngRec><label><input type=checkbox onchange=showUserAdminDialogValidate() id=ua_managerecordings>' + "Manage Recordings" + '</label><br></div>';
  15141. x += '<div id=d2AllEvnt><label><input type=checkbox onchange=showUserAdminDialogValidate() id=ua_allevents>' + "View All Events" + '</label><br></div>';
  15142. if (x != '') { x += '<hr/>'; }
  15143. x = '<div><div id=d2AdminPermissions>' + x;
  15144. x += '<label><input type=checkbox onchange=showUserAdminDialogValidate() id=ua_lockedaccount>' + "Lock Account" + '</label><br>';
  15145. x += '<label><input type=checkbox onchange=showUserAdminDialogValidate() id=ua_nonewgroups>' + "No New Device Groups" + '</label><br>';
  15146. x += '<label><input type=checkbox onchange=showUserAdminDialogValidate() id=ua_nonewdevices>' + "No New Devices" + '</label><br>';
  15147. x += '<label><input type=checkbox onchange=showUserAdminDialogValidate() id=ua_nomeshcmd>' + "No Tools (MeshCmd/Router)" + '</label><br>';
  15148. x += '<label><input type=checkbox onchange=showUserAdminDialogValidate() id=ua_locksettings>' + "Lock Account Settings" + '</label><br>';
  15149. x += '</div>';
  15150. setDialogMode(2, "Server Permissions", 2 + (uself?0:1), showUserAdminDialogEx, x, user);
  15151. if (user.siteadmin && user.siteadmin != 0) {
  15152. Q('ua_fulladmin').checked = (user.siteadmin == 0xFFFFFFFF);
  15153. Q('ua_serverbackup').checked = ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1) != 0)); // Server Backup
  15154. Q('ua_serverrestore').checked = ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 4) != 0)); // Server Restore
  15155. Q('ua_fileaccess').checked = ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 8) != 0)); // Server Files
  15156. Q('ua_serverupdate').checked = ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 16) != 0)); // Server Update
  15157. Q('ua_lockedaccount').checked = ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 32) != 0)); // Account locked
  15158. Q('ua_nonewgroups').checked = ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 64) != 0)); // No New Groups
  15159. Q('ua_nomeshcmd').checked = ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 128) != 0)); // No Tools (MeshCMD / Router)
  15160. Q('ua_locksettings').checked = ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 1024) != 0)); // Lock account settings
  15161. Q('ua_nonewdevices').checked = ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 4096) != 0)); // No New Devices
  15162. }
  15163. if ((userinfo.siteadmin & 2) != 0) {
  15164. Q('ua_manageusers').checked = ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 2) != 0)); // Manage Users
  15165. QE('ua_manageusers', !uself && (userinfo.siteadmin & 2));
  15166. }
  15167. if ((userinfo.siteadmin & 256) != 0) {
  15168. Q('ua_manageusergroups').checked = ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 256) != 0)); // Manage User Groups
  15169. QE('ua_manageusergroups', !uself && (userinfo.siteadmin & 256));
  15170. }
  15171. if ((userinfo.siteadmin & 512) != 0) {
  15172. Q('ua_managerecordings').checked = ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 512) != 0)); // Manage Recordings
  15173. QE('ua_managerecordings', !uself && (userinfo.siteadmin & 512));
  15174. }
  15175. if ((userinfo.siteadmin & 2048) != 0) {
  15176. Q('ua_allevents').checked = ((user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & 2048) != 0)); // View all events
  15177. QE('ua_allevents', !uself && (userinfo.siteadmin & 2048));
  15178. }
  15179. QE('ua_fulladmin', !uself && (userinfo.siteadmin == 0xFFFFFFFF));
  15180. QE('ua_serverbackup', !uself && (userinfo.siteadmin == 0xFFFFFFFF));
  15181. QE('ua_serverrestore', !uself && (userinfo.siteadmin == 0xFFFFFFFF));
  15182. QE('ua_fileaccess', !uself && (userinfo.siteadmin == 0xFFFFFFFF));
  15183. QE('ua_fileaccessquota', !uself && (userinfo.siteadmin == 0xFFFFFFFF));
  15184. QE('ua_serverupdate', !uself && (userinfo.siteadmin == 0xFFFFFFFF));
  15185. QV('d2AdminPermissions', userinfo.siteadmin == 0xFFFFFFFF)
  15186. QV('d2MngUsr', (userinfo.siteadmin & 2) != 0);
  15187. QV('d2MngGrp', (userinfo.siteadmin & 256) != 0);
  15188. QV('d2MngRec', (userinfo.siteadmin & 512) != 0);
  15189. QE('ua_lockedaccount', !uself && (userinfo.siteadmin & 2) && (user.siteadmin != 0xFFFFFFFF) && (userinfo._id != user._id));
  15190. QE('ua_nonewgroups', !uself && (userinfo.siteadmin & 2) && (user.siteadmin != 0xFFFFFFFF) && (userinfo._id != user._id));
  15191. QE('ua_nomeshcmd', !uself && (userinfo.siteadmin & 2) && (user.siteadmin != 0xFFFFFFFF) && (userinfo._id != user._id));
  15192. QE('ua_nonewdevices', !uself && (userinfo.siteadmin & 2) && (user.siteadmin != 0xFFFFFFFF) && (userinfo._id != user._id));
  15193. QE('ua_locksettings', !uself && (userinfo.siteadmin & 2) && (user.siteadmin != 0xFFFFFFFF) && (userinfo._id != user._id));
  15194. Q('ua_fileaccessquota').value = (user.quota != null)?(user.quota / 1024):'';
  15195. showUserAdminDialogValidate();
  15196. return false;
  15197. }
  15198. function showUserAdminDialogValidate() {
  15199. if (userinfo.siteadmin == 0xFFFFFFFF) {
  15200. QE('ua_serverbackup', !Q('ua_fulladmin').checked);
  15201. QE('ua_manageusers', !Q('ua_fulladmin').checked);
  15202. QE('ua_serverrestore', !Q('ua_fulladmin').checked);
  15203. QE('ua_fileaccess', !Q('ua_fulladmin').checked);
  15204. QE('ua_serverupdate', !Q('ua_fulladmin').checked);
  15205. QE('ua_lockedaccount', !Q('ua_fulladmin').checked);
  15206. QE('ua_nonewgroups', !Q('ua_fulladmin').checked);
  15207. QE('ua_nomeshcmd', !Q('ua_fulladmin').checked);
  15208. QE('ua_nonewdevices', !Q('ua_fulladmin').checked);
  15209. QE('ua_manageusergroups', !Q('ua_fulladmin').checked);
  15210. QE('ua_managerecordings', !Q('ua_fulladmin').checked);
  15211. QE('ua_allevents', !Q('ua_fulladmin').checked);
  15212. QE('ua_locksettings', !Q('ua_fulladmin').checked);
  15213. QE('ua_fileaccessquota', Q('ua_fileaccess').checked && !Q('ua_fulladmin').checked);
  15214. }
  15215. }
  15216. function showUserAdminDialogEx(button, user) {
  15217. var siteadmin = 0, quota = parseInt(Q('ua_fileaccessquota').value);
  15218. if (Q('ua_fulladmin').checked == true) { siteadmin = 0xFFFFFFFF; } else {
  15219. if (Q('ua_serverbackup').checked == true) siteadmin += 1;
  15220. if (Q('ua_manageusers').checked == true) siteadmin += 2;
  15221. if (Q('ua_serverrestore').checked == true) siteadmin += 4;
  15222. if (Q('ua_fileaccess').checked == true) siteadmin += 8;
  15223. if (Q('ua_serverupdate').checked == true) siteadmin += 16;
  15224. if (Q('ua_lockedaccount').checked == true) siteadmin += 32;
  15225. if (Q('ua_nonewgroups').checked == true) siteadmin += 64;
  15226. if (Q('ua_nomeshcmd').checked == true) siteadmin += 128;
  15227. if (Q('ua_manageusergroups').checked == true) siteadmin += 256;
  15228. if (Q('ua_managerecordings').checked == true) siteadmin += 512;
  15229. if (Q('ua_locksettings').checked == true) siteadmin += 1024;
  15230. if (Q('ua_allevents').checked == true) siteadmin += 2048;
  15231. if (Q('ua_nonewdevices').checked == true) siteadmin += 4096;
  15232. }
  15233. var x = { action: 'edituser', id: user._id, siteadmin: siteadmin };
  15234. if (isNaN(quota) == false) { x.quota = (quota * 1024); }
  15235. meshserver.send(x);
  15236. }
  15237. function onUserSearchInputChanged() { mainUpdate(16384); }
  15238. //
  15239. // MY USER GROUPS
  15240. //
  15241. function updateUserGroups() {
  15242. // Display user group operations only if allowed for us
  15243. QV('p50userGroupOps', (userinfo.siteadmin & 256) != 0); // SITERIGHT_USERGROUPS = 256
  15244. // Sort the list of group names
  15245. var sortedGroups = [], x = '';
  15246. if (usergroups) { for (var i in usergroups) { sortedGroups.push(usergroups[i]); } }
  15247. sortedGroups.sort(nameSort);
  15248. // Save the list of currently checked users
  15249. var checkedUserGroupids = [], elements = document.getElementsByClassName('UserGroupCheckbox');
  15250. for (var i=0;i<elements.length;i++) { if (elements[i].checked) { checkedUserGroupids.push(elements[i].value); } }
  15251. if (sortedGroups.length == 0) {
  15252. x += '<br />' + "No groups found." + '<br />';
  15253. QV('DuplicateUserGroupButton', false);
  15254. } else {
  15255. // Display the groups using the sorted list
  15256. x += '<table class=p3usersTable cellpadding=0 cellspacing=0>';
  15257. x += '<th>' + "Name" + '<th style=width:80px>' + "Users" + '<th style=width:80px>' + "Device Groups" + '<th style=width:80px>' + "Devices";
  15258. for (var i in sortedGroups) { x += addUserGroupHtml(sortedGroups[i]); }
  15259. x += '</table>';
  15260. QV('DuplicateUserGroupButton', true);
  15261. }
  15262. QH('p50groups', x);
  15263. // Re-check userid's
  15264. elements = document.getElementsByClassName('UserGroupCheckbox');
  15265. for (var i = 0; i < elements.length; i++) { elements[i].checked = ((checkedUserGroupids.indexOf(elements[i].value) >= 0)); }
  15266. p50updateInfo();
  15267. // Update current user panel if needed
  15268. if ((currentUserGroup != null) && (xxcurrentView == 51)) { gotoUserGroup(encodeURIComponentEx(currentUserGroup._id), true); }
  15269. }
  15270. function addUserGroupHtml(group) {
  15271. var usercount = 0, meshcount = 0, devicecount = 0;
  15272. if (group.links) { for (var i in group.links) { if (i.startsWith('user/')) { usercount++; } if (i.startsWith('mesh/')) { meshcount++; } if (i.startsWith('node/')) { devicecount++; } } }
  15273. // Group name, if we are a cross-domain administrator, add the domain.
  15274. var name = EscapeHtml(group.name);
  15275. if ((serverinfo.crossDomain != null)) {
  15276. var grpdomain = group._id.split('/')[1];
  15277. if (grpdomain != '') { name += ', <span style=color:#26F>' + EscapeHtml(grpdomain) + '</span>'; }
  15278. }
  15279. var x = '<tr tabindex=0 onmouseover=userMouseHover2(this,1) onmouseout=userMouseHover2(this,0) onkeypress="if (event.key==\'Enter\') gotoUserGroup(\'' + encodeURIComponentEx(group._id) + '\')"><td style=cursor:pointer>';
  15280. x += '<div class=bar style=width:100%>';
  15281. x += '<div class=baricon><input class=UserGroupCheckbox value=' + encodeURIComponentEx(group._id) + ' onclick=p50updateInfo() type=checkbox></div>';
  15282. x += '<div class=baricon onclick=gotoUserGroup("' + encodeURIComponentEx(group._id) + '")><div class=m4></div></div>';
  15283. x += '<div class=g1 onclick=gotoUserGroup("' + encodeURIComponentEx(group._id) + '")></div><div class=g2 onclick=gotoUserGroup("' + encodeURIComponentEx(group._id) + '")></div>';
  15284. x += '<div style=line-height:24px onclick=gotoUserGroup("' + encodeURIComponentEx(group._id) + '")><span style=font-size:16px>' + name + '</span></div></div><td style=text-align:center>' + usercount + '<td style=text-align:center>' + meshcount + '<td style=text-align:center>' + devicecount;
  15285. return x;
  15286. }
  15287. function p50updateInfo() {
  15288. var elements = document.getElementsByClassName('UserGroupCheckbox'), checkcount = 0;
  15289. for (var i=0;i<elements.length;i++) { if (elements[i].checked === true) checkcount++; }
  15290. QE('UsersGroupsGroupActionButton', checkcount > 0);
  15291. Q('UsersGroupsSelectAllButton').value = (checkcount > 0)?"Select None":"Select All";
  15292. }
  15293. // Called to select all or unselect all users
  15294. function p50usersSelectallButtonFunction() {
  15295. var eself = encodeURIComponentEx(userinfo._id);
  15296. var elements = document.getElementsByClassName('UserGroupCheckbox'), checkcount = 0;
  15297. for (var i=0;i<elements.length;i++) { if (elements[i].checked === true) checkcount++; }
  15298. for (var i=0;i<elements.length;i++) { elements[i].checked = (checkcount == 0) && (elements[i].value != eself); }
  15299. p50updateInfo();
  15300. }
  15301. // Called to perform a group action on many users
  15302. function p50usersGroupActionFunction() {
  15303. var elements = document.getElementsByClassName('UserGroupCheckbox'), checkcount = 0;
  15304. for (var i=0;i<elements.length;i++) { if (elements[i].checked === true) checkcount++; }
  15305. if (checkcount == 0) return;
  15306. var x = "Select an operation to perform on all selected users." + '<br /><br />';
  15307. x += addHtmlValue("Operation", '<select style=width:240px id=d50groupop><option value=1>' + "Delete group" + '</option></select>');
  15308. setDialogMode(2, "Group Action", 3, p50usersGroupActionFunctionEx, x);
  15309. }
  15310. function p50usersGroupActionFunctionEx() {
  15311. var elements = document.getElementsByClassName('UserGroupCheckbox'), userids = [];
  15312. for (var i=0;i<elements.length;i++) { if (elements[i].checked === true) { userids.push(decodeURIComponent(elements[i].value)); } }
  15313. var op = Q('d50groupop').value;
  15314. if (op == 1) {
  15315. // Delete user groups, ask for confirmation
  15316. var x = "Confirm delete selected user group(s)?" + '<br /><br />';
  15317. x += '<label><input id=d3check type=checkbox onchange=p50usersGroupActionFunctionDelCheck() />' + "Confirm" + '</label>';
  15318. setDialogMode(2, "Delete User Groups", 3, p50groupActionFunctionDelExec, x);
  15319. QE('idx_dlgOkButton', false);
  15320. }
  15321. }
  15322. function p50usersGroupActionFunctionDelCheck() { QE('idx_dlgOkButton', Q('d3check').checked); }
  15323. // Delete a batch of user accounts
  15324. function p50groupActionFunctionDelExec(b) {
  15325. var elements = document.getElementsByClassName('UserGroupCheckbox');
  15326. for (var i=0;i<elements.length;i++) { if (elements[i].checked === true) { meshserver.send({ action: 'deleteusergroup', ugrpid: decodeURIComponent(elements[i].value) }); } }
  15327. }
  15328. function showCreateUserGroupDialog(mode) {
  15329. if (xxdialogMode) return;
  15330. var x = '', y = '';
  15331. if (mode == 2) {
  15332. if (usergroups) { for (var i in usergroups) { y += '<option value=' + encodeURIComponentEx(i) + '>' + EscapeHtml(usergroups[i].name) + '</option>'; } }
  15333. x += addHtmlValue("User Group", '<div style=width:230px;margin:0;padding:0><select id=dp4groupid style=width:100%>' + y + '</select></div>');
  15334. }
  15335. if ((mode == 1) && (serverinfo.crossDomain)) {
  15336. var y = '<select style=width:240px id=p4domain>';
  15337. for (var i in serverinfo.crossDomain) { y += '<option value=' + i + '>' + ((serverinfo.crossDomain[i] == '')?"Default":EscapeHtml(serverinfo.crossDomain[i])) + '</option>'; }
  15338. y += '</select>';
  15339. x += addHtmlValue("Domain", y);
  15340. }
  15341. x += addHtmlValue("Name", '<input id=p4name maxlength=64 onchange=showCreateUserGroupDialogValidate() onkeyup=showCreateUserGroupDialogValidate() />');
  15342. x += addHtmlValue("Description", '<textarea id=p4desc value="" style=width:230px;height:60px;resize:none maxlength=1024 /></textarea>');
  15343. setDialogMode(2, (mode == 1)?"Create User Group":"Duplicate User Group", 3, showCreateUserGroupDialogEx, x, mode);
  15344. showCreateUserGroupDialogValidate();
  15345. Q('p4name').focus();
  15346. }
  15347. function showCreateUserGroupDialogValidate() { QE('idx_dlgOkButton', Q('p4name').value.length > 0); }
  15348. function showCreateUserGroupDialogEx(b, mode) {
  15349. var x = { action: 'createusergroup', name: Q('p4name').value, desc: Q('p4desc').value };
  15350. if (mode == 2) { x.clone = decodeURIComponent(Q('dp4groupid').value); }
  15351. if ((mode == 1) && (serverinfo.crossDomain)) { x.domain = serverinfo.crossDomain[parseInt(Q('p4domain').value)]; }
  15352. meshserver.send(x);
  15353. }
  15354. //
  15355. // MY USER GROUP
  15356. //
  15357. var currentUserGroup = null;
  15358. function gotoUserGroup(groupid, force) {
  15359. if (xxdialogMode && !force) return;
  15360. var group = currentUserGroup = usergroups?usergroups[decodeURIComponent(groupid)]:null;
  15361. if (group == null) { if (xxcurrentView == 51) { setDialogMode(0); go(50); } return; }
  15362. // Add user group name
  15363. var gname = EscapeHtml(group.name);
  15364. if (gname.length == 0) { gname = '<i>' + "None" + '</i>'; }
  15365. if ((currentUserGroup.membershipType == null) && ((userinfo.siteadmin & 256) != 0)) { gname = '<span tabindex=0 title="' + "Click here to edit the user group name" + '" onclick=p51editgroup(1) onkeyup="if (event.key == \'Enter\') p51editgroup(1)" style=cursor:pointer>' + gname + ' <img class=hoverButton src="images/link5.png" /></span>'; }
  15366. QH('p51groupName', gname);
  15367. var usercount = 0, meshcount = 0, devicecount = 0;
  15368. if (group.links) {
  15369. for (var i in group.links) {
  15370. if (i.startsWith('user/')) { usercount++; }
  15371. if (i.startsWith('mesh/')) { meshcount++; }
  15372. if (i.startsWith('node/')) { devicecount++; }
  15373. }
  15374. }
  15375. var desc = group.desc;
  15376. if ((desc == null) || (desc == '')) { desc = '<i>' + "None" + '<i>'; } else { desc = EscapeHtml(desc); }
  15377. var x = '<div style=min-height:80px><table style=width:100%>';
  15378. if ((args.hide & 8) != 0) { x += '<br />' + addDeviceAttribute("Name", gname); } // If title bar is hidden, display the user group name here
  15379. if ((serverinfo.crossDomain != null) || (debugmode != 0)) {
  15380. var d = group._id.split('/')[1];
  15381. x += addDeviceAttribute("Domain", (d != '')?EscapeHtml(d):('<i>' + "Default" + '</i>'));
  15382. x += addDeviceAttribute("Group Identifier", EscapeHtml(group._id));
  15383. }
  15384. if (currentUserGroup.membershipType != null) {
  15385. x += addDeviceAttribute("Group Type", EscapeHtml(currentUserGroup.membershipType));
  15386. }
  15387. if ((userinfo.siteadmin & 256) != 0) {
  15388. x += addDeviceAttribute("Description", '<span onclick=p51editgroup(2,' + (currentUserGroup.membershipType != null) + ') style=cursor:pointer>' + desc + ' <img class=hoverButton src="images/link5.png" /></span>');
  15389. } else {
  15390. x += addDeviceAttribute("Description", desc);
  15391. }
  15392. // Display features
  15393. if (serverinfo.userGroupsSessionRecording == 1) {
  15394. var userGroupFeatures = [];
  15395. if ((group.flags) && (group.flags & 2)) { userGroupFeatures.push("Record Sessions"); }
  15396. userGroupFeatures = userGroupFeatures.join(', ');
  15397. if (userGroupFeatures == '') { userGroupFeatures = '<i>' + "None" + '</i>'; }
  15398. x += addDeviceAttribute("Features", addLink(userGroupFeatures, 'p51edituserGroupFeatures()'));
  15399. }
  15400. // Display user consent flags for this user group
  15401. {
  15402. var consentOptionsStr = [], consent = 0;
  15403. if (group.consent) { consent = group.consent; }
  15404. if (serverinfo.consent) { consent |= serverinfo.consent; }
  15405. if ((consent & 0x0040) && (consent & 0x0008)) { consentOptionsStr.push("Desktop Prompt+Toolbar"); } else if (consent & 0x0040) { consentOptionsStr.push("Desktop Toolbar"); } else if (consent & 0x0008) { consentOptionsStr.push("Desktop Prompt"); } else { if (consent & 0x0001) { consentOptionsStr.push("Desktop Notify"); } }
  15406. if (consent & 0x0010) { consentOptionsStr.push("Terminal Prompt"); } else { if (consent & 0x0002) { consentOptionsStr.push("Terminal Notify"); } }
  15407. if (consent & 0x0020) { consentOptionsStr.push("Files Prompt"); } else { if (consent & 0x0004) { consentOptionsStr.push("Files Notify"); } }
  15408. if (consent == 7) { consentOptionsStr = ["Always Notify"]; }
  15409. if ((consent & 56) == 56) { consentOptionsStr = ["Always Prompt"]; }
  15410. consentOptionsStr = consentOptionsStr.join(', ');
  15411. if (consentOptionsStr == '') { consentOptionsStr = '<i>' + "None" + '</i>'; }
  15412. x += addDeviceAttribute("User Consent", addLinkConditional(consentOptionsStr, 'p20editmeshconsent(4)', true));
  15413. }
  15414. x += addDeviceAttribute("Users", usercount);
  15415. x += addDeviceAttribute("Device Groups", meshcount);
  15416. x += addDeviceAttribute("Devices", devicecount);
  15417. x += '</table></div><br />';
  15418. if ((userinfo.siteadmin & 256) != 0) {
  15419. x += '<input type=button value="' + "Broadcast" + '" title="' + "Send a notice to all users in this group." + '" onclick=showUserBroadcastDialog("' + encodeURIComponentEx(group._id) + '") />';
  15420. }
  15421. // Setup the panel
  15422. QH('p51group', x);
  15423. x = '<br />';
  15424. if ((currentUserGroup.membershipType == null) && ((userinfo.siteadmin & 256) != 0)) {
  15425. x += '<a href=# onclick="return p51showAddUserDialog()" style=cursor:pointer;margin-right:10px><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add Users" + '</a>';
  15426. }
  15427. 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>' + "Group Members" + '</th><th scope=col style=text-align:left></th></tr>';
  15428. // Sort the users for this mesh
  15429. var count = 1, sortedusers = [];
  15430. for (var i in currentUserGroup.links) {
  15431. if (i.startsWith('user/') == false) continue;
  15432. var uname = i.split('/')[2];
  15433. if (currentUserGroup.links[i].name) { uname = currentUserGroup.links[i].name; }
  15434. if (i == userinfo._id) { uname = userinfo.name; }
  15435. sortedusers.push({ id: i, name: uname, rights: currentUserGroup.links[i].rights });
  15436. }
  15437. sortedusers.sort(function(a, b) { if (a.name > b.name) return 1; if (a.name < b.name) return -1; return 0; });
  15438. // Display all users for this user group
  15439. for (var i in sortedusers) {
  15440. var trash = '';
  15441. if (currentUserGroup.membershipType == null) { trash = '<a href=# onclick=\'return p51deleteUser(event,"' + encodeURIComponentEx(sortedusers[i].id) + '")\' title="' + "Remove user rights to this device group" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
  15442. var username = EscapeHtml(decodeURIComponent(sortedusers[i].name));
  15443. if (users != null) { username = '<a href=# onclick=\'gotoUser("' + encodeURIComponentEx(sortedusers[i].id) + '");haltEvent(event);\'>' + username + '</a>'; }
  15444. x += '<tr ' + (((count % 2) == 0) ? 'style=background-color:#DDD' : '') + '><td><div title="' + "User" + '" class=m2></div><div>&nbsp;' + username + '<div></div></div></td><td><div style=float:right>' + trash + '</div></td></tr>';
  15445. ++count;
  15446. }
  15447. if (count == 1) { x += '<tr><td><div style=padding:6px>&nbsp;<i>' + "No Members" + '</i><div></div></div></td><td></td></tr>'; }
  15448. x += '</tbody></table><br />';
  15449. // Display all device groups for this user group
  15450. count = 1;
  15451. var deviceGroupCount = 0, newDeviceGroup = false;
  15452. for (var i in meshes) { if (currentUserGroup._id.split('/')[1] != meshes[i]._id.split('/')[1]) continue; deviceGroupCount++; if ((currentUserGroup.links == null) || (currentUserGroup.links[i] == null)) { newDeviceGroup = true; } }
  15453. if ((deviceGroupCount > 0) && (newDeviceGroup)) { x += '<a href=# onclick="return p20showAddMeshUserDialog(3)" style=cursor:pointer;margin-right:10px><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add Device Group" + '</a>'; }
  15454. 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>' + "Common Device Groups" + '</th><th scope=col style=text-align:left></th></tr>';
  15455. if (currentUserGroup.links) {
  15456. var omeshes = [];
  15457. for (var i in currentUserGroup.links) { if (i.startsWith('mesh/')) { if (meshes[i] != null) { omeshes.push(meshes[i]); } } }
  15458. omeshes = getOrderedList(omeshes, 'name');
  15459. for (var i in omeshes) {
  15460. var cr = 0, mesh = omeshes[i], r = currentUserGroup.links[mesh._id].rights, trash = '', rights = makeDeviceGroupRightsString(r);
  15461. if ((userinfo.links) && (userinfo.links[mesh._id] != null) && (userinfo.links[mesh._id].rights != null)) { cr = userinfo.links[mesh._id].rights; }
  15462. var meshname = '<i>' + "Unknown Device Group" + '</i>';
  15463. if (mesh) { meshname = '<a href=# onclick=\'gotoMesh("' + mesh._id + '");haltEvent(event);\'>' + mesh.name + '</a>'; } else {}
  15464. if ((cr & 2) != 0) {
  15465. trash = '<a href=# onclick=\'return p51removeMeshFromUserGroup(event,"' + encodeURIComponentEx(mesh._id) + '")\' title="' + "Remove user group rights to this device group" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>';
  15466. rights = '<span style=cursor:pointer onclick=p20showAddMeshUserDialog(3,"' + encodeURIComponentEx(mesh._id) + '")>' + rights + ' <img class=hoverButton style=cursor:pointer src=images/link5.png></span>';
  15467. }
  15468. x += '<tr ' + (((++count % 2) == 0) ? 'style=background-color:#DDD' : '') + '><td style=width:30%><div title="' + "Device Group" + '" class=m99></div><div>&nbsp;' + meshname + '<div></div></div></td><td style=width:70%><div style=float:right>' + trash + '</div><div>' + rights + '</div></td></tr>';
  15469. }
  15470. }
  15471. if (count == 1) { x += '<tr><td><div style=padding:6px>&nbsp;<i>' + "No device groups in common" + '</i><div></div></div></td><td></td></tr>'; }
  15472. x += '</tbody></table>';
  15473. // Display all devices for this user group
  15474. count = 1;
  15475. x += '<br />';
  15476. if (currentUserGroup._id.split('/')[1] == userinfo._id.split('/')[1]) { x += '<a href=# onclick="return p20showAddMeshUserDialog(7)" style=cursor:pointer;margin-right:10px><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add Device" + '</a>'; }
  15477. 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>' + "Common Devices" + '</th><th scope=col style=text-align:left></th></tr>';
  15478. if (currentUserGroup.links) {
  15479. var onodes = [];
  15480. for (var i in currentUserGroup.links) { if (i.startsWith('node/')) { var node = getNodeFromId(i); if (node != null) { onodes.push(node); } } }
  15481. onodes = getOrderedList(onodes, 'name');
  15482. for (var i in onodes) {
  15483. var node = onodes[i], r = currentUserGroup.links[node._id].rights, trash = '', rights = makeUserDeviceRightsString(r), cr = GetNodeRights(node);
  15484. var nodename = '<i>' + "Unknown Device" + '</i>';
  15485. if (node) { nodename = '<a href=# onclick=\'gotoDevice("' + node._id + '");haltEvent(event);\'>' + node.name + '</a>'; } else {}
  15486. if ((cr & 2) != 0) {
  15487. trash = '<a href=# onclick=\'return p51removeDeviceFromUserGroup(event,"' + encodeURIComponentEx(node._id) + '")\' title="' + "Remove user group rights to this device" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>';
  15488. rights = '<span style=cursor:pointer onclick=p20showAddMeshUserDialog(7,"' + encodeURIComponentEx(node._id) + '")>' + rights + ' <img class=hoverButton style=cursor:pointer src=images/link5.png></span>';
  15489. }
  15490. x += '<tr ' + (((++count % 2) == 0) ? 'style=background-color:#DDD' : '') + '><td style=width:30%><div title="' + "Device Group" + '" class=m99></div><div>&nbsp;' + nodename + '<div></div></div></td><td style=width:70%><div style=float:right>' + trash + '</div><div>' + rights + '</div></td></tr>';
  15491. }
  15492. }
  15493. if (count == 1) { x += '<tr><td><div style=padding:6px>&nbsp;<i>' + "No devices in common" + '</i><div></div></div></td><td></td></tr>'; }
  15494. x += '</tbody></table>';
  15495. if (((currentUserGroup.membershipType == null) || (usercount == 0)) && ((userinfo.siteadmin & 256) != 0)) {
  15496. x += '<div style=font-size:small;text-align:right><span><a href=# onclick=p51showDeleteUserGroupDialog() style=cursor:pointer>' + "Delete User Group" + '</a></span></div>';
  15497. }
  15498. QH('p51group2', x);
  15499. go(51);
  15500. // Change the URL
  15501. var urlviewmode = '';
  15502. if (((features & 0x10000000) == 0) && (xxcurrentView >= 51) && (xxcurrentView <= 59) && (currentUserGroup != null)) {
  15503. urlviewmode = '?viewmode=' + xxcurrentView + '&gotougrp=' + ((serverinfo.crossDomain)?currentUserGroup._id:currentUserGroup._id.split('/')[2]);
  15504. for (var i in urlargs) { urlviewmode += ('&' + i + '=' + urlargs[i]); }
  15505. try { window.history.replaceState({}, document.title, window.location.pathname + urlviewmode); } catch (ex) { }
  15506. }
  15507. }
  15508. function p51edituserGroupFeatures() {
  15509. if (xxdialogMode) return;
  15510. var flags = (currentUserGroup.flags)?currentUserGroup.flags:0, x = ''; // Flags: 2 = Session Recording
  15511. if (serverinfo.userGroupsSessionRecording == 1) {
  15512. x += '<div><label><input type=checkbox id=d51flag1 ' + ((flags & 2) ? 'checked' : '') + '>' + "Record sessions" + '</label><br></div>';
  15513. }
  15514. setDialogMode(2, "Edit User Group Features", 3, p51edituserGroupFeaturesEx, x);
  15515. }
  15516. // Send to the server the new user's real name
  15517. function p51edituserGroupFeaturesEx() {
  15518. // Setup user flags
  15519. var flags = 0; // Flags: 2 = Session Recording
  15520. if ((serverinfo.userGroupsSessionRecording == 1) && Q('d51flag1').checked) { flags += 2; }
  15521. meshserver.send({ action: 'editusergroup', ugrpid: currentUserGroup._id, flags: flags });
  15522. }
  15523. function p51removeDeviceFromUserGroup(e, nodeid) {
  15524. if (xxdialogMode) return;
  15525. var node = getNodeFromId(decodeURIComponent(nodeid));
  15526. if (node == null) return;
  15527. setDialogMode(2, "Remove Device Permissions", 3, p51removeDeviceFromUserGroupEx, format("Confirm removal of access rights for device \"{0}\"?", node.name), node._id);
  15528. }
  15529. function p51removeDeviceFromUserGroupEx(b, nodeid) {
  15530. meshserver.send({ action: 'adddeviceuser', nodeid: nodeid, userids: [ currentUserGroup._id ], rights: 0, remove: true });
  15531. }
  15532. function p51removeMeshFromUserGroup(e, meshid) {
  15533. if (xxdialogMode) return;
  15534. var mesh = meshes[decodeURIComponent(meshid)];
  15535. if (mesh == null) return;
  15536. setDialogMode(2, "Remove Device Group Permissions", 3, p51removeMeshFromUserGroupEx, format("Confirm removal of access rights for device group \"{0}\"?", mesh.name), mesh._id);
  15537. }
  15538. function p51removeMeshFromUserGroupEx(b, meshid) {
  15539. meshserver.send({ action: 'removemeshuser', meshid: meshid, userid: currentUserGroup._id });
  15540. }
  15541. function p51editgroup(focus, nameReadOnly) {
  15542. if (xxdialogMode) return;
  15543. var x = addHtmlValue("Name", '<input id=dp51name style=width:230px maxlength=32 onchange=p51editgroupValidate() onkeyup=p51editgroupValidate(event) ' + (nameReadOnly ? 'readonly disabled' : '') + '/>');
  15544. x += addHtmlValue("Description", '<div style=width:230px;margin:0;padding:0><textarea id=dp51desc maxlength=1024 style=width:100%;resize:none></textarea></div>');
  15545. setDialogMode(2, "Edit User Group", 3, p51editgroupEx, x);
  15546. Q('dp51name').value = currentUserGroup.name;
  15547. if (currentUserGroup.desc) Q('dp51desc').value = currentUserGroup.desc;
  15548. p51editgroupValidate();
  15549. if (focus == 2) { Q('dp51desc').focus(); } else { Q('dp51name').focus(); }
  15550. }
  15551. function p51editgroupEx() {
  15552. meshserver.send({ action: 'editusergroup', ugrpid: currentUserGroup._id, name: Q('dp51name').value, desc: Q('dp51desc').value });
  15553. }
  15554. function p51editgroupValidate(e) {
  15555. QE('idx_dlgOkButton', Q('dp51name').value.length > 0); if (e && e.key == 'Enter') { Q('dp51desc').focus(); }
  15556. }
  15557. function p51showDeleteUserGroupDialog() {
  15558. if (xxdialogMode) return false;
  15559. var x = format("Delete user group {0}?", EscapeHtml(currentUserGroup.name)) + '<br /><br />';
  15560. x += '<label><input id=p51check type=checkbox onchange=p51validateDeleteGroupDialog() />' + "Confirm" + '</label>';
  15561. setDialogMode(2, "Delete User Group", 3, p51showDeleteUserGroupDialogEx, x);
  15562. p51validateDeleteGroupDialog();
  15563. return false;
  15564. }
  15565. function p51validateDeleteGroupDialog() {
  15566. QE('idx_dlgOkButton', Q('p51check').checked);
  15567. }
  15568. function p51showDeleteUserGroupDialogEx(buttons, tag) {
  15569. meshserver.send({ action: 'deleteusergroup', ugrpid: currentUserGroup._id });
  15570. }
  15571. function p51deleteUser(e, id) {
  15572. haltEvent(e);
  15573. p51viewuserEx(2, decodeURIComponent(id));
  15574. return false;
  15575. }
  15576. function p51viewuserEx(button, userid) {
  15577. if (button != 2) return;
  15578. var uname = userid.split('/')[2];
  15579. if (users && users[userid]) { uname = users[userid].name; }
  15580. if (userinfo._id == userid) { uname = userinfo.name; }
  15581. setDialogMode(2, "Remove User Membership", 3, p51viewuserEx2, format("Confirm membership removal of user \"{0}\"?", EscapeHtml(decodeURIComponent(uname))), userid);
  15582. }
  15583. function p51viewuserEx2(button, userid) { meshserver.send({ action: 'removeuserfromusergroup', ugrpid: currentUserGroup._id, userid: userid }); }
  15584. function p51showAddUserDialog() {
  15585. if (xxdialogMode) return false;
  15586. var x = "Allow users to manage this device group and devices in this group.";
  15587. if (features & 0x00080000) { x += " Users need to login to this server once before they can be added to a device group." }
  15588. x += '<br /><br /><div style=\'position:relative\'>';
  15589. x += addHtmlValue("User Identifiers", '<input id=dp51username style=width:230px maxlength=32 onchange=p51validateAddUserDialog() onkeyup=p51validateAddUserDialog() placeholder="user1, user2, user3" />');
  15590. x += '<div id=dp51usersuggest class=suggestionBox style=\'top:30px;left:130px;display:none\'></div>';
  15591. x += '</div><br>';
  15592. setDialogMode(2, "Add Users to User Group", 3, p51showAddUserDialogEx, x);
  15593. Q('dp51username').focus();
  15594. p51validateAddUserDialog();
  15595. return false;
  15596. }
  15597. function p51setname(name) {
  15598. name = decodeURIComponent(name);
  15599. var xusers = Q('dp51username').value.split(',');
  15600. for (var i in xusers) { xusers[i] = xusers[i].trim(); }
  15601. xusers[xusers.length - 1] = name;
  15602. Q('dp51username').value = xusers.join(', ');
  15603. p51validateAddUserDialog();
  15604. return false;
  15605. }
  15606. function p51validateAddUserDialog() {
  15607. var ok = true;
  15608. if (Q('dp51username')) {
  15609. var xusers = Q('dp51username').value.split(',');
  15610. for (var i in xusers) {
  15611. var xuser = xusers[i] = xusers[i].trim();
  15612. if (xuser.length == 0) { ok = false; } else if (xuser.indexOf('"') >= 0) { ok = false; }
  15613. }
  15614. // Fill the suggestion box
  15615. var showsuggestbox = false, exactMatch = false;
  15616. if (users != null) {
  15617. var lastuser = xusers[xusers.length - 1].trim(), lastuserl = lastuser.toLowerCase(), matchingUsers = [];
  15618. if (lastuser.length > 0) {
  15619. for (var i in users) {
  15620. var userSplit = users[i]._id.split('/');
  15621. if ((currentUserGroup.domain == userSplit[1]) && (userSplit[2] === lastuserl)) { exactMatch = true; break; }
  15622. if ((users[i].name.toLowerCase().indexOf(lastuserl) >= 0) && (currentUserGroup.domain == userSplit[1])) { matchingUsers.push([users[i]._id, users[i].name]); if (matchingUsers.length >= 8) break; }
  15623. }
  15624. if ((exactMatch == false) && (matchingUsers.length > 0)) {
  15625. var x = '';
  15626. for (var i in matchingUsers) {
  15627. var sid = matchingUsers[i][0], sname = matchingUsers[i][1];
  15628. if (sid.split('/')[2] == sname.toLowerCase()) {
  15629. x += '<div class=suggestionBoxItem onclick=\'p51setname("' + encodeURIComponentEx(sid.split('/')[2]) + '")\'>' + EscapeHtml(sname) + '</div>';
  15630. } else {
  15631. x += '<div class=suggestionBoxItem onclick=\'p51setname("' + encodeURIComponentEx(sid.split('/')[2]) + '")\'><div>' + EscapeHtml(sname) + '</div><div class=suggestionBoxSubItem>' + EscapeHtml(sid.split('/')[2]) + '</div></div>';
  15632. }
  15633. }
  15634. QH('dp51usersuggest', x);
  15635. showsuggestbox = true;
  15636. }
  15637. }
  15638. }
  15639. QV('dp51usersuggest', showsuggestbox);
  15640. }
  15641. QE('idx_dlgOkButton', ok);
  15642. }
  15643. function p51showAddUserDialogEx(b, t) {
  15644. if (t == null) {
  15645. var users = Q('dp51username').value.split(','), users2 = [];
  15646. for (var i in users) { users2.push(users[i].trim()); }
  15647. meshserver.send({ action: 'addusertousergroup', ugrpid: currentUserGroup._id, usernames: users2 });
  15648. } else {
  15649. meshserver.send({ action: 'addusertousergroup', ugrpid: currentUserGroup._id, usernames: [ t.split('/')[2] ] });
  15650. }
  15651. }
  15652. //
  15653. // MY USER GENERAL
  15654. //
  15655. var currentUser = null;
  15656. function gotoUser(userid, force, event) {
  15657. if (xxdialogMode && !force) return;
  15658. if ((event != null) && (event.originalTarget != null) && (event.originalTarget.href != null)) return;
  15659. var user = currentUser = users[decodeURIComponent(userid)];
  15660. if (user == null) { setDialogMode(0); go(4); return; }
  15661. QH('p30userName', EscapeHtml(user.name));
  15662. QH('p31userName', EscapeHtml(user.name));
  15663. var self = (user._id == userinfo._id), activeSessions = 0;
  15664. if (wssessions != null && wssessions[user._id]) { activeSessions = wssessions[user._id]; }
  15665. // Update account picture
  15666. if ((user.flags != null) && (user.flags & 1)) {
  15667. QV('MainUserImage', false);
  15668. QV('MainUserImageEx', true);
  15669. if (user.accountImageRnd == null) { user.accountImageRnd = Math.floor(Math.random() * 9999999999); }
  15670. Q('MainUserImageEx').src = 'userimage.ashx?id=' + (user._id.split('/')[2]) + '&rnd=' + user.accountImageRnd;
  15671. } else {
  15672. // Change user grayscale
  15673. Q('MainUserImage').classList.remove('gray');
  15674. if (activeSessions == 0) { Q('MainUserImage').classList.add('gray'); }
  15675. QV('MainUserImage', true);
  15676. QV('MainUserImageEx', false);
  15677. }
  15678. // Add user auth strategy
  15679. var shortuserid = user._id.split('/')[2];
  15680. if (shortuserid.startsWith('~twitter:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/twitter64.png'; }
  15681. else if (shortuserid.startsWith('~google:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/google64.png'; }
  15682. else if (shortuserid.startsWith('~github:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/github64.png'; }
  15683. else if (shortuserid.startsWith('~azure:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/azure64.png'; }
  15684. else if (shortuserid.startsWith('~oidc:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/oidc64.png'; }
  15685. else if (shortuserid.startsWith('~jumpcloud:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/jumpcloud64.png'; }
  15686. else if (shortuserid.startsWith('~intel:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/intel64.png'; }
  15687. else if (shortuserid.startsWith('~:')) { QV('p30userAuthServiceLogo', true); Q('p30userAuthServiceLogo').src = 'images/login/generic64.png'; }
  15688. else { QV('p30userAuthServiceLogo', false); }
  15689. // Server permissions
  15690. var msg = [], premsg = '';
  15691. if ((user.siteadmin != null) && ((user.siteadmin & 32) != 0) && (user.siteadmin != 0xFFFFFFFF)) { premsg = '<img src="images/padlock12.png" height=12 width=8 title="' + "Account is locked" + '" style="margin-top:2px" /> '; msg.push("Locked account"); }
  15692. if ((user.siteadmin == null) || ((user.siteadmin & (0xFFFFFFFF - 1248)) == 0)) { msg.push("No server rights"); } else if (user.siteadmin == 8) { msg.push("Access to server files"); } else if (user.siteadmin == 0xFFFFFFFF) { msg.push("Full administrator"); } else { msg.push("Partial rights"); }
  15693. if ((user.siteadmin != null) && (user.siteadmin != 0xFFFFFFFF) && ((user.siteadmin & (64 + 128 + 1024)) != 0)) { msg.push("Restrictions"); }
  15694. // Show user attributes
  15695. var x = '<div style=min-height:80px><table style=width:100%>';
  15696. if ((args.hide & 8) != 0) { x += '<br />' + addDeviceAttribute("Name", user.name); } // If title bar is hidden, display the user name here
  15697. var email = user.email?EscapeHtml(user.email):'<i>' + "Not set" + '</i>', everify = '';
  15698. var realname = user.realname?EscapeHtml(user.realname):'<i>' + "Not set" + '</i>';
  15699. if (serverinfo.emailcheck) { everify = ((user.emailVerified == true) ? '<b style=color:green;cursor:pointer title="' + "Email is verified" + '">&#x2713</b> ' : '<b style=color:red;cursor:pointer title="' + "Email not verified" + '">&#x2717;</b> '); }
  15700. if ((serverinfo.crossDomain) || (debugmode != 0)) {
  15701. var d = user._id.split('/')[1];
  15702. x += addDeviceAttribute("Domain", ((d != '')?EscapeHtml(d):('<i>' + "Default" + '</i>')));
  15703. x += addDeviceAttribute("User Identifier", EscapeHtml(user._id));
  15704. } else {
  15705. if (user.name.toLowerCase() != user._id.split('/')[2]) { x += addDeviceAttribute("User Identifier", EscapeHtml(user._id.split('/')[2])); }
  15706. }
  15707. var emailLink = '';
  15708. if (user.email) { emailLink = ' <a href="mailto:' + EscapeHtml(user.email) + '" \'><img class=hoverButton src="images/link1.png" /></a>'; }
  15709. if (((user.siteadmin != 0xFFFFFFFF) || (userinfo.siteadmin == 0xFFFFFFFF))) { // If we are not site admin, we can't change a admin email or real name
  15710. x += addDeviceAttribute("Email", '<span style=cursor:pointer onclick=p30showUserEmailChangeDialog(event,"' + encodeURIComponentEx(user._id) + '")>' + everify + email + emailLink + ' <img class=hoverButton style=cursor:pointer src="images/link5.png" /></span>');
  15711. x += addDeviceAttribute("Real Name", '<span style=cursor:pointer onclick=p30showUserRealNameChangeDialog(event,"' + encodeURIComponentEx(user._id) + '")>' + realname + ' <img class=hoverButton style=cursor:pointer src="images/link5.png" /></span>');
  15712. } else {
  15713. x += addDeviceAttribute("Email", everify + email + emailLink);
  15714. x += addDeviceAttribute("Real Name", realname);
  15715. }
  15716. if ((features & 0x02000000) || (user.phone != null)) { // If SMS is enabled on the server or user has a phone number
  15717. x += addDeviceAttribute("Phone Number", '<span style=cursor:pointer onclick=p30editPhone()>' + (user.phone?user.phone:('<i>' + "None" + '</i>')) + ' <img class=hoverButton style=cursor:pointer src="images/link5.png" /></span>');
  15718. }
  15719. if ((features2 & 0x02000000) || (user.msghandle != null)) { // If user messaging is enabled on the server or user has a messaging handle
  15720. x += addDeviceAttribute("Messaging", '<span style=cursor:pointer onclick=p30editMessaging()><img src="images/messaging12.png" height=12 width=12 title="' + "Messaging enabled" + '" style="margin-top:2px" /> ' + (user.msghandle?user.msghandle:('<i>' + "None" + '</i>')) + ' <img class=hoverButton style=cursor:pointer src="images/link5.png" /></span>');
  15721. }
  15722. // Display features
  15723. var userFeatures = [];
  15724. if ((serverinfo.usersSessionRecording == 1) && (user.flags) && (user.flags & 2)) { userFeatures.push("Record Sessions"); }
  15725. if (user.removeRights) {
  15726. if ((user.removeRights & 0x00000008) != 0) { userFeatures.push("No Remote Control"); } else {
  15727. if ((user.removeRights & 0x00010000) != 0) { userFeatures.push("No Desktop"); }
  15728. else if ((user.removeRights & 0x00000100) != 0) { userFeatures.push("Desktop View Only"); }
  15729. if ((user.removeRights & 0x00000200) != 0) { userFeatures.push("No Terminal"); }
  15730. if ((user.removeRights & 0x00000400) != 0) { userFeatures.push("No Files"); }
  15731. }
  15732. if ((user.removeRights & 0x00000010) != 0) { userFeatures.push("No Console"); }
  15733. if ((user.removeRights & 0x00008000) != 0) { userFeatures.push("No Uninstall"); }
  15734. if ((user.removeRights & 0x00020000) != 0) { userFeatures.push("No Remote Command"); }
  15735. if ((user.removeRights & 0x00000040) != 0) { userFeatures.push("No Wake"); }
  15736. if ((user.removeRights & 0x00040000) != 0) { userFeatures.push("No Reset/Off"); }
  15737. }
  15738. userFeatures = userFeatures.join(', ');
  15739. if (userFeatures == '') { userFeatures = '<i>' + "None" + '</i>'; }
  15740. x += addDeviceAttribute("Features", addLink(userFeatures, 'p20edituserfeatures()'));
  15741. x += addDeviceAttribute("Server Rights", '<span style=cursor:pointer onclick=\'return showUserAdminDialog(event,"' + encodeURIComponentEx(user._id) + '")\'>' + premsg + msg.join(', ') + ' <img style=cursor:pointer class=hoverButton src="images/link5.png" /></span>');
  15742. if (user.quota) x += addDeviceAttribute("Server Quota", EscapeHtml(parseInt(user.quota) / 1024) + ' k');
  15743. x += addDeviceAttribute("Creation", printDateTime(new Date(user.creation * 1000)));
  15744. if (user.login) x += addDeviceAttribute("Last Login", printDateTime(new Date(user.login * 1000)));
  15745. if (user.passchange == -1) { x += addDeviceAttribute("Password", "Will be changed on next login."); }
  15746. else if (user.passchange) { x += addDeviceAttribute("Password", format("Last changed: {0}", printDateTime(new Date(user.passchange * 1000)))); }
  15747. // Device Groups
  15748. var linkCount = 0, linkCountStr = '<i>' + "None" + '<i>';
  15749. if (user.links) {
  15750. for (var i in user.links) { if (i.startsWith('mesh/')) { linkCount++; } }
  15751. if (linkCount == 1) { linkCountStr = "1 group"; } else if (linkCount > 1) { linkCountStr = format("{0} groups", linkCount); }
  15752. }
  15753. x += addDeviceAttribute("Device Groups", linkCountStr);
  15754. // Administrative Realms
  15755. if ((userinfo.siteadmin == 0xFFFFFFFF) || (userinfo.siteadmin & 2)) {
  15756. var xuserGroups = '<i>' + "None" + '</i>';
  15757. if (user.groups) { xuserGroups = ''; for (var i in user.groups) { xuserGroups += '<span class="tagSpan">' + EscapeHtml(user.groups[i]) + '</span>'; } }
  15758. x += addDeviceAttribute("Admin Realms", addLinkConditional(xuserGroups, 'showUserGroupDialog(event,"' + encodeURIComponentEx(user._id) + '")', (userinfo.siteadmin == 0xFFFFFFFF) || ((userinfo.groups == null) && (userinfo._id != user._id) && (user.siteadmin != 0xFFFFFFFF))));
  15759. }
  15760. // Display device user consent
  15761. {
  15762. var meshFeatures = [], consent = 0;
  15763. if (user.consent) { consent = user.consent; }
  15764. if (serverinfo.consent) { consent |= serverinfo.consent; }
  15765. if ((consent & 0x0040) && (consent & 0x0008)) { meshFeatures.push("Desktop Prompt+Toolbar"); } else if (consent & 0x0040) { meshFeatures.push("Desktop Toolbar"); } else if (consent & 0x0008) { meshFeatures.push("Desktop Prompt"); } else { if (consent & 0x0001) { meshFeatures.push("Desktop Notify"); } }
  15766. if (consent & 0x0010) { meshFeatures.push("Terminal Prompt"); } else { if (consent & 0x0002) { meshFeatures.push("Terminal Notify"); } }
  15767. if (consent & 0x0020) { meshFeatures.push("Files Prompt"); } else { if (consent & 0x0004) { meshFeatures.push("Files Notify"); } }
  15768. if (consent == 7) { meshFeatures = ["Always Notify"]; }
  15769. if ((consent & 56) == 56) { meshFeatures = ["Always Prompt"]; }
  15770. meshFeatures = meshFeatures.join(', ');
  15771. if (meshFeatures == '') { meshFeatures = '<i>' + "None" + '</i>'; }
  15772. x += addDeviceAttribute("User Consent", addLinkConditional(meshFeatures, 'p20editmeshconsent(2)', true));
  15773. }
  15774. var multiFactor = 0;
  15775. if ((user.otpsecret > 0) || (user.otphkeys > 0) || (user.otpekey > 0) || (user.otpduo > 0)) {
  15776. multiFactor = 1;
  15777. var factors = [];
  15778. if (user.otpsecret > 0) { factors.push("Authentication App"); }
  15779. if (user.otphkeys > 0) { factors.push("Security Key"); }
  15780. if (user.otpekey > 0) { factors.push("Email"); }
  15781. if (user.otpduo > 0) { factors.push("Duo"); }
  15782. if (user.otpkeys > 0) { factors.push("Backup Codes"); }
  15783. if (user.otpdev > 0) { factors.push("Device Push"); }
  15784. if ((user.phone != null) && (features & 0x04000000)) { factors.push("SMS"); }
  15785. if ((user.msghandle != null) && (features2 & 0x04000000)) { factors.push("Messaging"); }
  15786. x += addDeviceAttribute("Security", '<img src="images/key12.png" height=12 width=11 title="' + "2nd factor authentication enabled" + '" style="margin-top:2px" /> ' + factors.join(', '));
  15787. }
  15788. x += '</table></div><br />';
  15789. // Add action buttons
  15790. x += '<input type=button value="' + "Notes" + '" title="' + "View notes about this user" + '" onclick=showNotes(false,"' + encodeURIComponentEx(user._id) + '") />';
  15791. if (user.phone && (features & 0x02000000)) { x += '<input type=button value="' + "SMS" + '" title="' + "Send a SMS message to this user" + '" onclick=showSendSMS("' + encodeURIComponentEx(user._id) + '") />'; }
  15792. if (user.msghandle && (features2 & 0x02000000)) { x += '<input type=button value="' + "Message" + '" title="' + "Send a message to this user" + '" onclick=showSendMessage("' + encodeURIComponentEx(user._id) + '") />'; }
  15793. if ((typeof user.email == 'string') && (user.emailVerified === true) && (features & 0x00000040)) { x += '<input type=button value="' + "Email" + '" title="' + "Send a email message to this user" + '" onclick=showSendEmail("' + encodeURIComponentEx(user._id) + '") />'; }
  15794. if (!self && ((activeSessions > 0) || ((features2 & 8) && (user.webpush)))) {
  15795. x += '<input type=button value="' + "Notify" + '" title="' + "Send user notification" + '" onclick=showUserAlertDialog(event,"' + encodeURIComponentEx(user._id) + '") />';
  15796. x += '<input type=button value="' + "Chat" + '" title="' + "Chat" + '" onclick=userChat(event,"' + encodeURIComponentEx(user._id) + '","' + encodeURIComponentEx(user.name) + '") />';
  15797. if ((activeSessions > 0) && (serverinfo != null) && (serverinfo.altmessenging != null)) {
  15798. for (var i in serverinfo.altmessenging) {
  15799. var am = serverinfo.altmessenging[i];
  15800. if ((am.type == null) || (am.type == 'user')) {
  15801. x += '<input type=button value="' + EscapeHtml(am.name) + '" onclick=altUserChat(event,"' + encodeURIComponentEx(user._id) + '","' + encodeURIComponentEx(user.name) + '",' + i + ') />';
  15802. }
  15803. }
  15804. }
  15805. }
  15806. // Setup the panel
  15807. QH('p30html', x);
  15808. // Draw the user timeline
  15809. drawUserPermissions();
  15810. // Check if we can change password / delete this user
  15811. var userAdminRights = (((userinfo.siteadmin != null) && (userinfo.siteadmin & 2) && (user.siteadmin != 0xFFFFFFFF)) || (userinfo.siteadmin == 0xFFFFFFFF));
  15812. // Show bottom buttons
  15813. x = '<div style=float:right;font-size:small>';
  15814. if (userAdminRights && (userinfo._id != user._id)) { x += '<a href=# style=cursor:pointer onclick=\'return p30showDeleteUserDialog()\' title="' + "Remove this user" + '">' + "Delete User" + '</a>'; }
  15815. x += '</div><div style=font-size:small>';
  15816. // If user admin rights and not SSPI/LDAP and UserID does not start with ~, show change password
  15817. if (userAdminRights && ((features & 0x00080000) == 0) && (user._id.split('/')[2][0] != '~')) {
  15818. x += '<a href=# style=cursor:pointer onclick=\'return p30showUserChangePassDialog(' + multiFactor + ')\' title="' + "Change the password for this user" + '">' + "Change Password" + '</a>';
  15819. x += ' <a href=# style=cursor:pointer onclick=\'return p30viewPreviousLogins()\' title="' + "View previous logins for this user" + '">' + "Previous Logins" + '</a>';
  15820. }
  15821. x += '</div><br>'
  15822. QH('p30html3', x);
  15823. // Update user's connection state
  15824. x = '';
  15825. if (activeSessions == 1) { x = "1 active session"; } else if (activeSessions > 1) { x = format("{0} active sessions", activeSessions); }
  15826. QH('MainUserState', x);
  15827. go(30);
  15828. // Update user events (TODO: do this only if we change users)
  15829. QH('p31events', '');
  15830. refreshUsersEvents();
  15831. // Change the URL
  15832. var urlviewmode = '';
  15833. if (((features & 0x10000000) == 0) && (xxcurrentView >= 30) && (xxcurrentView <= 39) && (currentUser != null)) {
  15834. urlviewmode = '?viewmode=' + xxcurrentView + '&gotouser=' + ((serverinfo.crossDomain)?currentUser._id:currentUser._id.split('/')[2]);
  15835. for (var i in urlargs) { urlviewmode += ('&' + i + '=' + urlargs[i]); }
  15836. try { window.history.replaceState({}, document.title, window.location.pathname + urlviewmode); } catch (ex) { }
  15837. }
  15838. }
  15839. function p30editPhone() {
  15840. if (xxdialogMode) return;
  15841. var x = '<table style=width:100%><tr><td style=width:56px><img src="images/phone80.png" style=padding:8px>';
  15842. x += '<td style=width:100%;text-align:center>' + "SMS capable phone number for this user." + '<br />' + "Leave blank for none.";
  15843. x += '<br /><br /><div style=width:100%;text-align:center>' + "Phone number:" + ' <input type=tel pattern="[0-9]" autocomplete="tel" value="' + (currentUser.phone?currentUser.phone:'') + '" inputmode="tel" maxlength=18 id=d2phoneinput onKeyUp=p30editPhoneValidate() onkeypress="if (event.key==\'Enter\') p30editPhoneValidate(1)"></div></table>';
  15844. setDialogMode(2, "Phone Notifications", 3, p30editPhoneEx, x, 'verifyPhone');
  15845. Q('d2phoneinput').focus();
  15846. p30editPhoneValidate();
  15847. }
  15848. function p30editMessaging() {
  15849. if (xxdialogMode) return;
  15850. var x = '<table style=width:100%><tr><td style=width:56px;vertical-align:top><img src="images/messaging40.png" style=padding:8px>';
  15851. x += '<td style=width:100%>' + "Messaging account for this user.";
  15852. var y = '<select id=d2serviceselect style=width:160px;margin-left:8px onchange=p30editMessagingValidate()><option value=0>' + "None" + '</option>';
  15853. if ((serverinfo.userMsgProviders & 1) != 0) { y += '<option value=1>' + "Telegram" + '</option>'; }
  15854. if ((serverinfo.userMsgProviders & 4) != 0) { y += '<option value=4>' + "Discord" + '</option>'; }
  15855. if ((serverinfo.userMsgProviders & 8) != 0) { y += '<option value=8>' + "XMPP" + '</option>'; }
  15856. if ((serverinfo.userMsgProviders & 16) != 0) { y += '<option value=16>' + "CallMeBot" + '</option>'; }
  15857. if ((serverinfo.userMsgProviders & 32) != 0) { y += '<option value=32>' + "Pushover" + '</option>'; }
  15858. if ((serverinfo.userMsgProviders & 64) != 0) { y += '<option value=64>' + "ntfy" + '</option>'; }
  15859. if ((serverinfo.userMsgProviders & 128) != 0) { y += '<option value=128>' + "Zulip" + '</option>'; }
  15860. if ((serverinfo.userMsgProviders & 256) != 0) { y += '<option value=256>' + "Slack" + '</option>'; }
  15861. y += '</select>';
  15862. x += '<table style=margin-top:12px><tr><td>' + "Service" + '<td>' + y;
  15863. x += '<tr><td>' + "Handle" + '<td><input maxlength=1024 style=width:160px;margin-left:8px id=d2handleinput onKeyUp=p30editMessagingValidate() onkeypress="if (event.key==\'Enter\') p30editMessagingValidate(1)">';
  15864. x += '</table>';
  15865. if (serverinfo.discordUrl) { x += '<div id=d2discordurl style=display:none><br /><a href=' + serverinfo.discordUrl + ' target="_discord">' + "Join this Discord server to receive notifications." + '</a></div>' }
  15866. x += '<div id=d2callmebotinfo style=display:none><br /><a href=https://www.callmebot.com/blog/free-api-signal-send-messages/ target="_callmebot">' + "Signal" + '</a>, <a href=https://www.callmebot.com/blog/free-api-whatsapp-messages/ target="_callmebot">' + "Whatsapp" + '</a>, <a href=https://www.callmebot.com/blog/free-api-facebook-messenger/ target="_callmebot">' + "Facebook" + '</a>, <a href=https://www.callmebot.com/blog/telegram-text-messages/ target="_callmebot">' + "Telegram" + '</a></div>';
  15867. x += '<div id=d2pushoverinfo style=display:none><br /><a href=https://pushover.net/ target="_pushover">' + "Information at Pushover.net" + '</a></div>';
  15868. x += '<div id=d2ntfyinfo style=display:none><br /><a href="' + (serverinfo.userMsgNftyUrl ? serverinfo.userMsgNftyUrl : 'https://ntfy.sh/') + '" target="_ntfy">' + "Free service at ntfy.sh" + '</a></div>';
  15869. x += '<div id=d2slackinfo style=display:none><br /><a href=https://api.slack.com/messaging/webhooks target="_slack">' + "Slack Webhook Setup" + '</a></div>';
  15870. setDialogMode(2, "Messaging Notifications", 3, p30editMessagingEx, x, 'verifyMessaging');
  15871. Q('d2handleinput').focus();
  15872. if (currentUser.msghandle) {
  15873. if (currentUser.msghandle.startsWith('telegram:') && ((serverinfo.userMsgProviders & 1) != 0)) { Q('d2serviceselect').value = 1; Q('d2handleinput').value = currentUser.msghandle.substring(10); }
  15874. if (currentUser.msghandle.startsWith('discord:') && ((serverinfo.userMsgProviders & 4) != 0)) { Q('d2serviceselect').value = 4; Q('d2handleinput').value = currentUser.msghandle.substring(8); }
  15875. if (currentUser.msghandle.startsWith('xmpp:') && ((serverinfo.userMsgProviders & 8) != 0)) { Q('d2serviceselect').value = 8; Q('d2handleinput').value = currentUser.msghandle.substring(5); }
  15876. if (currentUser.msghandle.startsWith('callmebot:') && ((serverinfo.userMsgProviders & 16) != 0)) {
  15877. Q('d2serviceselect').value = 16;
  15878. var toData = currentUser.msghandle.substring(10).split('|');
  15879. if ((toData[0] == 'signal') && (toData.length == 3)) { Q('d2handleinput').value = 'https://api.callmebot.com/signal/send.php?phone=' + decodeURIComponent(toData[1]) + '&apikey=' + decodeURIComponent(toData[2]); }
  15880. if ((toData[0] == 'whatsapp') && (toData.length == 3)) { Q('d2handleinput').value = 'https://api.callmebot.com/whatsapp.php?phone=' + decodeURIComponent(toData[1]) + '&apikey=' + decodeURIComponent(toData[2]); }
  15881. if ((toData[0] == 'facebook') && (toData.length == 2)) { Q('d2handleinput').value = 'https://api.callmebot.com/facebook/send.php?apikey=' + decodeURIComponent(toData[1]); }
  15882. }
  15883. if (currentUser.msghandle.startsWith('pushover:') && ((serverinfo.userMsgProviders & 32) != 0)) { Q('d2serviceselect').value = 32; Q('d2handleinput').value = currentUser.msghandle.substring(9); }
  15884. if (currentUser.msghandle.startsWith('ntfy:') && ((serverinfo.userMsgProviders & 64) != 0)) { Q('d2serviceselect').value = 64; Q('d2handleinput').value = currentUser.msghandle.substring(5); }
  15885. if (currentUser.msghandle.startsWith('zulip:') && ((serverinfo.userMsgProviders & 128) != 0)) { Q('d2serviceselect').value = 128; Q('d2handleinput').value = currentUser.msghandle.substring(6); }
  15886. if (currentUser.msghandle.startsWith('slack:') && ((serverinfo.userMsgProviders & 256) != 0)) { Q('d2serviceselect').value = 256; Q('d2handleinput').value = currentUser.msghandle.substring(6); }
  15887. }
  15888. p30editMessagingValidate();
  15889. }
  15890. function p30editMessagingValidate(x) {
  15891. QE('d2handleinput', Q('d2serviceselect').value != 0);
  15892. if (serverinfo.discordUrl) { QV('d2discordurl', Q('d2serviceselect').value == 4); }
  15893. QV('d2callmebotinfo', Q('d2serviceselect').value == 16);
  15894. QV('d2pushoverinfo', Q('d2serviceselect').value == 32);
  15895. QV('d2ntfyinfo', Q('d2serviceselect').value == 64);
  15896. QV('d2slackinfo', Q('d2serviceselect').value == 256);
  15897. if (Q('d2serviceselect').value == 0) { Q('d2handleinput')['placeholder'] = ''; }
  15898. else if (Q('d2serviceselect').value == 4) { Q('d2handleinput')['placeholder'] = "Username:0000"; }
  15899. else if (Q('d2serviceselect').value == 8) { Q('d2handleinput')['placeholder'] = "[email protected]"; }
  15900. else if (Q('d2serviceselect').value == 16) { Q('d2handleinput')['placeholder'] = "https://api.callmebot.com/..."; }
  15901. else if (Q('d2serviceselect').value == 32) { Q('d2handleinput')['placeholder'] = "User key"; }
  15902. else if (Q('d2serviceselect').value == 64) { Q('d2handleinput')['placeholder'] = "Topic"; }
  15903. else if (Q('d2serviceselect').value == 128) { Q('d2handleinput')['placeholder'] = "[email protected]"; }
  15904. else if (Q('d2serviceselect').value == 256) { Q('d2handleinput')['placeholder'] = "https://hooks.slack.com/..."; }
  15905. else { Q('d2handleinput')['placeholder'] = "Username"; }
  15906. if (x == 1) { dialogclose(1); }
  15907. }
  15908. // Send to the server the user's messaging account
  15909. function p30editMessagingEx() {
  15910. var handle = null;
  15911. if ((Q('d2handleinput').value == '') || (Q('d2serviceselect').value == 0)) { handle = ''; }
  15912. else if (Q('d2serviceselect').value == 1) { handle = 'telegram:@' + Q('d2handleinput').value; }
  15913. else if (Q('d2serviceselect').value == 4) { handle = 'discord:' + Q('d2handleinput').value; }
  15914. else if (Q('d2serviceselect').value == 8) { handle = 'xmpp:' + Q('d2handleinput').value; }
  15915. else if (Q('d2serviceselect').value == 16) { handle = 'callmebot:' + Q('d2handleinput').value; }
  15916. else if (Q('d2serviceselect').value == 32) { handle = 'pushover:' + Q('d2handleinput').value; }
  15917. else if (Q('d2serviceselect').value == 64) { handle = 'ntfy:' + Q('d2handleinput').value; }
  15918. else if (Q('d2serviceselect').value == 128) { handle = 'zulip:' + Q('d2handleinput').value; }
  15919. else if (Q('d2serviceselect').value == 256) { handle = 'slack:' + Q('d2handleinput').value; }
  15920. if (handle != null) { meshserver.send({ action: 'edituser', id: currentUser._id, msghandle: handle }); }
  15921. }
  15922. function p20edituserfeatures() {
  15923. if (xxdialogMode) return;
  15924. var flags = (currentUser.flags)?currentUser.flags:0, x = ''; // Flags: 1 = Account Image, 2 = Session Recording
  15925. var removeRights = (currentUser.removeRights)?currentUser.removeRights:0, x = ''; // Remove Device Group Rights
  15926. if (serverinfo.usersSessionRecording == 1) {
  15927. x += '<div><label><input type=checkbox id=d20flag1 onchange=p20edituserfeaturesValidate() ' + ((flags & 2) ? 'checked' : '') + '>' + "Record sessions" + '</label><br></div>';
  15928. }
  15929. x += '<div><label><input type=checkbox id=d20flag7 onchange=p20edituserfeaturesValidate() ' + ((removeRights & 0x00000008) ? 'checked' : '') + '>' + "No Remote Control" + '</label><br></div>';
  15930. x += '<div style=margin-left:8px><label><input type=checkbox id=d20flag2 onchange=p20edituserfeaturesValidate() ' + ((removeRights & 0x00010000) ? 'checked' : '') + '>' + "No Desktop Access" + '</label><br></div>';
  15931. x += '<div style=margin-left:16px><label><input type=checkbox id=d20flag3 onchange=p20edituserfeaturesValidate() ' + ((removeRights & 0x00000100) ? 'checked' : '') + '>' + "Remote View Only" + '</label><br></div>';
  15932. x += '<div style=margin-left:8px><label><input type=checkbox id=d20flag4 onchange=p20edituserfeaturesValidate() ' + ((removeRights & 0x00000200) ? 'checked' : '') + '>' + "No Terminal Access" + '</label><br></div>';
  15933. x += '<div style=margin-left:8px><label><input type=checkbox id=d20flag5 onchange=p20edituserfeaturesValidate() ' + ((removeRights & 0x00000400) ? 'checked' : '') + '>' + "No File Access" + '</label><br></div>';
  15934. x += '<div><label><input type=checkbox id=d20flag6 onchange=p20edituserfeaturesValidate() ' + ((removeRights & 0x00000010) ? 'checked' : '') + '>' + "No Agent Console" + '</label><br></div>';
  15935. x += '<div><label><input type=checkbox id=d20flag8 onchange=p20edituserfeaturesValidate() ' + ((removeRights & 0x00008000) ? 'checked' : '') + '>' + "No Uninstall" + '</label><br></div>';
  15936. x += '<div><label><input type=checkbox id=d20flag9 onchange=p20edituserfeaturesValidate() ' + ((removeRights & 0x00020000) ? 'checked' : '') + '>' + "No Remote Command" + '</label><br></div>';
  15937. x += '<div><label><input type=checkbox id=d20flag10 onchange=p20edituserfeaturesValidate() ' + ((removeRights & 0x00000040) ? 'checked' : '') + '>' + "No Wake" + '</label><br></div>';
  15938. x += '<div><label><input type=checkbox id=d20flag11 onchange=p20edituserfeaturesValidate() ' + ((removeRights & 0x00040000) ? 'checked' : '') + '>' + "No Reset/Off" + '</label><br></div>';
  15939. setDialogMode(2, "Edit User Features", 3, p20edituserfeaturesEx, x);
  15940. p20edituserfeaturesValidate();
  15941. }
  15942. function p20edituserfeaturesValidate() {
  15943. QE('d20flag2', !Q('d20flag7').checked);
  15944. QE('d20flag3', !Q('d20flag7').checked && !Q('d20flag2').checked);
  15945. QE('d20flag4', !Q('d20flag7').checked);
  15946. QE('d20flag5', !Q('d20flag7').checked);
  15947. }
  15948. // Send to the server the new user's real name
  15949. function p20edituserfeaturesEx() {
  15950. // Setup user flags
  15951. var flags = (currentUser.flags)?currentUser.flags:0; // Flags: 1 = Account Image, 2 = Session Recording
  15952. var f = flags & 1;
  15953. if ((serverinfo.usersSessionRecording == 1) && Q('d20flag1').checked) { f += 2; }
  15954. // Setup user permission removal
  15955. var r = 0;
  15956. if (Q('d20flag7').checked) { r += 0x00000008; } else {
  15957. if (Q('d20flag2').checked) { r += 0x00010000; }
  15958. else if (Q('d20flag3').checked) { r += 0x00000100; }
  15959. if (Q('d20flag4').checked) { r += 0x00000200; }
  15960. if (Q('d20flag5').checked) { r += 0x00000400; }
  15961. }
  15962. if (Q('d20flag6').checked) { r += 0x00000010; }
  15963. if (Q('d20flag8').checked) { r += 0x00008000; }
  15964. if (Q('d20flag9').checked) { r += 0x00020000; }
  15965. if (Q('d20flag10').checked) { r += 0x00000040; }
  15966. if (Q('d20flag11').checked) { r += 0x00040000; }
  15967. meshserver.send({ action: 'edituser', id: currentUser._id, flags: f, removeRights: r });
  15968. }
  15969. function p30editPhoneValidate(x) {
  15970. var ok = (Q('d2phoneinput').value == '') || (isPhoneNumber(Q('d2phoneinput').value));
  15971. QE('idx_dlgOkButton', ok);
  15972. if ((x == 1) && ok) { dialogclose(1); }
  15973. }
  15974. // Send to the server the user's new phone number
  15975. function p30editPhoneEx() { meshserver.send({ action: 'edituser', id: currentUser._id, phone: Q('d2phoneinput').value }); }
  15976. // Display the user's real name change dialog box
  15977. function p30showUserRealNameChangeDialog(event) {
  15978. if (xxdialogMode) return false;
  15979. var x = '';
  15980. x += addHtmlValue("Real Name", '<input id=dp30realname style=width:230px maxlength=256 />');
  15981. setDialogMode(2, format("Change Real Name for {0}", EscapeHtml(currentUser.name)), 3, p30showUserRealNameChangeDialogEx, x);
  15982. Q('dp30realname').focus();
  15983. Q('dp30realname').value = (currentUser.realname?currentUser.realname:'');
  15984. return false;
  15985. }
  15986. // Send to the server the new user's real name
  15987. function p30showUserRealNameChangeDialogEx() {
  15988. meshserver.send({ action: 'edituser', id: currentUser._id, realname: Q('dp30realname').value });
  15989. }
  15990. // Display the user's email change dialog box
  15991. function p30showUserEmailChangeDialog(event) {
  15992. if (xxdialogMode) return false;
  15993. var x = '';
  15994. x += addHtmlValue("Email", '<input id=dp30email style=width:230px maxlength=32 onchange=p30validateEmail() onkeyup=p30validateEmail() />');
  15995. if (serverinfo.emailcheck) { x += addHtmlValue("Status", '<select id=dp30verified style=width:230px onchange=p30validateEmail()><option value=0>' + "Not verified" + '</option><option value=1>' + "Verified" + '</option></select>'); }
  15996. setDialogMode(2, format("Change Email for {0}", EscapeHtml(currentUser.name)), 3, p30showUserEmailChangeDialogEx, x);
  15997. Q('dp30email').focus();
  15998. Q('dp30email').value = (currentUser.email?currentUser.email:'');
  15999. if (serverinfo.emailcheck) { Q('dp30verified').value = currentUser.emailVerified?1:0; }
  16000. p30validateEmail();
  16001. return false;
  16002. }
  16003. // Perform validation on the user's email change dialog box
  16004. function p30validateEmail() {
  16005. var v = Q('dp30email').value, x = v.split('@');
  16006. x = (v.length == 0) || ((x.length == 2) && (x[0].length > 0) && (x[1].split('.').length > 1) && (x[1].length > 2) && (v.length < 1024) && ((v != userinfo.email) || ((serverinfo.emailcheck == true) && (Q('dp30verified').value != (userinfo.emailVerified?1:0)))));
  16007. QE('idx_dlgOkButton', x);
  16008. }
  16009. // Send to the server the new user's email address and validation status
  16010. function p30showUserEmailChangeDialogEx() {
  16011. var x = { action: 'edituser', id: currentUser._id, email: Q('dp30email').value };
  16012. if (serverinfo.emailcheck) { x.emailVerified = (Q('dp30verified').value == 1); }
  16013. meshserver.send(x);
  16014. }
  16015. // Display the previous logins of this user
  16016. function p30viewPreviousLogins() {
  16017. if (xxdialogMode) return;
  16018. setDialogMode(2, "Previous Logins", 1, null, "Loading...", 'previousLogins:' + currentUser._id);
  16019. meshserver.send({ action: 'previousLogins', userid: currentUser._id });
  16020. }
  16021. // Display the user's password change dialog box
  16022. function p30showUserChangePassDialog(multiFactor) {
  16023. if (xxdialogMode) return;
  16024. var x = '';
  16025. x += addHtmlValue("Password", '<input id=p4pass1 type=password style=width:230px maxlength=256 onchange=p30showUserChangePassDialogValidate(1) onkeyup=p30showUserChangePassDialogValidate(1)></input>');
  16026. x += addHtmlValue("Password", '<input id=p4pass2 type=password style=width:230px maxlength=256 onchange=p30showUserChangePassDialogValidate(1) onkeyup=p30showUserChangePassDialogValidate(1)></input>');
  16027. if (features & 0x00010000) { x += addHtmlValue("Password hint", '<input id=p4hint type=text style=width:230px maxlength=256></input>'); }
  16028. if (passRequirements) {
  16029. var r = [], rc = 0;
  16030. for (var i in passRequirements) { if ((i != 'reset') && (i != 'hint')) { r.push(i + ':' + passRequirements[i]); rc++; } }
  16031. if (rc > 0) { x += '<div style=font-size:x-small;padding:6px>' + format("Requirements: {0}.", r.join(', ')) + '</div>'; }
  16032. }
  16033. x += '<div><label><input id=p4resetNextLogin type=checkbox />' + "Force password reset on next login." + '</label></div>';
  16034. if (multiFactor == 1) { x += '<div><label><input id=p4twoFactorRemove type=checkbox />' + "Remove all 2nd factor authentication." + '</label></div>'; }
  16035. setDialogMode(2, format("Change Password for {0}", EscapeHtml(currentUser.name)), 3, p30showUserChangePassDialogEx, x, multiFactor);
  16036. p30showUserChangePassDialogValidate();
  16037. Q('p4pass1').focus();
  16038. if (currentUser.passchange == -1) { Q('p4resetNextLogin').checked = true; }
  16039. }
  16040. function p30showUserChangePassDialogValidate() {
  16041. var ok = true;
  16042. if ((Q('p4pass1').value != '') || (Q('p4pass2').value != '')) {
  16043. if (Q('p4pass1').value != Q('p4pass2').value) { ok = false; } else {
  16044. if (passRequirements) { if (checkPasswordRequirements(Q('p4pass1').value, passRequirements) == false) { ok = false; } }
  16045. }
  16046. }
  16047. QE('idx_dlgOkButton', ok);
  16048. }
  16049. function p30showUserChangePassDialogEx(b, tag) {
  16050. var removeMultiFactor = false;
  16051. if ((tag == 1) && (Q('p4twoFactorRemove').checked == true)) { removeMultiFactor = true; }
  16052. if (Q('p4pass1').value == Q('p4pass2').value) {
  16053. var r = { action: 'changeuserpass', userid: currentUser._id, pass: Q('p4pass1').value, removeMultiFactor: removeMultiFactor, resetNextLogin: Q('p4resetNextLogin').checked };
  16054. if (features & 0x00010000) { r.hint = Q('p4hint').value; }
  16055. meshserver.send(r);
  16056. }
  16057. }
  16058. function p30showDeleteUserDialog() {
  16059. if (xxdialogMode) return;
  16060. setDialogMode(2, format("Delete User {0}", EscapeHtml(currentUser.name)), 3, p30showDeleteUserDialogEx, format("Confirm deletion of user {0}?", EscapeHtml(currentUser.name)) + '<br /><br /><label><input id=p10check type=checkbox onchange=p10validateDeleteNodeDialog() />' + "Confirm" + '</label>');
  16061. p10validateDeleteNodeDialog();
  16062. }
  16063. function p30showDeleteUserDialogEx() {
  16064. meshserver.send({ action: 'deleteuser', userid: currentUser._id, username: currentUser.name });
  16065. }
  16066. // Draw device power bars. The bars are 766px wide.
  16067. function drawUserPermissions() {
  16068. var count = 1, x = '';
  16069. // Display common device groups
  16070. var deviceGroupCount = 0, newDeviceGroup = false;
  16071. for (var i in meshes) { if (meshes[i]._id.split('/')[1] != currentUser._id.split('/')[1]) continue; deviceGroupCount++; if ((currentUser.links == null) || (currentUser.links[i] == null)) { newDeviceGroup = true; } }
  16072. if ((deviceGroupCount > 0) && (newDeviceGroup)) { x += '<a href=# onclick="return p20showAddMeshUserDialog(1)" style=cursor:pointer;margin-right:10px><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add Device Group" + '</a>'; }
  16073. 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>' + "Common Device Groups" + '</th><th scope=col style=text-align:left></th></tr>';
  16074. if (currentUser.links) {
  16075. var omeshes = [];
  16076. for (var i in currentUser.links) { if (i.startsWith('mesh/')) { if (meshes[i] != null) { omeshes.push(meshes[i]); } } }
  16077. omeshes = getOrderedList(omeshes, 'name');
  16078. for (var i in omeshes) {
  16079. var cr = 0, mesh = omeshes[i], r = currentUser.links[mesh._id].rights, trash = '', rights = makeDeviceGroupRightsString(r);
  16080. if (mesh == null) { continue; }
  16081. if ((userinfo.links) && (userinfo.links[mesh._id] != null) && (userinfo.links[mesh._id].rights != null)) { cr = userinfo.links[mesh._id].rights; }
  16082. var meshname = '<i>' + "Unknown Device Group" + '</i>';
  16083. if (mesh) { meshname = '<a href=# onclick=\'gotoMesh("' + mesh._id + '");haltEvent(event);\'>' + EscapeHtml(mesh.name) + '</a>'; }
  16084. if ((currentUser._id != userinfo._id) && ((cr & 2) != 0)) { // 2 = MESHRIGHT_MANAGEUSERS
  16085. trash = '<a href=# onclick=\'return p30removeMeshFromUser(event,"' + encodeURIComponentEx(mesh._id) + '")\' title="' + "Remove user rights to this device group" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>';
  16086. rights = '<span style=cursor:pointer onclick=p20showAddMeshUserDialog(1,"' + encodeURIComponentEx(mesh._id) + '")>' + rights + ' <img class=hoverButton style=cursor:pointer src=images/link5.png></span>';
  16087. }
  16088. x += '<tr ' + (((++count % 2) == 0) ? 'style=background-color:#DDD' : '') + '><td style=width:30%><div title="' + "Device Group" + '" class=m99></div><div>&nbsp;' + meshname + '<div></div></div></td><td style=width:70%><div style=float:right>' + trash + '</div><div>' + rights + '</div></td></tr>';
  16089. }
  16090. }
  16091. if (count == 1) { x += '<tr><td><div style=padding:6px>&nbsp;<i>' + "No device groups in common" + '</i><div></div></div></td><td></td></tr>'; }
  16092. x += '</tbody></table>';
  16093. // Display user groups
  16094. if (usergroups != null) {
  16095. count = 1;
  16096. x += '<br />';
  16097. if ((userinfo.siteadmin & 256) != 0) {
  16098. var userGroupCount = 0, newUserGroup = false;
  16099. for (var i in usergroups) {
  16100. if ((usergroups[i].membershipType != null) || (usergroups[i]._id.split('/')[1] != currentUser._id.split('/')[1])) continue;
  16101. userGroupCount++;
  16102. if ((currentUser.links == null) || (currentUser.links[i] == null)) { newUserGroup = true; }
  16103. }
  16104. if ((userGroupCount > 0) && (newUserGroup)) { x += '<a href=# onclick="return p30showAddUserGroupDialog()" style=cursor:pointer;margin-right:10px><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add User Group" + '</a>'; }
  16105. }
  16106. 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 Group Memberships" + '</th><th scope=col style=text-align:left></th></tr>';
  16107. if (currentUser.links) {
  16108. var ougroups = [];
  16109. for (var i in currentUser.links) { if (i.startsWith('ugrp/')) { if (usergroups[i] != null) { ougroups.push(usergroups[i]); } } }
  16110. ougroups = getOrderedList(ougroups, 'name');
  16111. for (var i in ougroups) {
  16112. var group = ougroups[i], r = currentUser.links[ougroups[i]._id].rights, trash = '';
  16113. var groupname = '<i>' + "Unknown User Group" + '</i>';
  16114. if (group != null) {
  16115. groupname = EscapeHtml(group.name);
  16116. if (usergroups != null) { groupname = '<a href=# onclick=\'gotoUserGroup("' + encodeURIComponentEx(ougroups[i]._id) + '");haltEvent(event);\'>' + groupname + '</a>'; }
  16117. }
  16118. if ((group.membershipType == null) && ((userinfo.siteadmin & 256) != 0)) { trash = '<a href=# onclick=\'return p30RemoveUserGroup(event,"' + encodeURIComponentEx(ougroups[i]._id) + '")\' title="' + "Remove user group membership" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>'; }
  16119. x += '<tr ' + (((++count % 2) == 0) ? 'style=background-color:#DDD' : '') + '><td><div title="' + "User Group" + '" class=m4></div><div>&nbsp;' + groupname + '<div></div></div></td><td><div style=float:right>' + trash + '</div></td></tr>';
  16120. }
  16121. }
  16122. if (count == 1) { x += '<tr><td><div style=padding:6px>&nbsp;<i>' + "No user group memberships" + '</i><div></div></div></td><td></td></tr>'; }
  16123. x += '</tbody></table>';
  16124. }
  16125. // Display common devices
  16126. count = 1;
  16127. x += '<br />';
  16128. if (currentUser._id.split('/')[1] == userinfo._id.split('/')[1]) { x += '<a href=# onclick="return p20showAddMeshUserDialog(4)" style=cursor:pointer;margin-right:10px><img src=images/icon-addnew.png border=0 height=12 width=12> ' + "Add Device" + '</a>'; }
  16129. 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>' + "Common Devices" + '</th><th scope=col style=text-align:left></th></tr>';
  16130. if (currentUser.links) {
  16131. // Sort the list of devices to display
  16132. var nodelist = [];
  16133. for (var i in currentUser.links) { if (i.startsWith('node/')) { var node = getNodeFromId(i); if (node != null) { nodelist.push(node); } } }
  16134. nodelist.sort(nameSort);
  16135. for (var i in nodelist) {
  16136. var node = nodelist[i], r = currentUser.links[node._id].rights, trash = '', cr = GetNodeRights(node);
  16137. if ((userinfo.links) && (userinfo.links[i] != null) && (userinfo.links[i].rights != null)) { cr = userinfo.links[i].rights; }
  16138. var nodename = node?EscapeHtml(node.name):('<i>' + "Unknown Device" + '</i>');
  16139. if ((cr & 2) != 0) {
  16140. trash = '<a href=# onclick=\'return p30removeNodeFromUser(event,"' + encodeURIComponentEx(node._id) + '")\' title="' + "Remove user rights to this device group" + '" style=cursor:pointer><img src=images/trash.png border=0 height=10 width=10></a>';
  16141. rights = '<span style=cursor:pointer onclick=p20showAddMeshUserDialog(4,"' + encodeURIComponentEx(node._id) + '")>' + makeUserDeviceRightsString(r) + ' <img class=hoverButton style=cursor:pointer src=images/link5.png></span>';
  16142. }
  16143. nodename = '<a href=# onclick=\'gotoDevice("' + node._id + '",10);haltEvent(event);\'>' + nodename + '</a>';
  16144. x += '<tr ' + (((++count % 2) == 0) ? 'style=background-color:#DDD' : '') + '><td style=width:30%><div title="' + "Device" + '" class=si' + node.icon + '></div><div>&nbsp;' + nodename + '<div></div></div></td><td style=width:70%><div style=float:right>' + trash + '</div><div>' + rights + '</div></td></tr>';
  16145. }
  16146. }
  16147. if (count == 1) { x += '<tr><td><div style=padding:6px>&nbsp;<i>' + "No devices in common" + '</i><div></div></div></td><td></td></tr>'; }
  16148. x += '</tbody></table>';
  16149. QH('p30html2', x);
  16150. }
  16151. function p30removeDeviceSharing(event, nodeid, publicid, guestname) {
  16152. if (xxdialogMode) return;
  16153. setDialogMode(2, "Remove Device Share", 3, function(b, tag) { meshserver.send({ action: 'removeDeviceShare', nodeid: tag[0], publicid: tag[1] }); }, format("Confirm removal of device share \"{0}\"?", decodeURIComponent(guestname)), [ decodeURIComponent(nodeid), decodeURIComponent(publicid) ]);
  16154. }
  16155. function p30removeNodeFromUser(event, nodeid) {
  16156. if (xxdialogMode) return;
  16157. var node = getNodeFromId(decodeURIComponent(nodeid));
  16158. setDialogMode(2, "Remove Device Permissions", 3, function(b, node) { meshserver.send({ action: 'adddeviceuser', nodeid: node._id, nodename: node.name, userids: [ currentUser._id ], rights: 0, remove: true }); }, format("Confirm removal of access rights for device \"{0}\"?", node.name), node);
  16159. }
  16160. function p30removeUserFromNode(event, userid) {
  16161. if (xxdialogMode) return;
  16162. var user = null, name = '';
  16163. userid = decodeURIComponent(userid);
  16164. if (userid.startsWith('user/')) {
  16165. if (users) { user = users[userid]; if (user != null) { name = user.name; } }
  16166. setDialogMode(2, "Remove User Permissions", 3, function(b, user) { meshserver.send({ action: 'adddeviceuser', nodeid: currentNode._id, nodename: currentNode.name, userids: [ userid ], rights: 0, remove: true }); }, name?format("Confirm removal of access rights for user \"{0}\"?", name):"Confirm removal of access rights?", user);
  16167. } else if (userid.startsWith('ugrp/')) {
  16168. if (usergroups) { user = usergroups[userid]; if (user != null) { name = user.name; } }
  16169. setDialogMode(2, "Remove User Group Permissions", 3, function(b, user) { meshserver.send({ action: 'adddeviceuser', nodeid: currentNode._id, nodename: currentNode.name, userids: [ userid ], rights: 0, remove: true }); }, name?format("Confirm removal of access rights for user group \"{0}\"?", name):"Confirm removal of access rights?", user);
  16170. }
  16171. }
  16172. function p30RemoveUserGroup(button, ugrpid) {
  16173. if (xxdialogMode || (usergroups == null)) return;
  16174. var groupid = decodeURIComponent(ugrpid), group = usergroups[groupid];
  16175. var name = (group != null)?EscapeHtml(group.name):('<i>' + "Unknown" + '</i>');
  16176. setDialogMode(2, "Remove User Group Membership", 3, p30RemoveUserGroupEx, format("Confirm membership removal of user group \"{0}\"?", name), groupid);
  16177. }
  16178. function p30RemoveUserGroupEx(b, groupid) {
  16179. meshserver.send({ action: 'removeuserfromusergroup', ugrpid: groupid, userid: currentUser._id });
  16180. }
  16181. function p30showAddUserGroupDialog() {
  16182. if (xxdialogMode || (usergroups == null)) return;
  16183. var y = '';
  16184. for (var i in usergroups) {
  16185. if ((usergroups[i].membershipType != null) || (usergroups[i]._id.split('/')[1] != currentUser._id.split('/')[1])) continue;
  16186. if ((currentUser.links == null) || (currentUser.links[i] == null)) { y += '<option value=' + encodeURIComponentEx(i) + '>' + EscapeHtml(usergroups[i].name) + '</option>'; }
  16187. }
  16188. var x = addHtmlValue("User Group", '<div style=width:230px;margin:0;padding:0><select id=dp2groupid style=width:100%>' + y + '</select></div>');
  16189. setDialogMode(2, "Add Membership", 3, p30showAddUserGroupDialogEx, x);
  16190. Q('dp2groupid').focus();
  16191. return false;
  16192. }
  16193. function p30showAddUserGroupDialogEx() {
  16194. meshserver.send({ action: 'addusertousergroup', ugrpid: decodeURIComponent(Q('dp2groupid').value), usernames: [ currentUser._id.split('/')[2] ] });
  16195. }
  16196. function p30removeMeshFromUser(e, meshid) {
  16197. if (xxdialogMode) return;
  16198. var mesh = meshes[decodeURIComponent(meshid)];
  16199. if (mesh == null) return;
  16200. setDialogMode(2, "Remove Device Group Permissions", 3, p30removeMeshFromUserEx, format("Confirm removal of access rights for device group \"{0}\"?", mesh.name), mesh._id);
  16201. }
  16202. function p30removeMeshFromUserEx(b, meshid) {
  16203. meshserver.send({ action: 'removemeshuser', meshid: meshid, userid: currentUser._id });
  16204. }
  16205. //
  16206. // MY USER EVENTS
  16207. //
  16208. var currentUserEvents = null;
  16209. function userEventsUpdate() {
  16210. if (currentUser == null) return;
  16211. var x = '', dateHeader = null;
  16212. for (var i in currentUserEvents) {
  16213. var event = currentUserEvents[i], time = new Date(event.time);
  16214. if (event.msg) {
  16215. if (event.h == null) { event.h = Math.random(); }
  16216. if (printDate(time) != dateHeader) {
  16217. if (dateHeader != null) x += '</table>';
  16218. dateHeader = printDate(time);
  16219. x += '<table class=p3eventsTable cellpadding=0 cellspacing=0><tr><td colspan=4 class=DevSt>' + dateHeader + '</td></tr>';
  16220. }
  16221. var icon = 'si3';
  16222. if (event.etype == 'user') icon = 'm2';
  16223. if (event.etype == 'server') icon = 'si3';
  16224. var msg;
  16225. if ((event.msgid == null) || (eventsMessageId[event.msgid] == null)) {
  16226. if (typeof event.msg == 'string') { msg = EscapeHtml(event.msg).split('(R)').join('&reg;'); } else { msg = ''; }
  16227. } else {
  16228. msg = eventsMessageId[event.msgid];
  16229. for (var i in event.msgArgs) {
  16230. var xx = event.msgArgs[i];
  16231. if ((typeof xx == 'string') && (xx.indexOf('DATETIME:') == 0)) { xx = printFlexDateTime(new Date(parseInt(xx.substring(9)))); }
  16232. msg = msg.split('{' + i + '}').join(xx);
  16233. }
  16234. //if (event.msgArgs != null) { for (var i in event.msgArgs) { msg = msg.split('{' + i + '}').join(event.msgArgs[i]); } }
  16235. msg = EscapeHtml(msg).split('(R)').join('&reg;');
  16236. }
  16237. if (event.nodeid) {
  16238. var node = getNodeFromId(event.nodeid);
  16239. if (node != null) {
  16240. icon = 'si' + node.icon;
  16241. msg = '<a href=# onclick=\'gotoDevice("' + event.nodeid + '",10);haltEvent(event);\'>' + EscapeHtml(node.name) + '</a> &rarr; ' + msg;
  16242. }
  16243. }
  16244. if (event.username && (event.username != currentUser.name)) {
  16245. var guestname = '';
  16246. if (event.guestname) { guestname = ' / <span title="' + "This is a guest sharing session" + '">' + EscapeHtml(event.guestname) + '</span>'; }
  16247. if ((userinfo.siteadmin & 2) && (event.userid)) {
  16248. msg = '<a href=# onclick=\'gotoUser("' + encodeURIComponentEx(event.userid) + '");haltEvent(event);\'>' + EscapeHtml(event.username) + '</a>' + guestname + ' &rarr; ' + msg;
  16249. } else {
  16250. msg = EscapeHtml(event.guestname) + ' &rarr; ' + msg;
  16251. }
  16252. } else if (event.guestname) {
  16253. msg = '<span title="' + "This is a guest sharing session" + '">' + EscapeHtml(event.guestname) + '</span> &rarr; ' + msg;
  16254. }
  16255. if (event.etype == 'relay' || event.action == 'relaylog') icon = 'relayIcon16';
  16256. x += '<tr onclick=showEventDetails(' + event.h + ',3) onmouseover=eventMouseHover(this,1) onmouseout=eventMouseHover(this,0) style=cursor:pointer><td style=width:18px><div class=' + icon + '></div></td><td class=g1>&nbsp;</td><td class=style10>' + printTime(time) + ' - ' + msg + '</td><td class=g2>&nbsp;</td></tr><tr style=height:2px></tr>';
  16257. }
  16258. }
  16259. if (dateHeader != null) x += '</table>';
  16260. if (x == '') x = '<br><i>' + "No Events Found" + '</i><br><br>';
  16261. QH('p31events', x);
  16262. }
  16263. function refreshUsersEvents() {
  16264. if (p31filterevents.value != "") {
  16265. meshserver.send({ action: 'events', limit: parseInt(p31limitdropdown.value), userid: currentUser._id, filter: p31filterevents.value });
  16266. } else {
  16267. meshserver.send({ action: 'events', limit: parseInt(p31limitdropdown.value), userid: currentUser._id });
  16268. }
  16269. }
  16270. //
  16271. // Recordings
  16272. //
  16273. var p52recordings = null;
  16274. function updateRecordings() {
  16275. // Save the list of currently checked recordings
  16276. var checkedRecordings = [], elements = document.getElementsByClassName('RecordingCheckbox'), x = '', addHeader = true;
  16277. for (var i=0;i<elements.length;i++) { if (elements[i].checked) { checkedRecordings.push(elements[i].value); } }
  16278. if (p52recordings == null) {
  16279. x += '<div style=width:100%;text-align:center;margin-top:20px><i>' + "Loading..." + '</i></div>';
  16280. } else if (typeof p52recordings == 'number') {
  16281. if (p52recordings == 1) { x += '<div style=width:100%;text-align:center;margin-top:20px><i>' + "Server is unable to read from the recordings folder." + '</i></div>'; }
  16282. else if (p52recordings == 2) { x += '<div style=width:100%;text-align:center;margin-top:20px><i>' + "Server is unable to get recordings from the database." + '</i></div>'; }
  16283. else { x += '<div style=width:100%;text-align:center;margin-top:20px><i>' + "An unknown error occured." + '</i></div>'; }
  16284. } else if (p52recordings.length == 0) {
  16285. x += '<div style=width:100%;text-align:center;margin-top:20px><i>' + "No recordings." + '</i></div>';
  16286. } else {
  16287. // Display the users using the sorted list
  16288. x += '<table class=p3usersTable cellpadding=0 cellspacing=0>';
  16289. x += '<th>' + "Session" + '<th style=width:110px>' + nobreak("Start Time") + '<th style=width:110px>' + "Duration" + '<th style=width:110px>' + "Size";
  16290. if (p52recordings != null) {
  16291. var recdate = null;
  16292. for (var i in p52recordings) {
  16293. var rec = p52recordings[i], rect = new Date(rec.startTime), day = printDate(rect);
  16294. if (day != recdate) { recdate = day; x += '<tr><td class=userTableHeader colspan=4>' + day; }
  16295. x += addRecordingHtml(i, rec);
  16296. }
  16297. }
  16298. x += '</table>';
  16299. }
  16300. QH('p52recordings', x);
  16301. // Re-check recordings
  16302. elements = document.getElementsByClassName('RecordingCheckbox');
  16303. for (var i=0;i<elements.length;i++) { elements[i].checked = ((checkedRecordings.indexOf(elements[i].value) >= 0)); }
  16304. p52updateInfo();
  16305. }
  16306. function addRecordingHtml(i, rec) {
  16307. var sessionLengthStr = '';
  16308. if (rec.lengthTime) { sessionLengthStr = pad2(Math.floor(rec.lengthTime / 3600)) + ':' + pad2(Math.floor((rec.lengthTime % 3600) / 60)) + ':' + pad2(Math.floor(rec.lengthTime % 60)); }
  16309. var sessionStartStr = printTime(new Date(rec.startTime));
  16310. if (rec.sessionStart) { sessionStartStr = printTime(new Date(rec.sessionStart)); }
  16311. var sessionSize = '';
  16312. if (rec.size) { sessionSize = format("{0} Kb", Math.round(rec.size / 1024)); }
  16313. var sessionName = '<i>' + "Unknown" + '</i>';
  16314. if (rec.name && rec.meshname) {
  16315. var recmesh = meshes[rec.meshid];
  16316. if (recmesh != null) {
  16317. sessionName = '<a href=# onclick=\'gotoMesh("' + rec.meshid + '")\'>' + EscapeHtml(rec.meshname) + '</a> - <a href=# onclick=\'gotoDevice("' + rec.nodeid + '",10)\'>' + EscapeHtml(rec.name) + '</a>';
  16318. } else {
  16319. sessionName = EscapeHtml(rec.meshname) + ' - ' + EscapeHtml(rec.name);
  16320. }
  16321. }
  16322. if ((rec.userids != null) && (rec.userids.length > 0)) {
  16323. if (rec.userids.length > 1) {
  16324. sessionName += ' - ' + format('{0} users', rec.userids.length);
  16325. } else {
  16326. var ruser = null;
  16327. if (users != null) { ruser = users[rec.userids[0]]; }
  16328. if (ruser != null) {
  16329. sessionName += ' - <a href=# onclick=\'gotoUser("' + encodeURIComponentEx(rec.userids[0]) + '")\'>' + EscapeHtml(ruser.name) + '</a>';
  16330. } else {
  16331. sessionName += ' - ' + EscapeHtml(rec.userids[0].split('/')[2]);
  16332. }
  16333. }
  16334. }
  16335. if (rec.protocol == 1) { sessionName += ' - ' + "Terminal Session"; }
  16336. if (rec.protocol == 2) { sessionName += ' - ' + "Desktop Session"; }
  16337. if (rec.protocol == 5) { sessionName += ' - ' + "File Transfer"; }
  16338. if (rec.protocol == 6) { sessionName += ' - ' + "Admin PowerShell"; }
  16339. if (rec.protocol == 8) { sessionName += ' - ' + "User Shell"; }
  16340. if (rec.protocol == 9) { sessionName += ' - ' + "User PowerShell"; }
  16341. if (rec.protocol == 100) { sessionName += ' - ' + "Intel&reg; AMT WSMAN"; }
  16342. if (rec.protocol == 101) { sessionName += ' - ' + "Intel&reg; AMT Redirection"; }
  16343. if (rec.protocol == 200) { sessionName += ' - ' + "Messenger"; }
  16344. var actions = '', icon = 'm0';
  16345. if (rec.present == 1) {
  16346. icon = 'm1';
  16347. actions = '<div style=cursor:pointer;float:right><a onclick=downloadFile("recordings.ashx?file=' + encodeURIComponentEx(rec.filename) + '")><img src=images/link4.png height=10 width=10 title="Download Recording"></a>&nbsp;</div>';
  16348. actions += '<div style=cursor:pointer;float:right><a href="player.htm?stream=' + encodeURIComponentEx(rec.filename) + '") rel=\"noreferrer noopener\" target=\"mcplay-' + encodeURIComponentEx(rec.filename) + '\"><img src=images/link7.png height=10 width=10 title="Play Recording"></a>&nbsp;</div>';
  16349. }
  16350. var x = '<tr tabindex=0 onmouseover=userMouseHover2(this,1) onmouseout=userMouseHover2(this,0) onkeypress="if (event.key==\'Enter\') showRecordingDialog(event,\'' + i + '\')"><td style=cursor:pointer>';
  16351. x += '<div class=bar style=width:100%>';
  16352. //x += '<div class=baricon><input class=RecordingCheckbox value="' + encodeURIComponentEx(rec.filename) + '" onclick=p52updateInfo() type=checkbox></div>';
  16353. x += '<div></div>';
  16354. x += '<div class=baricon onclick=showRecordingDialog(event,"' + i + '")><div class=' + icon + '></div></div>';
  16355. x += '<div class=g1 onclick=showRecordingDialog(event,"' + i + '")></div><div class=g2 onclick=showRecordingDialog("' + i + '")></div>';
  16356. x += '<div style=line-height:24px onclick=showRecordingDialog(event,"' + i + '")>' + actions + '<div style=font-size:16px>' + sessionName + '</div></div></div><td style=text-align:center>' + sessionStartStr + '<td style=text-align:center>' + sessionLengthStr + '<td style=text-align:center>' + sessionSize;
  16357. return x;
  16358. }
  16359. function showRecordingDialog(event, i) {
  16360. if (xxdialogMode) return;
  16361. if ((event.target.tagName == 'IMG') || (event.target.tagName == 'A')) return;
  16362. var rec = p52recordings[i];
  16363. var x = '';
  16364. if (rec.protocol) {
  16365. var protocolStr = "Unknown";
  16366. if (rec.protocol == 1) { protocolStr = "Terminal"; }
  16367. if (rec.protocol == 2) { protocolStr = "Desktop"; }
  16368. if (rec.protocol == 5) { protocolStr = "Files"; }
  16369. if (rec.protocol == 6) { protocolStr = "Admin PowerShell"; }
  16370. if (rec.protocol == 8) { protocolStr = "User Shell"; }
  16371. if (rec.protocol == 9) { protocolStr = "User PowerShell"; }
  16372. if (rec.protocol == 100) { protocolStr = "Intel&reg; AMT WSMAN"; }
  16373. if (rec.protocol == 101) { protocolStr = "Intel&reg; AMT Redirection"; }
  16374. if (rec.protocol == 200) { protocolStr = "Messenger"; }
  16375. x += addHtmlValue4("Protocol", protocolStr);
  16376. }
  16377. x += addHtmlValue4("Status", (rec.present == 1)?"Present on server":"Not on server");
  16378. if (rec.name) { x += addHtmlValue4("Device Name", EscapeHtml(rec.name)); }
  16379. if (rec.meshname) { x += addHtmlValue4("Device Group", EscapeHtml(rec.meshname)); }
  16380. if (rec.size) { x += addHtmlValue4("Size", format("{0} bytes", rec.size)); }
  16381. if (rec.startTime) { x += addHtmlValue4("Start Time", printTime(new Date(rec.startTime))); }
  16382. if (rec.startTime && rec.lengthTime) { x += addHtmlValue4("End Time", printTime(new Date(rec.startTime + (rec.lengthTime * 1000)))); }
  16383. if (rec.lengthTime) { x += addHtmlValue4("Duration", pad2(Math.floor(rec.lengthTime / 3600)) + ':' + pad2(Math.floor((rec.lengthTime % 3600) / 60)) + ':' + pad2(Math.floor(rec.lengthTime % 60))); }
  16384. if (rec.multiplex == true) { x += addHtmlValue4("Multiplexor", "Enabled"); }
  16385. if (rec.userids) { for (var i in rec.userids) { x += addHtmlValue4("User", rec.userids[i].split('/')[2]); } }
  16386. setDialogMode(2, "Recording Details", 9, null, x);
  16387. }
  16388. function refreshRecodings() { meshserver.send({ action: 'recordings', limit: 1000 }); }
  16389. function openRecodringPlayer() { if (!xxdialogMode) safeNewWindow(window.location.origin + '{{{domainurl}}}player.htm', 'meshcentral-deskplayer'); }
  16390. function p52updateInfo() {
  16391. var elements = document.getElementsByClassName('RecordingCheckbox'), checkcount = 0;
  16392. for (var i=0;i<elements.length;i++) { if (elements[i].checked === true) { checkcount++; } }
  16393. if (checkcount > 0) {
  16394. } else {
  16395. }
  16396. }
  16397. //
  16398. // FILE SELECTOR, DIALOG 3
  16399. //
  16400. function d3init() {
  16401. d3fileoptions = { dialog: 1, filter: 'd3filter', files: 'd3serverfiles', folderup: 'p3FolderUp', currentFolder: 'p3CurrentFolder', func: d3setActions };
  16402. Q('d3localFile').value = '';
  16403. Q('d3localFile').accept = Q('d3filter').value;
  16404. d3modechange();
  16405. }
  16406. function d3modechange() {
  16407. var mode = Q('d3uploadMode').value;
  16408. QV('d3localmode', mode == 1);
  16409. QV('d3servermode', mode == 2);
  16410. if (mode == 1) { d3setActions(); } else { d3updatefiles(); }
  16411. }
  16412. var d3filetreelinkpath;
  16413. var d3filetreelocation = [];
  16414. var d3fileoptions = null;
  16415. function d3updatefiles() {
  16416. if (d3fileoptions == null) return;
  16417. if ((d3fileoptions.filter == 'd3filter') && (Q('d3uploadMode').value == 1)) return;
  16418. var html1 = '', html2 = '', filetreex = filetree, folderdepth = 1, publicPath = null, lastFolderName = '';
  16419. // Navigate to path location, build the paths at the same time
  16420. var d3filetreelocation2 = [], oldlinkpath = d3filetreelinkpath, checkedBoxes = [], checkboxes = document.getElementsByName('fc');
  16421. for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { checkedBoxes.push(checkboxes[i].value) }; } // Save all existing checked boxes
  16422. d3filetreelinkpath = '';
  16423. for (var i in d3filetreelocation) {
  16424. if ((filetreex.f != null) && (filetreex.f[d3filetreelocation[i]] != null)) {
  16425. d3filetreelocation2.push(d3filetreelocation[i]);
  16426. if ((folderdepth == 1)) {
  16427. var sp = d3filetreelocation[i].split('/');
  16428. publicPath = window.location.origin + domainUrl + sp[0] + 'files/' + sp[2];
  16429. if (d3filetreelocation[i] === userinfo._id) { d3filetreelinkpath += 'self'; } else { d3filetreelinkpath += (sp[0] + '/' + sp[2]); }
  16430. } else {
  16431. if (d3filetreelinkpath != '') { d3filetreelinkpath += '/' + d3filetreelocation[i]; if (folderdepth > 2) { publicPath += '/' + d3filetreelocation[i]; } }
  16432. }
  16433. filetreex = filetreex.f[d3filetreelocation[i]];
  16434. lastFolderName = filetreex.n;
  16435. folderdepth++;
  16436. } else {
  16437. break;
  16438. }
  16439. }
  16440. d3filetreelocation = d3filetreelocation2; // In case we could not go down the full path, we set the new path location here.
  16441. // Sort the files
  16442. var filetreexx = p5sort_files(filetreex.f);
  16443. // File filter
  16444. var fileFilter = '';
  16445. if (d3fileoptions.filter) { fileFilter = Q(d3fileoptions.filter).value };
  16446. // Display all files and folders at this location
  16447. for (var i in filetreexx) {
  16448. // Figure out the name and shortname
  16449. var f = filetreexx[i], name = f.n, shortname;
  16450. // Filter out files
  16451. if ((f.t == 3) && (fileFilter != '') && (f.nx.toLowerCase().endsWith(fileFilter) == false)) { continue; }
  16452. // if (name.length > 70) { shortname = '<span title="' + EscapeHtml(name) + '">' + EscapeHtml(name.substring(0, 70)) + ("..." + '</span>'); } else { shortname = EscapeHtml(name); }
  16453. // Removed redundant filename length check because we handle it in the CSS
  16454. shortname = EscapeHtml(name);
  16455. // Figure out the size
  16456. var fsize = '';
  16457. if (f.s != null) { fsize = getFileSizeStr(f.s); }
  16458. var h = '';
  16459. if (f.t != 3) {
  16460. var title = '';
  16461. 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>';
  16462. } else {
  16463. var link = shortname;
  16464. //if (f.s > 0) { link = "<a rel=\"noreferrer noopener\" target=\"_blank\" href=\"downloadfile.ashx?link=" + encodeURIComponentEx(filetreelinkpath + '/' + f.nx) + "\">" + shortname + "</a>"; }
  16465. 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>';
  16466. }
  16467. if (f.t < 3) { html1 += h; } else { html2 += h; }
  16468. }
  16469. if (d3fileoptions.currentFolder) { QH(d3fileoptions.currentFolder, lastFolderName); }
  16470. QH(d3fileoptions.files, html1 + html2);
  16471. QE(d3fileoptions.folderup, d3filetreelocation.length > 0);
  16472. if (d3fileoptions.func) { d3fileoptions.func(); }
  16473. }
  16474. function d3folderset(x) { d3filetreelocation.push(decodeURIComponent(x)); d3updatefiles(); return false; }
  16475. function d3folderup(x) { if (x == null) { d3filetreelocation.pop(); } else { while (d3filetreelocation.length > x) { d3filetreelocation.pop(); } } d3updatefiles(); }
  16476. 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; }
  16477. function d3setActions() {
  16478. if (d3fileoptions.dialog == 1) {
  16479. var mode = Q('d3uploadMode').value;
  16480. if (mode == 1) {
  16481. QE('idx_dlgOkButton', Q('d3localFile').value.length > 0);
  16482. } else {
  16483. QE('idx_dlgOkButton', d3getFileSel().length == 1);
  16484. }
  16485. } else if (d3fileoptions.dialog == 2) {
  16486. QE('idx_dlgOkButton', d3getFileSel().length == 1);
  16487. }
  16488. }
  16489. //
  16490. // MY REPORTS
  16491. //
  16492. var reportCSV = null;
  16493. function generateReportDialog() {
  16494. if (xxdialogMode) return;
  16495. var y = '', x = '', settings = JSON.parse(getstore('_ReportSettings', '{}'));
  16496. var options = { 1 : "Remote Sessions", 2 : "User Traffic Usage", 3 : "User Logins" }
  16497. if (userinfo.siteadmin == 0xFFFFFFFF) { options[4] = "Database Records"; }
  16498. for (var i in options) { y += '<option value=' + i + ((settings.type == i)?' selected':'') + '>' + options[i] + '</option>'; }
  16499. x += addHtmlValue("Type", '<select id=d2reportType style=float:right;width:250px onchange=generateReportDialogValidate()>' + y + '</select>');
  16500. x += '<div id=d2GroupByDiv style=display:none>';
  16501. y = '';
  16502. var options = { 1 : "User", 2: "Device", 3: "Day" }
  16503. for (var i in options) { y += '<option value=' + i + ((settings.groupBy == i)?' selected':'') + '>' + options[i] + '</option>'; }
  16504. x += addHtmlValue("Group by", '<select id=d2groupBy style=float:right;width:250px onchange=generateReportDialogValidate()>' + y + '</select>');
  16505. x += '</div>';
  16506. x += '<div id=d2GroupByDiv2 style=display:none>';
  16507. y = '';
  16508. var options = { 1 : "User", 3: "Day" }
  16509. for (var i in options) { y += '<option value=' + i + ((settings.groupBy2 == i)?' selected':'') + '>' + options[i] + '</option>'; }
  16510. x += addHtmlValue("Group by", '<select id=d2groupBy2 style=float:right;width:250px onchange=generateReportDialogValidate()>' + y + '</select>');
  16511. x += '</div>';
  16512. x += '<div id=d2DeviceGroupDiv style=display:none>';
  16513. y = '<option value=0' + ((settings.devGroup == 0)?' selected':'') + '>' + "All" + '</option>';
  16514. var omeshs = getOrderedList(meshes, 'name');
  16515. for (var i in omeshs) { y += '<option value=' + encodeURIComponentEx(omeshs[i]._id) + ((settings.devGroup == omeshs[i]._id)?' selected':'') + '>' + EscapeHtml(omeshs[i].name) + '</option>'; }
  16516. x += addHtmlValue("Device Group", '<select onchange=generateReportDialogValidate() id=d2groupId style=float:right;width:250px>' + y + '</select>');
  16517. x += '</div>';
  16518. x += '<div id=d2timeBackDiv style=display:none>';
  16519. y = '';
  16520. if (settings.timeRange == null) { settings.timeRange = 1; }
  16521. var options = { 1 : "Last Day", 7: "Last 7 days", 30: "Last 30 days", 0: "Time range" }
  16522. for (var i in options) { y += '<option value=' + i + ((settings.timeRange == i)?' selected':'') + '>' + options[i] + '</option>'; }
  16523. x += addHtmlValue("Time", '<select id=d2timeRange style=float:right;width:250px onchange=generateReportDialogValidate()>' + y + '</select>');
  16524. x += '</div>';
  16525. x += '<div id=d2timeRangeDiv style=display:none>';
  16526. x += addHtmlValue("Time Range Start", '<input id=d2timeRangeStartSelector style=float:right;width:250px class=flatpickr type="text" placeholder="' + "Select Date & Time..." + '" data-id="altinput">');
  16527. x += addHtmlValue("Time Range End", '<input id=d2timeRangeEndSelector style=float:right;width:250px class=flatpickr type="text" placeholder="' + "Select Date & Time..." + '" data-id="altinput">');
  16528. x += '</div>';
  16529. x += '<div id=d2showTrafficDiv style=display:none>';
  16530. x += addHtmlValue("", '<div style=width:250px><label><input type=checkbox id=d2showTraffic ' + ((settings.showTraffic) ? 'checked' : '') + '>' + "Show Traffic" + '</label><div>');
  16531. x += '</div>';
  16532. setDialogMode(2, "Generate Report", 3, generateReportDialogEx, x);
  16533. generateReportDialogValidate();
  16534. var lastWeek = new Date();
  16535. lastWeek.setDate(lastWeek.getDate() - 7);
  16536. var rangeStartTime = flatpickr('#d2timeRangeStartSelector', {
  16537. enableTime: true, maxDate: new Date(), defaultDate: lastWeek, minuteIncrement: 1, plugins: [new confirmDatePlugin({})],
  16538. onChange: function(selectedDates, dateStr, instance) {
  16539. rangeEndTime.set('minDate', dateStr);
  16540. if (rangeEndTime.selectedDates[0] && rangeEndTime.selectedDates[0] < selectedDates[0]) {
  16541. rangeEndTime.clear();
  16542. }
  16543. }
  16544. });
  16545. var rangeEndTime = flatpickr('#d2timeRangeEndSelector', { enableTime: true, maxDate: new Date(), defaultDate: new Date(), minuteIncrement: 1, plugins: [new confirmDatePlugin({})] });
  16546. xxdialogTag = { start: rangeStartTime, end: rangeEndTime };
  16547. }
  16548. function generateReportDialogValidate() {
  16549. QV('d2timeRangeDiv', Q('d2timeRange').value == 0);
  16550. QV('d2timeBackDiv', Q('d2reportType').value != 4);
  16551. QV('d2GroupByDiv', Q('d2reportType').value == 1);
  16552. QV('d2GroupByDiv2', Q('d2reportType').value == 3);
  16553. QV('d2DeviceGroupDiv', Q('d2reportType').value == 1);
  16554. QV('d2showTrafficDiv', Q('d2reportType').value == 1);
  16555. }
  16556. function generateReportDialogEx(b, tag) {
  16557. var start, end;
  16558. if (Q('d2timeRange').value == 0) {
  16559. end = Math.floor(tag.end.selectedDates[0].getTime() / 1000);
  16560. start = Math.floor(tag.start.selectedDates[0].getTime() / 1000);
  16561. } else {
  16562. end = Math.floor(new Date() / 1000);
  16563. start = new Date();
  16564. start = Math.floor(start.setDate(start.getDate() - Q('d2timeRange').value) / 1000);
  16565. }
  16566. var tz = null;
  16567. try { tz = Intl.DateTimeFormat().resolvedOptions().timeZone; } catch (ex) {}
  16568. var devGroup = decodeURIComponent(Q('d2groupId').value);
  16569. if (devGroup == 0) { devGroup = null; }
  16570. putstore('_ReportSettings', JSON.stringify({ type: parseInt(Q('d2reportType').value), groupBy: parseInt(Q('d2groupBy').value), groupBy2: parseInt(Q('d2groupBy2').value), timeRange: parseInt(Q('d2timeRange').value), devGroup: devGroup, showTraffic: Q('d2showTraffic').checked }));
  16571. var groupBy = parseInt(Q('d2groupBy').value);
  16572. if (Q('d2reportType').value == 3) { groupBy = parseInt(Q('d2groupBy2').value); }
  16573. meshserver.send({ action: 'report', type: parseInt(Q('d2reportType').value), groupBy: groupBy, start: start, end: end, tz: tz, tf: new Date().getTimezoneOffset(), l: getLang(), devGroup: devGroup, showTraffic: Q('d2showTraffic').checked });
  16574. }
  16575. function renderReport(r) {
  16576. var colTranslation = { time: "Time", device: "Device", session: "Session", user: "User", devgroup: "Device Group", guest: "Guest", length: "Length", bytesin: "Bytes In", bytesout: "Bytes Out", os: "Operating System", ip: "IP Address", browser: "Browser", msg: "Message", twofactor: "2nd Factor" }
  16577. var x = '<table style=width:100%>', firstItem;
  16578. var sumByCol = null; // Indicate by what colum we sum by
  16579. var sumByValues = []; // Indicate by what values we sum by
  16580. if (Object.keys(r.groups).length == 0) {
  16581. reportCSV = null;
  16582. QV('p60downloadReportDiv', false);
  16583. x += '<tr><td style=text-align:center;padding-top:32px>' + "Report returned no entires." + '</tr></td>';
  16584. } else {
  16585. reportCSV = '"' + "Group" + '"';
  16586. x += '<tr>'
  16587. for (var i in r.columns) {
  16588. var coltitle;
  16589. if (colTranslation[r.columns[i].title] != null) { coltitle = colTranslation[r.columns[i].title]; } else { coltitle = EscapeHtml(r.columns[i].title); }
  16590. var style = '';
  16591. if (r.columns[i].align) { style += ';text-align:' + EscapeHtml(r.columns[i].align); } else { style += ';text-align:left'; }
  16592. if ((i == 0) && ((r.columns[i].format == 'datetime') || (r.columns[i].format == 'time'))) { style += ';width:1%'; }
  16593. x += '<th style=' + style + '>' + coltitle + '</th>';
  16594. reportCSV += ',"' + csvClean(coltitle) + '"';
  16595. firstItem = false;
  16596. }
  16597. x += '</tr>'
  16598. reportCSV += '\r\n';
  16599. for (var i in r.groups) {
  16600. x += '<tr style=height:8px></tr>'
  16601. x += '<tr><td colspan=' + r.columns.length + ' style="border-bottom:1pt solid black"><b>'
  16602. if (i != 0) { x += renderReportFormat(i, r.groupFormat); }
  16603. x += '</b></td></tr>'
  16604. for (var j in r.groups[i].entries) {
  16605. var e = r.groups[i].entries[j];
  16606. x += '<tr>'
  16607. if (i != 0) { reportCSV += '"' + csvClean(renderReportFormatCSV(i, r.groupFormat)) + '"'; } else { reportCSV += '""'; }
  16608. for (var k in r.columns) {
  16609. var style = '';
  16610. if (r.columns[k].format != null) { style = 'white-space:nowrap;' }
  16611. if (r.columns[k].align) { style = 'text-align:' + EscapeHtml(r.columns[k].align); }
  16612. if (e[r.columns[k].id] != null) {
  16613. x += '<td style="' + style + '">' + renderReportFormat(e[r.columns[k].id], r.columns[k].format) + '</td>';
  16614. reportCSV += ',"' + csvClean(renderReportFormatCSV(e[r.columns[k].id], r.columns[k].format)) + '"';
  16615. } else {
  16616. x += '<td></td>';
  16617. reportCSV += ',';
  16618. }
  16619. if (r.columns[k].sumBy != null) {
  16620. var v1 = e[r.columns[k].sumBy];
  16621. if (r.columns[k].sumBy === true) { v1 = true; }
  16622. var v2 = e[r.columns[k].id];
  16623. sumByCol = r.columns[k].sumBy;
  16624. if (v2 != null) {
  16625. if (sumByValues.indexOf(v1) == -1) { sumByValues.push(v1); }
  16626. if (r.columns[k].subtotals == null) { r.columns[k].subtotals = {}; r.columns[k].totals = {}; }
  16627. if (r.columns[k].subtotals[v1] == null) { r.columns[k].subtotals[v1] = v2; } else { r.columns[k].subtotals[v1] += v2; }
  16628. if (r.columns[k].totals[v1] == null) { r.columns[k].totals[v1] = v2; } else { r.columns[k].totals[v1] += v2; }
  16629. }
  16630. }
  16631. }
  16632. x += '</tr>'
  16633. reportCSV += '\r\n';
  16634. }
  16635. }
  16636. // Display totals
  16637. if (sumByCol != null) {
  16638. var sumByColNum = -1;
  16639. if (sumByCol === true) { sumByColNum = 99999; } else { for (var i in r.columns) { if (r.columns[i].id == sumByCol) { sumByColNum = i; } } }
  16640. if (sumByColNum >= 0) {
  16641. sumByValues.sort();
  16642. var firstRow = true;
  16643. x += '<tr style=height:8px></tr>'
  16644. for (var j in sumByValues) {
  16645. x += '<tr>'
  16646. for (var i in r.columns) {
  16647. if (i == sumByColNum) {
  16648. var style = '';
  16649. if (r.columns[k].align) { style += ';text-align:' + EscapeHtml(r.columns[k].align); }
  16650. if (firstRow) { style += ';border-top:1pt solid #777'; }
  16651. x += '<td style="color:#777' + style + '">' + renderReportFormat(sumByValues[j], r.columns[i].format); + '</td>';
  16652. } else if (r.columns[i].totals != null) {
  16653. var style = '';
  16654. if (r.columns[k].align) { style += ';text-align:' + EscapeHtml(r.columns[k].align); }
  16655. if (firstRow) { style += ';border-top:1pt solid #777'; }
  16656. x += '<td style="color:#777' + style + '">' + renderReportFormat(r.columns[i].totals[sumByValues[j]], r.columns[i].format); + '</td>';
  16657. } else {
  16658. x += '<td></td>';
  16659. }
  16660. }
  16661. x += '</tr>'
  16662. firstRow = false;
  16663. }
  16664. }
  16665. }
  16666. QV('p60downloadReportDiv', true);
  16667. }
  16668. x += '</table>';
  16669. QH('p60report', x);
  16670. }
  16671. function renderReportFormat(v, f) {
  16672. if (v == null) return '';
  16673. if (f == 'datetime') { return printDateTime(new Date(v)); }
  16674. if (f == 'time') { return printTime(new Date(v)); }
  16675. if (f == 'protocol') {
  16676. if (v == 1) return "Terminal";
  16677. if (v == 2) return "Desktop";
  16678. if (v == 5) return "Files";
  16679. if (v == 6) return "Admin PowerShell";
  16680. if (v == 8) return "User Shell";
  16681. if (v == 9) return "User PowerShell";
  16682. if (v == 100) return "AMT-WSMAN";
  16683. if (v == 101) return "AMT-Redir";
  16684. if (v == 200) return "Messenger";
  16685. if (v == 201) return "Web-RDP";
  16686. if (v == 202) return "Web-SSH";
  16687. if (v == 203) return "Web-SFTP";
  16688. if (v == 204) return "Web-VNC";
  16689. return "Unknown" + ' (' + v + ')';
  16690. }
  16691. if (f == 'seconds') {
  16692. var seconds = v % 60;
  16693. var minutes = Math.floor(v / 60) % 60;
  16694. var hours = Math.floor(v / 3600);
  16695. return zeroPad(hours, 2) + ':' + zeroPad(minutes, 2) + ':' + zeroPad(seconds, 2);
  16696. }
  16697. if (f == 'node') {
  16698. var node = getNodeFromId(v);
  16699. if (node != null) { return '<div onclick=\'gotoDevice("' + node._id + '",10);haltEvent(event);\' style=float:left;margin-right:4px class="j' + node.icon + '"></div>' + EscapeHtml(node.name); } else { return '<i>' + "Unknown" + '</i>'; }
  16700. }
  16701. if (f == 'mesh') {
  16702. var mesh = meshes[v];
  16703. if (mesh != null) { return '<div onclick=\'gotoMesh("' + mesh._id + '",10);haltEvent(event);\' style=float:left;margin-right:4px;cursor:pointer class="m99"></div>' + EscapeHtml(mesh.name); } else { return '<i>' + "Unknown" + '</i>'; }
  16704. }
  16705. if (f == 'nodemesh') {
  16706. var nodeid = null, meshid = null;
  16707. var s = v.split('/'), x = '<table><tr>';
  16708. if (s.length >= 3) { nodeid = s[0] + '/' + s[1] + '/' + s[2]; }
  16709. if (s.length >= 6) { meshid = s[3] + '/' + s[4] + '/' + s[5]; }
  16710. if (meshid != null) {
  16711. var mesh = meshes[meshid];
  16712. if (mesh != null) { x += '<td><div onclick=\'gotoMesh("' + mesh._id + '",10);haltEvent(event);\' style=float:left;margin-right:4px;cursor:pointer class="m99"></div>' + EscapeHtml(mesh.name) + '&nbsp;&nbsp;&nbsp;</td>'; } else { return '<i>' + "Unknown" + '</i>'; }
  16713. }
  16714. var node = getNodeFromId(nodeid);
  16715. if (node != null) { x += '<td><div onclick=\'gotoDevice("' + node._id + '",10);haltEvent(event);\' style=float:left;margin-right:4px class="j' + node.icon + '"></div>' + EscapeHtml(node.name) + '</td>'; } else { return '<i>' + "Unknown" + '</i>'; }
  16716. x += '</tr></table>';
  16717. return x;
  16718. }
  16719. if (f == 'user') {
  16720. var user = null;
  16721. if (v == userinfo._id) { user = userinfo; } else { if (users != null) { user = users[v]; } }
  16722. if (user != null) {
  16723. var name = user.name;
  16724. if (user.realname != null) { name += ', ' + user.realname; }
  16725. return '<div onclick=\'gotoUser("' + user._id + '",10);haltEvent(event);\' style=float:left;margin-right:4px;cursor:pointer class="m2"></div>' + EscapeHtml(name);
  16726. } else {
  16727. return '<i>' + "Unknown User" + '</i>';
  16728. }
  16729. }
  16730. if (f == 'msg') {
  16731. if (v == 107) return '<div style=float:left;margin-right:4px;cursor:pointer class="NotifyIconTiny1"></div>' + "Succesful login";
  16732. if (v == 108) return '<div style=float:left;margin-right:4px;cursor:pointer class="NotifyIconTiny2"></div>' + "Incorrect 2nd factor";
  16733. if (v == 109) return '<div style=float:left;margin-right:4px;cursor:pointer class="NotifyIconTiny4"></div>' + "Locked account";
  16734. if (v == 110) return '<div style=float:left;margin-right:4px;cursor:pointer class="NotifyIconTiny3"></div>' + "Invalid login attempt";
  16735. }
  16736. if (f == '2fa') {
  16737. if (v == '') { return "None"; }
  16738. if (v == 'backup') { return "Backup Codes"; }
  16739. if (v == 'fido') { return "FIDO key"; }
  16740. if (v == 'sms') { return "SMS message"; }
  16741. if (v == 'hwotp') { return "Hardware OTP"; }
  16742. if (v == 'messenger') { return "Messenging"; }
  16743. if (v == 'push') { return "Push Notification"; }
  16744. if (v == 'otp') { return "One-Time Password"; }
  16745. if (v == 'cookie') { return "Remember Device"; }
  16746. if (v == 'tokenlogin') { return "Login Token"; }
  16747. if (v == 'ipaddr') { return "IP Address"; }
  16748. if (v == 'sso') { return "Single Sign-on"; }
  16749. }
  16750. if (f == 'records') {
  16751. var c = {
  16752. 'node': "Device record",
  16753. 'mesh': "Device group record",
  16754. 'user': "User records",
  16755. 'sysinfo': "Device information records",
  16756. 'iploc': "IP location information records",
  16757. 'note': "Text notes records",
  16758. 'ifinfo': "Network interface information records",
  16759. 'cfile': "Configuration file records",
  16760. 'lastconnect': "Last connection time records",
  16761. 'power': "Device power transition records",
  16762. 'events': "Event records",
  16763. 'serverstats': "Server statistics records",
  16764. 'ugrp': "User group records",
  16765. 'deviceshare': "Device sharing records",
  16766. 'logintoken': "Account login token records",
  16767. 'smbios': "Device SMBIOS record",
  16768. 'pmt': "Device Push Notification Record",
  16769. 'meshcentral': "Device, users, groups and other records"
  16770. }
  16771. return (c[v] != null)? c[v] : v;
  16772. }
  16773. return EscapeHtml(v);
  16774. }
  16775. function renderReportFormatCSV(v, f) {
  16776. if (f == 'datetime') { return printDateTime(new Date(v)); }
  16777. if (f == 'time') { return printTime(new Date(v)); }
  16778. if (f == 'protocol') {
  16779. if (v == 1) return "Terminal";
  16780. if (v == 2) return "Desktop";
  16781. if (v == 5) return "Files";
  16782. if (v == 6) return "Admin PowerShell";
  16783. if (v == 8) return "User Shell";
  16784. if (v == 9) return "User PowerShell";
  16785. if (v == 100) return "AMT-WSMAN";
  16786. if (v == 101) return "AMT-Redir";
  16787. if (v == 200) return "Messenger";
  16788. if (v == 201) return "Web-RDP";
  16789. if (v == 202) return "Web-SSH";
  16790. if (v == 203) return "Web-SFTP";
  16791. if (v == 204) return "Web-VNC";
  16792. return "Unknown" + ' (' + v + ')';
  16793. }
  16794. if (f == 'node') {
  16795. var node = getNodeFromId(v);
  16796. if (node != null) { return node.name; }
  16797. }
  16798. if (f == 'mesh') {
  16799. var mesh = meshes[v];
  16800. if (mesh != null) { return mesh.name }
  16801. }
  16802. if (f == 'user') {
  16803. var user = null;
  16804. if (v == userinfo._id) { user = userinfo; } else { if (users != null) { user = users[v]; } }
  16805. if (user != null) {
  16806. var name = user.name;
  16807. if (user.realname != null) { name += ' - ' + user.realname; }
  16808. return name;
  16809. } else {
  16810. return "Unknown User";
  16811. }
  16812. }
  16813. if (f == 'msg') {
  16814. if (v == 107) return "Succesful login";
  16815. if (v == 108) return "Incorrect 2nd factor";
  16816. if (v == 109) return "Locked account";
  16817. if (v == 110) return "Invalid login attempt";
  16818. }
  16819. if (f == 'records') { return renderReportFormat(v, f); }
  16820. return '' + v;
  16821. }
  16822. function p60downloadReport() {
  16823. if (xxdialogMode || (reportCSV == null)) return;
  16824. saveAs(stringToUtf8Blob(reportCSV), "Report.csv");
  16825. }
  16826. //
  16827. // NOTIFICATIONS
  16828. //
  16829. var notifications = [];
  16830. // Toggle showing notifications
  16831. function clickNotificationIcon(show) {
  16832. //addNotification({ icon:0, text:'test' });
  16833. if (show == true) { QV('notifiyBox', true); } else if (show == false) { QV('notifiyBox', false); } else { QV('notifiyBox', QS('notifiyBox')['display'] == 'none'); }
  16834. drawNotifications();
  16835. }
  16836. // Set the notification count on the upper right of the screen
  16837. function setNotificationCount(c) {
  16838. if (parseInt(Q('notificationCount').innerHTML) == c) return; // If the count did not change, exit now.
  16839. QH('notificationCount', c);
  16840. QS('notificationCount')['background-color'] = (c == 0)?'lightblue':'orange';
  16841. QV('notificationCount', c > 0);
  16842. }
  16843. // Refresh the notification box
  16844. function drawNotifications() {
  16845. var notifySettings = getstore('notifications', 0);
  16846. var r = '';
  16847. if (notifications.length == 0) {
  16848. r = '<div style=margin:5px>' + "There are currently no notifications" + '</div>';
  16849. } else {
  16850. for (var i in notifications) {
  16851. var n = notifications[i], t = '', d = new Date(n.time), icon = 0;
  16852. if (n.title != null) { t = '<b>' + EscapeHtml(n.title) + '</b>: ' }
  16853. if (n.nodeid != null) {
  16854. var node = getNodeFromId(n.nodeid);
  16855. if (node != null) {
  16856. icon = node.icon;
  16857. 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
  16858. }
  16859. }
  16860. 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') + '">';
  16861. if (icon) { r += '<div class=j' + icon + ' onclick="notificationSelected(' + n.id + ')" style=margin:5px;float:left></div>'; }
  16862. 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>';
  16863. }
  16864. }
  16865. var deleteall = '';
  16866. 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>'; }
  16867. QH('notifiyBox', '<div class=customScroll style="max-height:170px;overflow-y:auto;margin:5px">' + r + deleteall + '</div>' );
  16868. }
  16869. // A notification was selected
  16870. function notificationSelected(id, del) {
  16871. var j = -1;
  16872. for (var i in notifications) { if (notifications[i].id == id) { j = i; } }
  16873. if (j != -1) {
  16874. notificationSelectedEx(notifications[j], id);
  16875. if (del && notifications[j]) {
  16876. if (notifications[j].notification) { notifications[j].notification.close(); delete notifications[j].notification; }
  16877. notificationDelete(id);
  16878. }
  16879. }
  16880. }
  16881. function notificationSelectedEx(n, id) {
  16882. if (n.nodeid != null) {
  16883. if (n.tag == 'desktop') gotoDevice(n.nodeid, 12); // Desktop
  16884. else if (n.tag == 'terminal') gotoDevice(n.nodeid, 11); // Terminal
  16885. else if (n.tag == 'files') gotoDevice(n.nodeid, 13); // Files
  16886. else if (n.tag == 'intelamt') gotoDevice(n.nodeid, 14); // Intel AMT
  16887. else if (n.tag == 'console') gotoDevice(n.nodeid, 15); // Files
  16888. else gotoDevice(n.nodeid, 10); // General
  16889. } else {
  16890. if ((n.tag == 'backupcodes') && !xxdialogMode) { go(2); account_manageOtp(0); notificationDelete(id); } // 2FA backup codes
  16891. else if ((n.tag != null) && n.tag.startsWith('meshmessenger/')) {
  16892. safeNewWindow('/messenger?id=' + n.tag + '&title=' + encodeURIComponentEx(n.username), n.tag.split('/')[2]);
  16893. notificationDelete(id);
  16894. } else if (n.url != null) {
  16895. safeNewWindow(n.url);
  16896. notificationDelete(id);
  16897. }
  16898. }
  16899. }
  16900. // Remove one notification
  16901. function notificationDelete(id) {
  16902. var j = -1, e = Q('notifyx' + id);
  16903. if (e != null) {
  16904. for (var i in notifications) { if (notifications[i].id == id) { j = i; } }
  16905. if (j != -1) {
  16906. meshserver.send({ action: 'intersession', subaction: 'removeNotify', id: id }); // Remove the notification in other sessions of the same user.
  16907. if (notifications[j].notification) { notifications[j].notification.close(); delete notifications[j].notification; }
  16908. notifications.splice(j, 1);
  16909. e.parentNode.removeChild(e);
  16910. setNotificationCount(notifications.length);
  16911. if (notifications.length == 0) { QV('notifiyBox', false); }
  16912. if (notifications.length == 1) { QV('notifyRemoveAll', false); }
  16913. if ((notifications.length > 0) && (j == 0)) {
  16914. var n = notifications[0];
  16915. QS('notifyx' + n.id)['border-top'] = '1px solid transparent';
  16916. }
  16917. }
  16918. }
  16919. }
  16920. // Add a new notification and play the notification sound
  16921. function addNotification(n) {
  16922. // Perform message translation
  16923. var translatedTitles = [
  16924. null,
  16925. "New Account", // 1
  16926. "Server Limit",
  16927. "Security Warning",
  16928. "Account Settings",
  16929. "Device Group",
  16930. "Invite Codes"
  16931. ];
  16932. var translatedMessages = [
  16933. null,
  16934. "Permission denied", // 1
  16935. "Invalid username",
  16936. "Invalid password",
  16937. "Invalid email",
  16938. "Invalid domain",
  16939. "Invalid site permissions",
  16940. "User already exists",
  16941. "Unable to add user in this mode",
  16942. "Validation exception",
  16943. "Account limit reached.", // 10
  16944. "Chat Request, Click here to accept.",
  16945. "There has been {0} failed login attempts on this account since the last login.",
  16946. "Failed to change email address, another account already using: {0}.",
  16947. "Email sent.",
  16948. "User {0} not found.",
  16949. "Users {0} not found.",
  16950. "Error, unable to change to previously used password.",
  16951. "Error, unable to change to commonly used password.",
  16952. "Error, password not changed.",
  16953. "Password changed.", // 20
  16954. "Current password not correct.",
  16955. "Error, invite code \"{0}\" already in use.",
  16956. "SMS gateway not enabled",
  16957. "No user management rights",
  16958. "Invalid SMS message",
  16959. "No phone number for this user",
  16960. "SMS succesfully sent.",
  16961. "SMS error",
  16962. "SMS error: {0}",
  16963. "Email domain \"{0}\" is not allowed. Only ({1}) are allowed", // 30,
  16964. "Invalid message",
  16965. "Message succesfully sent.",
  16966. "Message error",
  16967. "Message error: {0}"
  16968. ];
  16969. if (typeof n.titleid == 'number') { try { n.title = translatedTitles[n.titleid]; } catch (ex) {} }
  16970. 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) { } }
  16971. // Show notification within the web page.
  16972. if (n.time == null) { n.time = Date.now(); }
  16973. if (n.id == null) { n.id = Math.random(); }
  16974. notifications.unshift(n);
  16975. setNotificationCount(notifications.length);
  16976. clickNotificationIcon(true);
  16977. var notifySettings = getstore('notifications', 0);
  16978. if (notifySettings & 1) { Q('chimes').play(); }
  16979. // If web notifications are granted, use it.
  16980. var notification = null;
  16981. if (Notification && (Notification.permission == 'granted')) {
  16982. var text = n.text.split('&reg;').join('').split('<b>').join('').split('</b>').join('').split('<br />').join('\r\n'); // Clean up any HTML codes
  16983. if (n.nodeid) {
  16984. var node = getNodeFromId(n.nodeid);
  16985. if (node) {
  16986. if (notifySettings & 16) { // Notify with group name
  16987. notification = new Notification(decodeURIComponent('{{{extitle}}}') + ' - ' + meshes[node.meshid].name + ' - ' + node.name, { tag: n.tag, body: text, icon: '/images/notify/icons128-' + node.icon + '.png' });
  16988. } else {
  16989. notification = new Notification(decodeURIComponent('{{{extitle}}}') + ' - ' + node.name, { tag: n.tag, body: text, icon: '/images/notify/icons128-' + node.icon + '.png' });
  16990. }
  16991. }
  16992. } else {
  16993. if (n.icon == null) { n.icon = 0; }
  16994. var title = n.title;
  16995. if (title == null) { title = ''; } else { title = ' - ' + n.title; }
  16996. notification = new Notification(decodeURIComponent('{{{extitle}}}') + title, { tag: n.tag, body: text, icon: '/images/notify/icons128-' + n.icon + '.png' });
  16997. }
  16998. notification.id = n.id;
  16999. notification.xtag = n.tag;
  17000. notification.url = n.url;
  17001. notification.nodeid = n.nodeid;
  17002. notification.username = n.username;
  17003. notification.onclick = function (e) { notificationSelected(e.target.id, true); }
  17004. n.notification = notification;
  17005. }
  17006. // If the notification has a max time, setup the timer here.
  17007. if ((typeof n.maxtime == 'number') && (n.maxtime > 0)) { var trigger = function notifyRemoveTrigger() { notificationDelete(notifyRemoveTrigger.xid); }; trigger.xid = n.id; setTimeout(trigger, n.maxtime * 1000); }
  17008. }
  17009. // Remove all notifications
  17010. function deleteAllNotifications() {
  17011. notifications = [];
  17012. setNotificationCount(0);
  17013. drawNotifications();
  17014. QV('notifiyBox', false);
  17015. }
  17016. //
  17017. // MyServer General
  17018. //
  17019. function setupGeneralServerStats() {
  17020. window.serverStatCpu = new Chart(document.getElementById('serverCpuChart').getContext('2d'), {
  17021. type: 'doughnut',
  17022. data: { datasets: [{ data: [0, 0], backgroundColor: ['#AAAAAA', '#00AA00'] }], labels: ["Used", "Free"] },
  17023. options: { events: [], responsive: true, plugins: { legend: { display: false, } }, animation: { animateScale: true, animateRotate: true }, width: '40px' }
  17024. });
  17025. window.serverStatMemory = new Chart(document.getElementById('serverMemoryChart').getContext('2d'), {
  17026. type: 'doughnut',
  17027. data: { datasets: [{ data: [0, 0], backgroundColor: ['#AAAAAA', '#00AA00'] }], labels: ["Used", "Free"] },
  17028. options: { events: [], responsive: true, plugins: { legend: { display: false, } }, animation: { animateScale: true, animateRotate: true }, width: '40px' }
  17029. });
  17030. }
  17031. var lastServerStats = null;
  17032. function updateGeneralServerStats(message) {
  17033. if (message != null) { lastServerStats = message; } else { message = lastServerStats; }
  17034. if (message == null) return;
  17035. var serverStatsStrings = {
  17036. ServerState: "Server State",
  17037. AgentErrorCounters: "Agent Error Counters",
  17038. UnknownGroup: "Unknown Group",
  17039. InvalidPKCSsignature: "Invalid PKCS signature",
  17040. InvalidRSAsignature: "Invalid RSA signature",
  17041. InvalidJSON: "Invalid JSON",
  17042. UnknownAction: "Unknown Action",
  17043. BadWebCertificate: "Bad Web Certificate",
  17044. BadSignature: "Bad Signature",
  17045. MaxSessionsReached: "Max Sessions Reached",
  17046. UnknownDeviceGroup: "Unknown Device Group",
  17047. InvalidDeviceGroupType: "Invalid Device Group Type",
  17048. DuplicateAgent: "Duplicate Agent",
  17049. ConnectedIntelAMT: "Connected Intel&reg; AMT",
  17050. ConnectedIntelAMTCira: "Connected Intel&reg; AMT CIRA",
  17051. RelayErrors: "Relay Errors",
  17052. UserAccounts: "User Accounts",
  17053. DeviceGroups: "Device Groups",
  17054. AgentSessions: "Agent Sessions",
  17055. ConnectedUsers: "Connected Users",
  17056. UsersSessions: "Users Sessions",
  17057. RelaySessions: "Relay Sessions",
  17058. RelayCount: "Relay Count"
  17059. };
  17060. // Paint the pie graphs
  17061. if (typeof message.cpuavg == 'object') {
  17062. var m = Math.min(message.cpuavg[0], 1);
  17063. window.serverStatCpu.config.data.datasets[0].data = [m, 1 - m];
  17064. QH('serverCpuChartText', '<div style=margin-bottom:5px>' + "CPU Load" + '</div><div><b title="' + "CPU load in the last minute" + '">' + (Math.round(message.cpuavg[0] * 100.0) / 100.0) + '</b>, <b title="' + "CPU load in the last 5 minutes" + '">' + (Math.round(message.cpuavg[1] * 100.0) / 100.0) + '</b>, <b title="' + "CPU load in the last 15 minutes" + '">' + (Math.round(message.cpuavg[2] * 100.0) / 100.0) + '</b></div>');
  17065. QS('serverCpuChartView')['display'] = 'inline-block';
  17066. window.serverStatCpu.update();
  17067. }
  17068. if ((typeof message.totalmem == 'number') && (typeof message.availablemem == 'number')) {
  17069. window.serverStatMemory.config.data.datasets[0].data = [message.totalmem - message.availablemem, message.availablemem];
  17070. QH('serverMemoryChartText', '<div style=margin-bottom:5px>' + "Available Memory" + '</div><div><b>' + getNiceSize3(message.availablemem) + '</b> ' + "free" + ', <b>' + getNiceSize3(message.totalmem) + '</b> ' + "total" + '</div>');
  17071. QS('serverMemoryChartView')['display'] = 'inline-block';
  17072. window.serverStatMemory.update();
  17073. } else if ((typeof message.totalmem == 'number') && (typeof message.freemem == 'number')) {
  17074. window.serverStatMemory.config.data.datasets[0].data = [message.totalmem - message.freemem, message.freemem];
  17075. QH('serverMemoryChartText', '<div style=margin-bottom:5px>' + "Memory" + '</div><div><b>' + getNiceSize3(message.freemem) + '</b> ' + "free" + ', <b>' + getNiceSize3(message.totalmem) + '</b> ' + "total" + '</div>');
  17076. QS('serverMemoryChartView')['display'] = 'inline-block';
  17077. window.serverStatMemory.update();
  17078. }
  17079. // Display all of the server values
  17080. var x = '<div style=width:100% cellpadding=0 cellspacing=0>';
  17081. if (typeof message.values == 'object') {
  17082. for (var i in message.values) {
  17083. x += '<div class=userTableHeader style=margin-bottom:4px;width:200px>' + (serverStatsStrings[i]?serverStatsStrings[i]:i) + '</div>';
  17084. for (var j in message.values[i]) {
  17085. x += '<div style=display:inline-block><table class=serverStateTableCell><tr><td class=h1></td><td><span>' + (serverStatsStrings[j]?serverStatsStrings[j]:j) + '</span><span style=float:right>' + message.values[i][j] + '</span></td><td class=h2></td></tr></table></div>';
  17086. }
  17087. }
  17088. }
  17089. x += '</div>';
  17090. QH('serverStatsTable', x);
  17091. }
  17092. //
  17093. // MyServer Stats
  17094. //
  17095. var serverTimelineStats = null;
  17096. var serverTimelineConfig = {
  17097. type: 'line',
  17098. data: { labels: [], datasets: [{ label: '', backgroundColor: 'rgba(255, 99, 132, .5)', borderColor: 'rgb(255, 99, 132)', data: [], fill: true }] },
  17099. options: {
  17100. animation: false,
  17101. responsive: true,
  17102. maintainAspectRatio: false,
  17103. elements: { line: { cubicInterpolationMode: 'monotone' } },
  17104. scales: {
  17105. x: { type: 'time', time: { tooltipFormat: 'll HH:mm' }, display: true, title: { display: false, text: '' } },
  17106. y: { type: 'linear', display: true, title: { display: true, text: '' }, stacked: true }
  17107. }
  17108. }
  17109. };
  17110. function refreshServerTimelineStats(stats) { if ((meshserver != null) && (meshserver.State == 2)) { meshserver.send({ action: 'servertimelinestats', hours: 24 * 30 }); } }
  17111. function pastDate(hours) { var t = new Date(); t.setTime(t.getTime() - (60 * 60 * 1000 * hours)); return t; }
  17112. function setServerTimelineStats(stats) { serverTimelineStats = stats; updateServerTimelineStats(); }
  17113. function addServerTimelineStats(stats) {
  17114. if (serverTimelineStats == null) return;
  17115. // Check if this new data is for our selected server
  17116. var selectedServer = null;
  17117. if (Q('p40server').value != null) {
  17118. selectedServer = decodeURIComponent(Q('p40server').value);
  17119. if (selectedServer == "") { selectedServer = null; }
  17120. }
  17121. if (stats.s != selectedServer) return;
  17122. serverTimelineStats.push(stats);
  17123. var chartType = Q('p40type').value;
  17124. if (chartType == 0) { // Connections
  17125. serverTimelineConfig.data.datasets[0].data.push({ x: stats.time, y: stats.conn.ca });
  17126. serverTimelineConfig.data.datasets[1].data.push({ x: stats.time, y: stats.conn.cu });
  17127. serverTimelineConfig.data.datasets[2].data.push({ x: stats.time, y: stats.conn.us });
  17128. serverTimelineConfig.data.datasets[3].data.push({ x: stats.time, y: stats.conn.rs });
  17129. serverTimelineConfig.data.datasets[4].data.push({ x: stats.time, y: stats.conn.am });
  17130. if (stats.conn.amc != null) { serverTimelineConfig.data.datasets[5].data.push({ x: stats.time, y: stats.conn.amc }); }
  17131. } else if (chartType == 1) { // Memory
  17132. serverTimelineConfig.data.datasets[0].data.push({ x: stats.time, y: stats.mem.external / (1024 * 1024) });
  17133. serverTimelineConfig.data.datasets[1].data.push({ x: stats.time, y: stats.mem.heapUsed / (1024 * 1024) });
  17134. serverTimelineConfig.data.datasets[2].data.push({ x: stats.time, y: stats.mem.heapTotal / (1024 * 1024) });
  17135. serverTimelineConfig.data.datasets[3].data.push({ x: stats.time, y: stats.mem.rss / (1024 * 1024) });
  17136. } else if ((chartType == 3) || (chartType == 4)) { // Traffic
  17137. updateServerTimelineStats();
  17138. } else if (chartType == 5) { // CPU
  17139. if ((typeof stats.cpu == 'object') && (typeof stats.cpu[0] == 'number')) { serverTimelineConfig.data.datasets[0].data.push({ x: stats.time, y: stats.cpu[0] }); }
  17140. }
  17141. /* else if (chartType == 2) {
  17142. serverTimelineConfig.data.datasets[0].data.push({ x: stats.time, y: stats.db.meshes });
  17143. serverTimelineConfig.data.datasets[1].data.push({ x: stats.time, y: stats.db.nodes });
  17144. serverTimelineConfig.data.datasets[2].data.push({ x: stats.time, y: stats.db.users });
  17145. serverTimelineConfig.data.datasets[3].data.push({ x: stats.time, y: stats.db.total });
  17146. } */
  17147. updateServerTimelineHours();
  17148. }
  17149. function updateServerTimelineHours() {
  17150. serverTimelineConfig.options.scales.y.type = (Q('p40log').checked ? 'logarithmic' : 'linear');
  17151. serverTimelineConfig.options.scales.x.min = pastDate(Q('p40time').value);
  17152. window.serverMainStats.update();
  17153. }
  17154. function setupServerTimelineStats() { window.serverMainStats = new Chart(document.getElementById('serverMainStats').getContext('2d'), serverTimelineConfig); }
  17155. const trafficSeriesNames = ["Agent", "CIRA", "AMT-OS", "HTTP", "Relay", "Terminal", "Desktop", "Files", "WebRDP", "WebSSH", "WebVNC", "Desktop Multiplex"];
  17156. const seriesColors = [[158, 151, 16], [16, 84, 158], [255, 99, 132], [39, 158, 16], [134, 16, 158], [0, 148, 255], [255, 216, 0], [255, 127, 237], [109, 213, 255], [89, 94, 255], [179, 104, 255], [179, 104, 255]];
  17157. function seriesColor1(n) { return 'rgba('+(seriesColors[n][0] + ((255 - seriesColors[n][0]) / 1.5))+','+(seriesColors[n][1] + ((255 - seriesColors[n][1]) / 1.5))+','+(seriesColors[n][2] + ((255 - seriesColors[n][2]) / 1.5))+')'; }
  17158. function seriesColor2(n) { return 'rgba('+seriesColors[n][0]+','+seriesColors[n][1]+','+seriesColors[n][2]+')'; }
  17159. function updateServerTimelineStats() {
  17160. if (serverTimelineStats == null) return;
  17161. var data, chartType = Q('p40type').value, timeAfter = pastDate(Q('p40time').value), servers = [], selectedServer = null, serverEmptyExists = false, serverAutoSelect = true;
  17162. if (Q('p40server').value != null) { selectedServer = decodeURIComponent(Q('p40server').value); if (selectedServer == "") { selectedServer = null; } serverAutoSelect = false; }
  17163. serverTimelineConfig.options.scales.x.min = timeAfter;
  17164. serverTimelineConfig.options.scales.y.stacked = ((chartType == 3) || (chartType == 4));
  17165. if (chartType == 0) { // Connections
  17166. serverTimelineConfig.options.scales.y.title.text = "Connection Count";
  17167. data = {
  17168. labels: [pastDate(0), timeAfter],
  17169. datasets: [
  17170. { label: "Agents", data: [], backgroundColor: 'rgba(158, 151, 16, .1)', borderColor: 'rgb(158, 151, 16)', fill: true },
  17171. { label: "Users", data: [], backgroundColor: 'rgba(16, 84, 158, .1)', borderColor: 'rgb(16, 84, 158)', fill: true },
  17172. { label: "User Sessions", data: [], backgroundColor: 'rgba(255, 99, 132, .1)', borderColor: 'rgb(255, 99, 132)', fill: true },
  17173. { label: "Relay Sessions", data: [], backgroundColor: 'rgba(39, 158, 16, .1)', borderColor: 'rgb(39, 158, 16)', fill: true },
  17174. { label: "Intel AMT", data: [], backgroundColor: 'rgba(134, 16, 158, .1)', borderColor: 'rgb(134, 16, 158)', fill: true },
  17175. { label: "Intel AMT CIRA", data: [], backgroundColor: 'rgba(255, 155, 0, .1)', borderColor: 'rgb(255, 155, 0)', fill: true }
  17176. ]
  17177. };
  17178. for (var i = 0; i < serverTimelineStats.length; i++) {
  17179. var t = new Date(serverTimelineStats[i].time);
  17180. if ((serverTimelineStats[i].s != null) && (servers.indexOf(serverTimelineStats[i].s) == -1)) {
  17181. servers.push(serverTimelineStats[i].s);
  17182. if (serverAutoSelect) { selectedServer = serverTimelineStats[i].s; serverAutoSelect = false; }
  17183. }
  17184. if (serverTimelineStats[i].s == null) { serverEmptyExists = true; }
  17185. if (serverTimelineStats[i].s != selectedServer) { continue; }
  17186. if (serverTimelineStats[i].first == true) {
  17187. data.datasets[0].data.push({ x: serverTimelineStats[i].time - 1, y: NaN });
  17188. data.datasets[1].data.push({ x: serverTimelineStats[i].time - 1, y: NaN });
  17189. data.datasets[2].data.push({ x: serverTimelineStats[i].time - 1, y: NaN });
  17190. data.datasets[3].data.push({ x: serverTimelineStats[i].time - 1, y: NaN });
  17191. data.datasets[4].data.push({ x: serverTimelineStats[i].time - 1, y: NaN });
  17192. data.datasets[5].data.push({ x: serverTimelineStats[i].time - 1, y: NaN });
  17193. }
  17194. if (serverTimelineStats[i].conn) {
  17195. data.datasets[0].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].conn.ca });
  17196. data.datasets[1].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].conn.cu });
  17197. data.datasets[2].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].conn.us });
  17198. data.datasets[3].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].conn.rs });
  17199. data.datasets[4].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].conn.am });
  17200. if (serverTimelineStats[i].conn.amc != null) { data.datasets[5].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].conn.amc }); }
  17201. }
  17202. }
  17203. } else if (chartType == 1) { // Memory
  17204. serverTimelineConfig.options.scales.y.title.text = "Megabytes";
  17205. data = {
  17206. labels: [pastDate(0), timeAfter],
  17207. datasets: [
  17208. { label: "External", data: [], backgroundColor: 'rgba(158, 151, 16, .1)', borderColor: 'rgb(158, 151, 16)', fill: true },
  17209. { label: "Heap Used", data: [], backgroundColor: 'rgba(16, 84, 158, .1)', borderColor: 'rgb(16, 84, 158)', fill: true },
  17210. { label: "Heap Total", data: [], backgroundColor: 'rgba(255, 99, 132, .1)', borderColor: 'rgb(255, 99, 132)', fill: true },
  17211. { label: "RSS", data: [], backgroundColor: 'rgba(39, 158, 16, .1)', borderColor: 'rgb(39, 158, 16)', fill: true }
  17212. ]
  17213. };
  17214. for (var i = 0; i < serverTimelineStats.length; i++) {
  17215. if ((serverTimelineStats[i].s != null) && (servers.indexOf(serverTimelineStats[i].s) == -1)) {
  17216. servers.push(serverTimelineStats[i].s);
  17217. if (serverAutoSelect) { selectedServer = serverTimelineStats[i].s; serverAutoSelect = false; }
  17218. }
  17219. if (serverTimelineStats[i].s == null) { serverEmptyExists = true; }
  17220. if (serverTimelineStats[i].s != selectedServer) { continue; }
  17221. if (serverTimelineStats[i].first == true) {
  17222. data.datasets[0].data.push({ x: serverTimelineStats[i].time - 1, y: NaN });
  17223. data.datasets[1].data.push({ x: serverTimelineStats[i].time - 1, y: NaN });
  17224. data.datasets[2].data.push({ x: serverTimelineStats[i].time - 1, y: NaN });
  17225. data.datasets[3].data.push({ x: serverTimelineStats[i].time - 1, y: NaN });
  17226. }
  17227. data.datasets[0].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].mem.external / (1024 * 1024) });
  17228. data.datasets[1].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].mem.heapUsed / (1024 * 1024) });
  17229. data.datasets[2].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].mem.heapTotal / (1024 * 1024) });
  17230. data.datasets[3].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].mem.rss / (1024 * 1024) });
  17231. }
  17232. } else if ((chartType == 3) || (chartType == 4)) { // Inbound/Outbound traffic
  17233. serverTimelineConfig.options.scales.y.title.text = "Megabytes";
  17234. data = { labels: [pastDate(0), timeAfter], datasets: [] };
  17235. var seriesWithData = 0;
  17236. // See that series have non-zero values
  17237. for (var i = 0; i < serverTimelineStats.length; i++) {
  17238. if (serverTimelineStats[i].traffic == null) continue;
  17239. if ((serverTimelineStats[i].s != null) && (servers.indexOf(serverTimelineStats[i].s) == -1)) {
  17240. servers.push(serverTimelineStats[i].s);
  17241. if (serverAutoSelect) { selectedServer = serverTimelineStats[i].s; serverAutoSelect = false; }
  17242. }
  17243. if (serverTimelineStats[i].s == null) { serverEmptyExists = true; }
  17244. if (serverTimelineStats[i].s != selectedServer) { continue; }
  17245. if (serverTimelineStats[i].traffic) {
  17246. if (serverTimelineStats[i].traffic.AgentCtrlIn > 0) { seriesWithData |= 0x0001; } // Agent control traffic
  17247. if (serverTimelineStats[i].traffic.CIRAIn > 0) { seriesWithData |= 0x0002; } // Intel AMT CIRA traffic
  17248. if (serverTimelineStats[i].traffic.LMSIn > 0) { seriesWithData |= 0x0004; } // Intel AMT LMS traffic
  17249. if (serverTimelineStats[i].traffic.httpIn > 0) { seriesWithData |= 0x0008; } // HTTP traffic
  17250. if (serverTimelineStats[i].traffic.relayIn) {
  17251. if (serverTimelineStats[i].traffic.relayIn[0] > 0) { seriesWithData |= 0x0010; } // Relay traffic = 0
  17252. if (serverTimelineStats[i].traffic.relayIn[1] > 0) { seriesWithData |= 0x0020; } // Terminal traffic = 1
  17253. if (serverTimelineStats[i].traffic.relayIn[2] > 0) { seriesWithData |= 0x0040; } // Desktop traffic = 2
  17254. if (serverTimelineStats[i].traffic.relayIn[5] > 0) { seriesWithData |= 0x0080; } // Files traffic = 5
  17255. if (serverTimelineStats[i].traffic.relayIn[10] > 0) { seriesWithData |= 0x0100; } // WebRDP traffic = 10
  17256. if (serverTimelineStats[i].traffic.relayIn[11] > 0) { seriesWithData |= 0x0200; } // WebSSH traffic = 11
  17257. if (serverTimelineStats[i].traffic.relayIn[12] > 0) { seriesWithData |= 0x0400; } // WebVNC traffic = 12
  17258. }
  17259. if (serverTimelineStats[i].traffic.desktopMultiplex) {
  17260. if (serverTimelineStats[i].traffic.desktopMultiplex.in > 0) { seriesWithData |= 0x0800; } // Desktop Multiplex traffic
  17261. }
  17262. }
  17263. }
  17264. // Setup data series labels
  17265. for (var i = 0; i < 13; i ++) { if (seriesWithData & (1 << i)) { data.datasets.push({ label: trafficSeriesNames[i], data: [], backgroundColor: seriesColor1(i), borderColor: seriesColor2(i), fill: true, steppedLine: true }); } }
  17266. // Place all of the series data
  17267. for (var i = 0; i < serverTimelineStats.length; i++) {
  17268. if (serverTimelineStats[i].traffic == null) continue;
  17269. if ((serverTimelineStats[i].s != null) && (servers.indexOf(serverTimelineStats[i].s) == -1)) {
  17270. servers.push(serverTimelineStats[i].s);
  17271. if (serverAutoSelect) { selectedServer = serverTimelineStats[i].s; serverAutoSelect = false; }
  17272. }
  17273. if (serverTimelineStats[i].s == null) { serverEmptyExists = true; }
  17274. if (serverTimelineStats[i].s != selectedServer) { continue; }
  17275. if (serverTimelineStats[i].first == true) { for (var j = 1; j < 0xF000; j = j << 1) { if (seriesWithData & j) { data.datasets[0].data.push({ x: serverTimelineStats[i].time - 1, y: NaN }); } } }
  17276. var z = 0;
  17277. if (chartType == 3) {
  17278. if (seriesWithData & 0x0001) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].traffic.AgentCtrlIn ? (serverTimelineStats[i].traffic.AgentCtrlIn / (1024 * 1024)) : 0 }); }
  17279. if (seriesWithData & 0x0002) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].traffic.CIRAIn ? (serverTimelineStats[i].traffic.CIRAIn / (1024 * 1024)) : 0 }); }
  17280. if (seriesWithData & 0x0004) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].traffic.LMSIn ? (serverTimelineStats[i].traffic.LMSIn / (1024 * 1024)) : 0 }); }
  17281. if (seriesWithData & 0x0008) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].traffic.httpIn ? (serverTimelineStats[i].traffic.httpIn / (1024 * 1024)) : 0 }); }
  17282. if (seriesWithData & 0x0010) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.relayIn && serverTimelineStats[i].traffic.relayIn[0]) ? (serverTimelineStats[i].traffic.relayIn[0] / 0x100000) : 0 }); }
  17283. if (seriesWithData & 0x0020) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.relayIn && serverTimelineStats[i].traffic.relayIn[1]) ? (serverTimelineStats[i].traffic.relayIn[1] / 0x100000) : 0 }); }
  17284. if (seriesWithData & 0x0040) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.relayIn && serverTimelineStats[i].traffic.relayIn[2]) ? (serverTimelineStats[i].traffic.relayIn[2] / 0x100000) : 0 }); }
  17285. if (seriesWithData & 0x0080) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.relayIn && serverTimelineStats[i].traffic.relayIn[5]) ? (serverTimelineStats[i].traffic.relayIn[5] / 0x100000) : 0 }); }
  17286. if (seriesWithData & 0x0100) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.relayIn && serverTimelineStats[i].traffic.relayIn[10]) ? (serverTimelineStats[i].traffic.relayIn[10] / 0x100000) : 0 }); }
  17287. if (seriesWithData & 0x0200) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.relayIn && serverTimelineStats[i].traffic.relayIn[11]) ? (serverTimelineStats[i].traffic.relayIn[11] / 0x100000) : 0 }); }
  17288. if (seriesWithData & 0x0400) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.relayIn && serverTimelineStats[i].traffic.relayIn[12]) ? (serverTimelineStats[i].traffic.relayIn[12] / 0x100000) : 0 }); }
  17289. if (seriesWithData & 0x0800) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.desktopMultiplex && serverTimelineStats[i].traffic.desktopMultiplex.in) ? (serverTimelineStats[i].traffic.desktopMultiplex['in'] / 0x100000) : 0 }); } // We have to put ['in'] here because the language translator will fail if using .in
  17290. } else if (chartType == 4) {
  17291. if (seriesWithData & 0x0001) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].traffic.AgentCtrlOut ? (serverTimelineStats[i].traffic.AgentCtrlOut / (1024 * 1024)) : 0 }); }
  17292. if (seriesWithData & 0x0002) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].traffic.CIRAOut ? (serverTimelineStats[i].traffic.CIRAOut / (1024 * 1024)) : 0 }); }
  17293. if (seriesWithData & 0x0004) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].traffic.LMSOut ? (serverTimelineStats[i].traffic.LMSOut / (1024 * 1024)) : 0 }); }
  17294. if (seriesWithData & 0x0008) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].traffic.httpOut ? (serverTimelineStats[i].traffic.httpOut / (1024 * 1024)) : 0 }); }
  17295. if (seriesWithData & 0x0010) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.relayOut && serverTimelineStats[i].traffic.relayOut[0]) ? (serverTimelineStats[i].traffic.relayOut[0] / 0x100000) : 0 }); }
  17296. if (seriesWithData & 0x0020) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.relayOut && serverTimelineStats[i].traffic.relayOut[1]) ? (serverTimelineStats[i].traffic.relayOut[1] / 0x100000) : 0 }); }
  17297. if (seriesWithData & 0x0040) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.relayOut && serverTimelineStats[i].traffic.relayOut[2]) ? (serverTimelineStats[i].traffic.relayOut[2] / 0x100000) : 0 }); }
  17298. if (seriesWithData & 0x0080) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.relayOut && serverTimelineStats[i].traffic.relayOut[5]) ? (serverTimelineStats[i].traffic.relayOut[5] / 0x100000) : 0 }); }
  17299. if (seriesWithData & 0x0100) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.relayOut && serverTimelineStats[i].traffic.relayOut[10]) ? (serverTimelineStats[i].traffic.relayOut[10] / 0x100000) : 0 }); }
  17300. if (seriesWithData & 0x0200) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.relayOut && serverTimelineStats[i].traffic.relayOut[11]) ? (serverTimelineStats[i].traffic.relayOut[11] / 0x100000) : 0 }); }
  17301. if (seriesWithData & 0x0400) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.relayOut && serverTimelineStats[i].traffic.relayOut[12]) ? (serverTimelineStats[i].traffic.relayOut[12] / 0x100000) : 0 }); }
  17302. if (seriesWithData & 0x0800) { data.datasets[z++].data.push({ x: serverTimelineStats[i].time, y: (serverTimelineStats[i].traffic.desktopMultiplex && serverTimelineStats[i].traffic.desktopMultiplex.out) ? (serverTimelineStats[i].traffic.desktopMultiplex.out / 0x100000) : 0 }); }
  17303. }
  17304. }
  17305. } else if (chartType == 5) { // CPU
  17306. serverTimelineConfig.options.scales.y.title.text = "Usage";
  17307. data = {
  17308. labels: [pastDate(0), timeAfter],
  17309. datasets: [
  17310. { label: "CPU", data: [], backgroundColor: 'rgba(158, 151, 16, .1)', borderColor: 'rgb(158, 151, 16)', fill: true }
  17311. ]
  17312. };
  17313. for (var i = 0; i < serverTimelineStats.length; i++) {
  17314. if ((typeof serverTimelineStats[i].cpu == 'object') && (typeof serverTimelineStats[i].cpu[0] == 'number')) {
  17315. if ((serverTimelineStats[i].s != null) && (servers.indexOf(serverTimelineStats[i].s) == -1)) {
  17316. servers.push(serverTimelineStats[i].s);
  17317. if (serverAutoSelect) { selectedServer = serverTimelineStats[i].s; serverAutoSelect = false; }
  17318. }
  17319. if (serverTimelineStats[i].s == null) { serverEmptyExists = true; }
  17320. if (serverTimelineStats[i].s != selectedServer) { continue; }
  17321. if (serverTimelineStats[i].first == true) {
  17322. data.datasets[0].data.push({ x: serverTimelineStats[i].time - 1, y: NaN });
  17323. }
  17324. data.datasets[0].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].cpu[0] });
  17325. }
  17326. }
  17327. }
  17328. /*else if (chartType == 2) { // Database
  17329. serverTimelineConfig.options.scales.y.title.text = 'Records';
  17330. data = {
  17331. labels: [pastDate(0), timeAfter],
  17332. datasets: [
  17333. { label: 'Groups', data: [], backgroundColor: 'rgba(158, 151, 16, .1)', borderColor: 'rgb(158, 151, 16)', fill: true },
  17334. { label: 'Devices', data: [], backgroundColor: 'rgba(16, 84, 158, .1)', borderColor: 'rgb(16, 84, 158)', fill: true },
  17335. { label: 'Users', data: [], backgroundColor: 'rgba(255, 99, 132, .1)', borderColor: 'rgb(255, 99, 132)', fill: true },
  17336. { label: 'Records', data: [], backgroundColor: 'rgba(39, 158, 16, .1)', borderColor: 'rgb(39, 158, 16)', fill: true }
  17337. ]
  17338. };
  17339. for (var i = 0; i < serverTimelineStats.length; i++) {
  17340. data.datasets[0].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].db.meshes });
  17341. data.datasets[1].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].db.nodes });
  17342. data.datasets[2].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].db.users });
  17343. data.datasets[3].data.push({ x: serverTimelineStats[i].time, y: serverTimelineStats[i].db.total });
  17344. }
  17345. }*/
  17346. serverTimelineConfig.data = data;
  17347. window.serverMainStats.update();
  17348. if (servers.length > 0) {
  17349. var x = '';
  17350. for (var i = 0; i < servers.length; i++) { x += '<option value="' + encodeURIComponentEx(servers[i]) + '"' + ((selectedServer == servers[i])?' selected':'') + '>' + EscapeHtml(servers[i]) + '</option>'; }
  17351. if (serverEmptyExists) { x += '<option value=""' + ((selectedServer == null)?' selected':'') + '>' + "Null" + '</option>'; }
  17352. QH('p40server', x);
  17353. }
  17354. QV('p40server', servers.length > 0);
  17355. }
  17356. function p40downloadEvents() {
  17357. var csv = "time, conn.agent, conn.users, conn.usersessions, conn.relaysession, conn.intelamt, mem.external, mem.heapused, mem.heaptotal, mem.rss" + '\r\n';
  17358. for (var i = 0; i < serverTimelineStats.length; i++) {
  17359. if (serverTimelineStats[i].conn && serverTimelineStats[i].mem) {
  17360. csv += new Date(serverTimelineStats[i].time) + ', ' + serverTimelineStats[i].conn.ca + ', ' + serverTimelineStats[i].conn.cu + ', ' + serverTimelineStats[i].conn.us + ', ' + serverTimelineStats[i].conn.rs + ', ' + (serverTimelineStats[i].conn.am ? serverTimelineStats[i].conn.am : '') + ', ' + serverTimelineStats[i].mem.external + ', ' + serverTimelineStats[i].mem.heapUsed + ', ' + serverTimelineStats[i].mem.heapTotal + ', ' + serverTimelineStats[i].mem.rss + '\r\n';
  17361. }
  17362. }
  17363. saveAs(stringToUtf8Blob(csv), "ServerStats.csv");
  17364. }
  17365. //
  17366. // My Server Tracing
  17367. //
  17368. var serverTrace = [];
  17369. var serverTraceSources = [];
  17370. function displayServerTrace() {
  17371. var x = '', max = parseInt(Q('p41limitdropdown').value);
  17372. if (serverTrace.length > max) { serverTrace.splice(max); }
  17373. for (var i in serverTrace) {
  17374. var args = [];
  17375. for (var j in serverTrace[i].args) { if (typeof serverTrace[i].args[j] == 'object') { args.push(JSON.stringify(serverTrace[i].args[j])); } else { args.push(serverTrace[i].args[j]); } }
  17376. x += '<div class=traceEvent onclick=showTraceEvent(' + i + ')>' + EscapeHtml(new Date(serverTrace[i].time).toLocaleTimeString()) + ' - <b>' + EscapeHtml(serverTrace[i].source.toUpperCase()) + '</b>: ' + EscapeHtml(args.join(', ')) + '</div>';
  17377. }
  17378. QH('p41events', x);
  17379. }
  17380. function showTraceEvent(i) {
  17381. var e = serverTrace[i], x = '';
  17382. if (xxdialogMode || (e == null)) return;
  17383. for (var i in e.args) {
  17384. if (typeof e.args[i] == 'object') { x += EscapeHtml(JSON.stringify(e.args[i])) + '<br /><br />'; } else { x += EscapeHtml(e.args[i]) + '<br /><br />'; }
  17385. }
  17386. setDialogMode(2, "Server Tracing Event", 1, null, '<div class=selecttext style=max-height:300px;overflow-y:auto>' + x + '</div>');
  17387. }
  17388. function clearServerTracing() { serverTrace = []; displayServerTrace(); }
  17389. function setServerTracing() {
  17390. var x = '<div style="max-height:200px;overflow-y:auto">';
  17391. x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px;margin-top:5px"><b>' + "Core Server" + '</b></div>';
  17392. x += '<div><label><input type=checkbox id=p41c1 ' + ((serverTraceSources.indexOf('cookie') >= 0) ? 'checked' : '') + '>' + "Cookie encoder" + '</label></div>';
  17393. x += '<div><label><input type=checkbox id=p41c2 ' + ((serverTraceSources.indexOf('dispatch') >= 0) ? 'checked' : '') + '>' + "Message Dispatcher" + '</label></div>';
  17394. x += '<div><label><input type=checkbox id=p41c3 ' + ((serverTraceSources.indexOf('main') >= 0) ? 'checked' : '') + '>' + "Main Server Messages" + '</label></div>';
  17395. x += '<div><label><input type=checkbox id=p41c4 ' + ((serverTraceSources.indexOf('peer') >= 0) ? 'checked' : '') + '>' + "MeshCentral Server Peering" + '</label></div>';
  17396. x += '<div><label><input type=checkbox id=p41c15 ' + ((serverTraceSources.indexOf('agent') >= 0) ? 'checked' : '') + '>' + "MeshAgent traffic" + '</label></div>';
  17397. x += '<div><label><input type=checkbox id=p41c14 ' + ((serverTraceSources.indexOf('agentupdate') >= 0) ? 'checked' : '') + '>' + "MeshAgent update" + '</label></div>';
  17398. x += '<div><label><input type=checkbox id=p41c16 ' + ((serverTraceSources.indexOf('cert') >= 0) ? 'checked' : '') + '>' + "Server Certificate" + '</label></div>';
  17399. x += '<div><label><input type=checkbox id=p41c17 ' + ((serverTraceSources.indexOf('db') >= 0) ? 'checked' : '') + '>' + "Server Database" + '</label></div>';
  17400. x += '<div><label><input type=checkbox id=p41c18 ' + ((serverTraceSources.indexOf('email') >= 0) ? 'checked' : '') + '>' + "Email/SMS/Push Traffic" + '</label></div>';
  17401. x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px;margin-top:5px"><b>' + "Web Server" + '</b></div>';
  17402. x += '<div><label><input type=checkbox id=p41c5 ' + ((serverTraceSources.indexOf('web') >= 0) ? 'checked' : '') + '>' + "Web Server" + '</label></div>';
  17403. x += '<div><label><input type=checkbox id=p41c6 ' + ((serverTraceSources.indexOf('webrequest') >= 0) ? 'checked' : '') + '>' + "Web Server Requests" + '</label></div>';
  17404. x += '<div><label><input type=checkbox id=p41c7 ' + ((serverTraceSources.indexOf('relay') >= 0) ? 'checked' : '') + '>' + "Web Socket Relay" + '</label></div>';
  17405. x += '<div><label><input type=checkbox id=p41c20 ' + ((serverTraceSources.indexOf('httpheaders') >= 0) ? 'checked' : '') + '>' + "Web Server HTTP Headers" + '</label></div>';
  17406. x += '<div><label><input type=checkbox id=p41c21 ' + ((serverTraceSources.indexOf('authlog') >= 0) ? 'checked' : '') + '>' + "User Authentication Log" + '</label></div>';
  17407. //x += '<div><label><input type=checkbox id=p41c8 ' + ((serverTraceSources.indexOf('webrelaydata') >= 0) ? 'checked' : '') + '>' + "Traffic Relay 2 Data" + '</label></div>';
  17408. x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px;margin-top:5px"><b>' + "Intel&reg; AMT" + '</b></div>';
  17409. x += '<div><label><input type=checkbox id=p41c19 ' + ((serverTraceSources.indexOf('amt') >= 0) ? 'checked' : '') + '>' + "Intel AMT manager" + '</label></div>';
  17410. x += '<div><label><input type=checkbox id=p41c9 ' + ((serverTraceSources.indexOf('webrelay') >= 0) ? 'checked' : '') + '>' + "Connection Relay" + '</label></div>';
  17411. x += '<div><label><input type=checkbox id=p41c10 ' + ((serverTraceSources.indexOf('mps') >= 0) ? 'checked' : '') + '>' + "CIRA Server" + '</label></div>';
  17412. x += '<div><label><input type=checkbox id=p41c11 ' + ((serverTraceSources.indexOf('mpscmd') >= 0) ? 'checked' : '') + '>' + "CIRA Server Commands" + '</label></div>';
  17413. //x += '<div style="width:100%;border-bottom:1px solid gray;margin-bottom:5px;margin-top:5px"><b>Legacy</b></div>';
  17414. //x += '<div><label><input type=checkbox id=p41c12 ' + ((serverTraceSources.indexOf('swarm') >= 0) ? 'checked' : '') + ">' + "Legacy Swarm Server" + '</label></div>";
  17415. //x += '<div><label><input type=checkbox id=p41c13 ' + ((serverTraceSources.indexOf('swarmcmd') >= 0) ? 'checked' : '') + ">' + "Legacy Swarm Server Commands" + '</label></div>";
  17416. x += '</div>';
  17417. setDialogMode(2, "Server Tracing", 7, setServerTracingEx, x);
  17418. }
  17419. function setServerTracingEx(b) {
  17420. var sources = [], allsources = ['cookie', 'dispatch', 'main', 'peer', 'web', 'webrequest', 'relay', 'webrelaydata', 'webrelay', 'mps', 'mpscmd', 'swarm', 'swarmcmd', 'agentupdate', 'agent', 'cert', 'db', 'email', 'amt', 'httpheaders', 'authlog'];
  17421. if (b == 1) { for (var i = 1; i < 22; i++) { try { if (Q('p41c' + i).checked) { sources.push(allsources[i - 1]); } } catch (ex) { } } }
  17422. meshserver.send({ action: 'traceinfo', traceSources: sources });
  17423. }
  17424. function p41downloadServerTrace() {
  17425. var csv = "time, source, message" + '\r\n';
  17426. for (var i in serverTrace) { csv += '"' + new Date(serverTrace[i].time).toLocaleTimeString() + '","' + serverTrace[i].source + '","' + serverTrace[i].args.join(', ') + '"\r\n'; }
  17427. saveAs(stringToUtf8Blob(csv), "servertrace.csv");
  17428. return false;
  17429. }
  17430. //
  17431. // SERVICE WORKER
  17432. //
  17433. function setupServiceWorker() {
  17434. if ((features2 & 8) == 0) return; // Web push not enabled on this server
  17435. if ((typeof serverinfo.vapidpublickey != 'string') || (Notification == null) || (Notification.permission != 'granted')) return;
  17436. // Register the service worker
  17437. if ('serviceWorker' in navigator) {
  17438. navigator.serviceWorker.register('serviceworker.js')
  17439. .then(function(reg) {
  17440. // Subscribe to push notifications
  17441. navigator.serviceWorker.ready.then(function(reg) {
  17442. reg.pushManager.subscribe({ applicationServerKey: urlBase64ToUint8Array(serverinfo.vapidpublickey), userVisibleOnly: true }).then(function(sub) {
  17443. meshserver.send({ action: 'webpush', sub: sub });
  17444. }).catch(function(e) { console.error('Worker: Unable to subscribe to push', e); });
  17445. })
  17446. }).catch(function(error) {
  17447. // Registration failed
  17448. console.log('Worker: Registration failed', error);
  17449. });
  17450. }
  17451. }
  17452. //
  17453. // POPUP DIALOG
  17454. //
  17455. // null = Hidden, 1 = Generic Message
  17456. var xxdialogMode;
  17457. var xxdialogFunc;
  17458. var xxdialogButtons;
  17459. var xxdialogTag;
  17460. var xxcurrentView = -1;
  17461. // Display a dialog box
  17462. // 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)
  17463. function setDialogMode(x, y, b, f, c, tag) {
  17464. setSessionActivity();
  17465. QV('uiMenu', false);
  17466. xxdialogMode = x;
  17467. xxdialogFunc = f;
  17468. xxdialogButtons = b;
  17469. xxdialogTag = tag;
  17470. // Reset dialog size
  17471. QS('dialog').width = QS('dialog').top = QS('dialog').left = QS('dialog').right = QS('dialog').bottom = null;
  17472. QE('idx_dlgOkButton', true);
  17473. QV('idx_dlgOkButton', b & 1);
  17474. QV('idx_dlgCancelButton', b & 2);
  17475. Q('idx_dlgCancelButton').value = ((b == 2)?"Close":"Cancel");
  17476. QV('id_dialogclose', (b & 2) || (b & 8));
  17477. QV('idx_dlgDeleteButton', b & 4);
  17478. QV('idx_dlgButtonBar', b & 7);
  17479. QV('idx_dlgMoreButtons', b & 16);
  17480. if (y) QH('id_dialogtitle', y);
  17481. for (var i = 1; i < 24; i++) { QV('dialog' + i, i == x); } // Edit this line when more dialogs are added
  17482. QV('dialog', x);
  17483. if (c) { if (x == 2) { QH('id_dialogOptions', c); } else { QH('id_dialogMessage', c); } }
  17484. MoreToggle(false);
  17485. }
  17486. function dialogclose(x) {
  17487. setSessionActivity();
  17488. var f = xxdialogFunc, b = xxdialogButtons, t = xxdialogTag;
  17489. setDialogMode();
  17490. if (((b & 8) || x) && f) f(x, t);
  17491. }
  17492. function center() {
  17493. setSessionActivity();
  17494. if (xxcurrentView == 11) { deskAdjust(); }
  17495. else if (xxcurrentView == 10) { mainUpdate(256); }
  17496. else if (xxcurrentView == 1) { mainUpdate(4); }
  17497. }
  17498. function messagebox(t, m) { setSessionActivity(); QH('id_dialogMessage', m); setDialogMode(1, t, 1); }
  17499. function statusbox(t, m) { setSessionActivity(); QH('id_dialogMessage', m); setDialogMode(1, t); }
  17500. function goBack() {
  17501. setSessionActivity();
  17502. if (xxdialogMode || (goBackStack.length == 0)) return;
  17503. if (fullscreen) { deskToggleFull(); }
  17504. go(goBackStack.pop());
  17505. goBackStack.pop();
  17506. }
  17507. function go(x, event) {
  17508. if(event && ((event.which == 3) || (event.button == 2))) return; // ignore right clicks as they are handled elsewhere
  17509. // Remind the user to add two factor authentication
  17510. if ((features & 0x00040000) && (count2factoraAuths() == 0) && (x > 2)) { x = 2; 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\" tab and look at the \"Account Security\" section."); }
  17511. if (pluginHandler != null) pluginHandler.callHook('goPageStart', x, event);
  17512. setSessionActivity();
  17513. if (xxdialogMode) return;
  17514. QV('uiMenu', false);
  17515. // If "shift" is pressed, open a new tab.
  17516. if (event && ((event.shiftKey == true) || (event.which == 2) || (event.button == 1)) && (x != 15) && ('{{{currentNode}}}'.toLowerCase() == '')) {
  17517. // Open the device in a different tab
  17518. if ((x >= 10) && (x <= 19)) {
  17519. if (currentNode) { safeNewWindow(window.location.origin + '{{{domainurl}}}' + '?gotonode=' + currentNode._id.split('/')[2] + '&viewmode=' + x + '&hide=16' + ((urlargs.key)?('&key=' + urlargs.key):''), 'meshcentral:' + currentNode._id); }
  17520. } else if ((x >= 20) && (x <= 29)) {
  17521. if (currentMesh) { safeNewWindow(window.location.origin + '{{{domainurl}}}' + '?gotomesh=' + currentMesh._id.split('/')[2] + '&viewmode=' + x + '&hide=16' + ((urlargs.key)?('&key=' + urlargs.key):''), 'meshcentral:' + currentMesh._id); }
  17522. } else if ((x >= 30) && (x <= 39)) {
  17523. if (currentUser) { safeNewWindow(window.location.origin + '{{{domainurl}}}' + '?gotouser=' + ((serverinfo.crossDomain)?currentUser._id:currentUser._id.split('/')[2]) + '&viewmode=' + x + '&hide=16' + ((urlargs.key)?('&key=' + urlargs.key):''), 'meshcentral:' + currentUser._id); }
  17524. } else if ((x >= 50) && (x <= 59)) {
  17525. if (currentUserGroup) { safeNewWindow(window.location.origin + '{{{domainurl}}}' + '?gotougrp=' + ((serverinfo.crossDomain)?currentUserGroup._id:currentUserGroup._id.split('/')[2]) + '&viewmode=' + x + '&hide=16' + ((urlargs.key)?('&key=' + urlargs.key):''), 'meshcentral:' + currentUserGroup._id); }
  17526. } else { // if (x < 10))
  17527. safeNewWindow(window.location.origin + '{{{domainurl}}}' + '?viewmode=' + x + '&hide=0' + ((urlargs.key)?('&key=' + urlargs.key):''), 'meshcentral:' + x);
  17528. }
  17529. return;
  17530. }
  17531. // If we are going to the same place, do nothing.
  17532. if (xxcurrentView == x) return;
  17533. // Set the goback stack, if going to top-level view, clear the stack.
  17534. if ((xxcurrentView < 0) || (x < 10)) { goBackStack = []; } else {
  17535. // Do not push into the back stack if we are changing tabs at the same level.
  17536. if ((xxcurrentView == 50) || (Math.floor(xxcurrentView / 10) != Math.floor(x / 10))) { goBackStack.push(xxcurrentView); }
  17537. }
  17538. // If we are recording the desktop, stop it now.
  17539. if ((xxcurrentView == 11) && (desktop != null) && (desktop.m.recordedData != null)) { deskRecordSession(); }
  17540. // If we are trying to go to "My Users" and we are not a user manager, move to recordings
  17541. if (((x == 4) && (userinfo != null) && ((userinfo.siteadmin & 2) == 0)) || ((features & 4) != 0)) { x = 52; }
  17542. // Stop the list graph if active
  17543. if (xxcurrentView == 17) deviceDetailsStatsClear();
  17544. // Edit this line when adding a new screen
  17545. for (var i = 0; i < 61; i++) { QV('p' + i, i == x); }
  17546. xxcurrentView = x;
  17547. // Get out of fullscreen if needed
  17548. if (fullscreen) { deskToggleFull(); }
  17549. // Change the URL
  17550. if (((features & 0x10000000) == 0) && (xxcurrentView > 0)) {
  17551. var urlviewmode = '';
  17552. if ((xxcurrentView >= 10) && (xxcurrentView <= 19)) { // Device Link
  17553. if (currentNode != null) { urlviewmode = '?viewmode=' + xxcurrentView + '&gotonode=' + currentNode._id.split('/')[2]; }
  17554. } else if ((xxcurrentView >= 20) && (xxcurrentView <= 29)) { // Device Group Link
  17555. if (currentMesh != null) { urlviewmode = '?viewmode=' + xxcurrentView + '&gotomesh=' + currentMesh._id.split('/')[2]; }
  17556. } else if ((xxcurrentView >= 30) && (xxcurrentView <= 39)) { // User Link
  17557. if (currentUser != null) { urlviewmode = '?viewmode=' + xxcurrentView + '&gotouser=' + ((serverinfo.crossDomain)?currentUser._id:currentUser._id.split('/')[2]); }
  17558. } else if ((xxcurrentView >= 51) && (xxcurrentView <= 51)) { // User Group Link
  17559. if ((currentUserGroup != null) && (currentUserGroup._id != null)) { urlviewmode = '?viewmode=' + xxcurrentView + '&gotougrp=' + ((serverinfo.crossDomain)?currentUserGroup._id:currentUserGroup._id.split('/')[2]); }
  17560. } else if (xxcurrentView > 1) { urlviewmode = '?viewmode=' + xxcurrentView; }
  17561. for (var i in urlargs) { urlviewmode += (((urlviewmode == '')?'?':'&') + i + '=' + urlargs[i]); }
  17562. try { window.history.replaceState({}, document.title, window.location.pathname + urlviewmode); } catch (ex) { }
  17563. }
  17564. // Remove top bar selection
  17565. var mainBarItems = ['MainMenuMyDevices', 'MainMenuMyAccount', 'MainMenuMyEvents', 'MainMenuMyFiles', 'MainMenuMyUsers', 'MainMenuMyServer'];
  17566. for (var i in mainBarItems) {
  17567. QC(mainBarItems[i]).remove('fullselect');
  17568. QC(mainBarItems[i]).remove('semiselect');
  17569. }
  17570. // Remove left bar selection
  17571. var leftBarItems = ['LeftMenuMyDevices', 'LeftMenuMyAccount', 'LeftMenuMyEvents', 'LeftMenuMyFiles', 'LeftMenuMyUsers', 'LeftMenuMyServer'];
  17572. for (var i in leftBarItems) {
  17573. QC(leftBarItems[i]).remove('lbbuttonsel');
  17574. QC(leftBarItems[i]).remove('lbbuttonsel2');
  17575. }
  17576. // Define class for Menu(s) as fully or semi active.
  17577. var mainMenuActiveClass = (x < 9 ? 'fullselect' : 'semiselect');
  17578. var leftMenuActiveClass = (((x < 9) || (x == 115) || (x == 40) || (x == 41) || (x == 42)) ? 'lbbuttonsel2' : 'lbbuttonsel');
  17579. var backView = 0;
  17580. if (goBackStack.length > 0) { backView = goBackStack[goBackStack.length - 1]; }
  17581. // My Devices
  17582. if (x == 1 || (backView == 1) || ((backView == 0) && (x >= 10 && x < 20))) {
  17583. QC('MainMenuMyDevices').add(mainMenuActiveClass);
  17584. QC('LeftMenuMyDevices').add(leftMenuActiveClass);
  17585. } else if (x == 2 || (backView == 2) || ((backView == 0) && (x >= 20 && x < 30))) {
  17586. // My Account
  17587. QC('MainMenuMyAccount').add(mainMenuActiveClass);
  17588. QC('LeftMenuMyAccount').add(leftMenuActiveClass);
  17589. } else if ((x == 3) || (x == 60)) {
  17590. // My Events
  17591. QC('MainMenuMyEvents').add(mainMenuActiveClass);
  17592. QC('LeftMenuMyEvents').add(leftMenuActiveClass);
  17593. } else if (x == 4 || (x >= 30 && x < 40) || (x == 50) || (x == 51) || (x == 52)) {
  17594. // My Users
  17595. QC('MainMenuMyUsers').add(mainMenuActiveClass);
  17596. QC('LeftMenuMyUsers').add(leftMenuActiveClass);
  17597. } else if (x == 5) {
  17598. // My Files
  17599. QC('MainMenuMyFiles').add(mainMenuActiveClass);
  17600. QC('LeftMenuMyFiles').add(leftMenuActiveClass);
  17601. } else if ((x == 6) || (x == 115) || (x >= 40 && x < 50)) {
  17602. // My Server
  17603. QC('MainMenuMyServer').add(mainMenuActiveClass);
  17604. QC('LeftMenuMyServer').add(leftMenuActiveClass);
  17605. }
  17606. QV('ServerPlugins', pluginHandler != null);
  17607. // column_l max-height
  17608. if (webPageStackMenu && (x >= 10)) { QC('column_l').add('room4submenu'); } else { QC('column_l').remove('room4submenu'); }
  17609. // If we are going to panel 0 in "full screen mode", hide the left bar.
  17610. QV('topbar', x != 0);
  17611. if ((x == 0) && (webPageFullScreen)) { QC('body').add('arg_hide'); }
  17612. QV('MainSubMenuSpan', (x >= 10) && (x < 20));
  17613. QV('UserDummyMenuSpan', (x == 51) || ((x < 10) && (x != 6) && webPageFullScreen));
  17614. QV('MeshSubMenuSpan', (x >= 20) && (x < 30));
  17615. QV('UserSubMenuSpan', (x >= 30) && (x < 40));
  17616. QV('ServerSubMenuSpan', x == 6 || x == 115 || x == 40 || x == 41 || x == 42 || x == 43);
  17617. QV('UsersSubMenuSpan', x == 4 || x == 50 || x == 52);
  17618. QV('EventsSubMenuSpan', (x == 3) || (x == 60));
  17619. var panels = { 3: 'EventsLive', 4: 'UsersGeneral', 10: 'MainDev', 11: 'MainDevDesktop', 12: 'MainDevTerminal', 13: 'MainDevFiles', 14: 'MainDevAmt', 15: 'MainDevConsole', 16: 'MainDevEvents', 17: 'MainDevInfo', 19: 'MainDevPlugins', 20: 'MeshGeneral', 21: 'MeshSummary', 30: 'UserGeneral', 31: 'UserEvents', 6: 'ServerGeneral', 40: 'ServerStats', 41: 'ServerTrace', 42: 'ServerPlugins', 50: 'UsersGroups', 52: 'UsersRecordings', 60: 'EventsReport', 115: 'ServerConsole' };
  17620. for (var i in panels) {
  17621. QC(panels[i]).remove('style3x');
  17622. QC(panels[i]).remove('style3sel');
  17623. QC(panels[i]).add((x == i) ? 'style3sel' : 'style3x');
  17624. }
  17625. // If going to the remote desktop tab, adjust the tab.
  17626. if (x == 11) { deskAdjust(); }
  17627. // Panel 115 is weird, it's panel 15 for device console but used as a server console.
  17628. if (x == 115) { QV('p15', true); }
  17629. QV('p15uploadCore', x != 115);
  17630. QV('p15BackButton', (x != 115) && ((args.hide & 32) == 0) && ('{{currentNode}}'.toLowerCase() == '')); // For device console, only show the back button if not hidden and not in single device view mode.
  17631. if ((x == 15) || (x == 115)) { setupConsole(); }
  17632. if (x == 1) mainUpdate(4);
  17633. // Setup web notifications
  17634. if ((x == 2) && Notification) { QV('accountEnableNotificationsSpan', Notification.permission != 'granted'); }
  17635. // Fetch the server timeline stats if needed
  17636. if ((x == 40) && (serverTimelineStats == null)) { refreshServerTimelineStats(); }
  17637. // MyServer Plugins
  17638. if (x == 42) { refreshPluginLatest(); }
  17639. // Update Mesh Summary
  17640. if (x == 21) { p21updateMesh(); }
  17641. // Update Recordings
  17642. if (x == 52) { if (p52recordings == null) { refreshRecodings(); } updateRecordings(); }
  17643. // Update terminal size
  17644. if ((x == 12) && (xtermfit != null)) { xtermfit.fit(); xterm.focus(); }
  17645. // Update the web page title
  17646. if ((currentNode) && (x >= 10) && (x < 20)) {
  17647. document.title = currentNode.name + ((meshes[currentNode.meshid])?(' - ' + meshes[currentNode.meshid].name):'') + ' - ' + decodeURIComponent('{{{extitle}}}');
  17648. } else {
  17649. document.title = decodeURIComponent('{{{extitle}}}');
  17650. }
  17651. if (pluginHandler != null) pluginHandler.callHook('goPageEnd', x, event);
  17652. // Some panels must not have scroll bars
  17653. QS('column_l')['overflow'] = ([11,12].indexOf(x)>= 0)?'hidden':null;
  17654. }
  17655. //
  17656. // Plugin Management
  17657. //
  17658. function updatePluginList(versInfo) {
  17659. if (pluginHandler == null) return;
  17660. if (Array.isArray(versInfo)) { versInfo.forEach(function(v) { updatePluginList(v); }); }
  17661. QV('pluginNoneNotice', installedPluginList.length == 0);
  17662. if (installedPluginList.length) {
  17663. if (versInfo != null) {
  17664. if (installedPluginList['version_info'] == null) installedPluginList['version_info'] = [];
  17665. installedPluginList['version_info'][versInfo.id] = versInfo;
  17666. }
  17667. var tr = Q('p42tbl').querySelectorAll('.p42tblRow');
  17668. if (tr.length) {
  17669. for (var i in Object.values(tr)) {
  17670. tr[i].parentNode.removeChild(tr[i]);
  17671. }
  17672. }
  17673. var statusMap = {
  17674. 0: {
  17675. 'text': 'Disabled',
  17676. 'color': '858483'
  17677. },
  17678. 1: {
  17679. 'text': 'Installed',
  17680. 'color': '00aa00'
  17681. }
  17682. };
  17683. var statusAvailability = {
  17684. 0: {
  17685. 'install': 'Install',
  17686. 'delete': 'Delete'
  17687. },
  17688. 1: {
  17689. 'disable': 'Disable',
  17690. 'upgrade': 'Upgrade',
  17691. // 'downgrade': 'Downgrade' // disabling until plugins have prior versions available for better testing
  17692. }
  17693. };
  17694. var vers_not_compat = ' [ <span onclick="return setDialogMode(2, \'Compatibility Issue\', 1, null, \'This plugin version is not compatible with your MeshCentral installation, please upgrade MeshCentral first.\');" title="' + "Version incompatible, please upgrade your MeshCentral installation first" + '" style="cursor: pointer; color:red;"> ! </span> ]';
  17695. var tbl = Q('p42tbl');
  17696. installedPluginList.forEach(function(p){
  17697. var cant_action = [];
  17698. if (p.hasAdminPanel == true && p.status) {
  17699. p.nameHtml = '<a onclick="return goPlugin(\'' + p.shortName + '\', \'' + p.name.replace(/'/g, "\\'") + '\');">' + EscapeHtml(p.name) + '</a>';
  17700. } else {
  17701. p.nameHtml = EscapeHtml(p.name);
  17702. }
  17703. p.statusText = statusMap[p.status].text;
  17704. p.statusColor = statusMap[p.status].color;
  17705. if (p.versionHistoryUrl == null) { cant_action.push('downgrade'); }
  17706. if (!p.status) { p.version = ' - '; } // It isn't technically installed, so no version number
  17707. p.upgradeAvail = "Checking...";
  17708. if (installedPluginList['version_info'] != null && installedPluginList['version_info'][p._id] != null) {
  17709. var vin = installedPluginList['version_info'][p._id];
  17710. if (vin.hasUpdate) {
  17711. p.upgradeAvail = '<a title="' + "View Changelog" + '" rel="noreferrer noopener" target="_blank" href="' + vin.changelogUrl + '">' + vin.version + '</a>';
  17712. } else {
  17713. cant_action.push('upgrade');
  17714. if (p.status) p.upgradeAvail = "Up to date";
  17715. else p.upgradeAvail = '<a title="' + "View Changelog" + '" rel="noreferrer noopener" target="_blank" href="' + vin.changelogUrl + '">' + vin.version + '</a>';
  17716. }
  17717. if (!vin.meshCentralCompat) {
  17718. p.upgradeAvail += vers_not_compat;
  17719. cant_action.push('install');
  17720. cant_action.push('upgrade');
  17721. }
  17722. }
  17723. p.actions = '<select onchange="return pluginAction(this,\'' + p._id + '\');"><option value=""> --</option>';
  17724. var entries = Object.entries(statusAvailability[p.status]);
  17725. for (var k in entries) {
  17726. if (cant_action.indexOf(entries[k][0]) === -1) {
  17727. p.actions += '<option value="' + entries[k][0] + '">' + entries[k][1] + '</option>';
  17728. }
  17729. }
  17730. p.actions += '</select>';
  17731. var tpl = '<td><img style=margin-top:3px src=images/plugin24.png></td><td class=gradTable1>&nbsp;</td><td class=gradTable2>' + p.nameHtml + '</td><td class=gradTable2>' + EscapeHtml(p.description) + '</td><td class=gradTable2 style=text-align:center><a href="' + EscapeHtml(p.homepage) + '" rel="noreferrer noopener" target="_blank">Home</a></td><td class=gradTable2 style=text-align:center>' + EscapeHtml(p.version) + '</td><td style=text-align:center class="pluginUpgradeAvailable gradTable2">' + p.upgradeAvail + '</td><td class=gradTable2 style="text-align:center;color:#' + p.statusColor + '">' + p.statusText + '</td><td class="pluginAction gradTable2" style=text-align:center>' + p.actions + '</td><td class=gradTable3>&nbsp;</td>';
  17732. var tr = tbl.insertRow(-1);
  17733. tr.innerHTML = tpl;
  17734. tr.classList.add('p42tblRow');
  17735. tr.setAttribute('data-id', p._id);
  17736. tr.setAttribute('id', 'pluginRow-' + p._id);
  17737. });
  17738. } else {
  17739. var tr = Q('p42tbl').querySelectorAll('.p42tblRow');
  17740. for (var i in Object.values(tr)) { tr[i].parentNode.removeChild(tr[i]); }
  17741. }
  17742. if (versInfo == null) refreshPluginLatest();
  17743. }
  17744. function refreshPluginLatest() {
  17745. if (pluginHandler == null) return;
  17746. meshserver.send({ action: 'pluginLatestCheck' });
  17747. }
  17748. function distributeCore() {
  17749. if (pluginHandler == null) return;
  17750. meshserver.send({ action: 'distributeCore', nodes: nodes }); // All nodes the user has access to
  17751. QV('pluginRestartNotice', false);
  17752. }
  17753. function pluginActionEx() {
  17754. if (pluginHandler == null) return;
  17755. var act = Q('lastPluginAct').value, id = Q('lastPluginId').value, pVersUrl = Q('lastPluginVersion').value;
  17756. switch(act) {
  17757. case 'upgrade':
  17758. case 'install':
  17759. meshserver.send({ 'action': 'installplugin', 'id': id, 'version_only': false });
  17760. break;
  17761. case 'downgrade':
  17762. Q('lastPluginVersion').querySelectorAll('option').forEach(function(opt) {
  17763. if (opt.value == pVersUrl) pVers = opt.text;
  17764. });
  17765. meshserver.send({ 'action': 'installplugin', 'id': id, 'version_only': { 'name': pVers, 'url': pVersUrl }});
  17766. break;
  17767. case 'delete':
  17768. meshserver.send({ 'action': 'removeplugin', 'id': id });
  17769. break;
  17770. case 'disable':
  17771. meshserver.send({ 'action': 'disableplugin', 'id': id });
  17772. break;
  17773. }
  17774. QV('pluginRestartNotice', true);
  17775. }
  17776. function pluginAction(elem, id) {
  17777. if (pluginHandler == null) return;
  17778. if (elem.value == 'downgrade') {
  17779. meshserver.send({ 'action': 'getpluginversions', 'id': id });
  17780. } else {
  17781. var plugin = null;
  17782. for (var i in installedPluginList) { if (installedPluginList[i]._id == id) { plugin = installedPluginList[i]; } }
  17783. setDialogMode(2, "Plugin Action", 3, pluginActionEx, format("Are you sure you want to {0} the plugin: {1}", elem.value, plugin.name) + '<input id="lastPluginAct" type="hidden" value="' + elem.value + '" /><input id="lastPluginId" type="hidden" value="' + id + '" /><input id="lastPluginVersion" type="hidden" value="" />');
  17784. }
  17785. elem.value = '';
  17786. }
  17787. function goPlugin(pname, title) {
  17788. if (pluginHandler == null) return;
  17789. if (pname == null) { Q('p43iframe').src = ''; } else { QH('p43title', title); Q('p43iframe').src = '/pluginadmin.ashx?pin=' + pname; go(43); }
  17790. }
  17791. //
  17792. // Access Control Functions
  17793. // These must match server
  17794. //
  17795. // Remove user rights
  17796. function removeUserRights(rights, userid) {
  17797. if ((userid != userinfo._id) || (userinfo.removeRights == null)) return rights;
  17798. var add = 0, substract = 0;
  17799. if ((userinfo.removeRights & 0x00000008) != 0) { substract += 0x00000008; } // No Remote Control
  17800. if ((userinfo.removeRights & 0x00010000) != 0) { add += 0x00010000; } // No Desktop
  17801. if ((userinfo.removeRights & 0x00000100) != 0) { add += 0x00000100; } // Desktop View Only
  17802. if ((userinfo.removeRights & 0x00000200) != 0) { add += 0x00000200; } // No Terminal
  17803. if ((userinfo.removeRights & 0x00000400) != 0) { add += 0x00000400; } // No Files
  17804. if ((userinfo.removeRights & 0x00000010) != 0) { substract += 0x00000010; } // No Console
  17805. if ((userinfo.removeRights & 0x00008000) != 0) { substract += 0x00008000; } // No Uninstall
  17806. if ((userinfo.removeRights & 0x00020000) != 0) { substract += 0x00020000; } // No Remote Command
  17807. if ((userinfo.removeRights & 0x00000040) != 0) { substract += 0x00000040; } // No Wake
  17808. if ((userinfo.removeRights & 0x00040000) != 0) { substract += 0x00040000; } // No Reset/Off
  17809. if (rights != 0xFFFFFFFF) {
  17810. // If not administrator, add and subsctract restrictions
  17811. rights |= add;
  17812. rights &= (0xFFFFFFFF - substract);
  17813. } else {
  17814. // If administrator for a device group, start with permissions and add and subsctract restrictions
  17815. rights = 1 + 2 + 4 + 8 + 32 + 64 + 128 + 16384 + 32768 + 131072 + 262144 + 524288 + 1048576;
  17816. rights |= add;
  17817. rights &= (0xFFFFFFFF - substract);
  17818. }
  17819. return rights;
  17820. }
  17821. // Get the right of a user on a given device group
  17822. function GetMeshRights(mesh, userid) {
  17823. if (mesh == null) { return 0; }
  17824. if (userid == null) { userid = userinfo._id; }
  17825. if (typeof mesh == 'string') { mesh = meshes[mesh] }
  17826. if ((mesh == null) || (mesh.links == null)) { return 0; }
  17827. // Check if super user
  17828. if (serverinfo.manageAllDeviceGroups && (userid == userinfo._id)) return removeUserRights(0xFFFFFFFF, userid);
  17829. // Check device group link permission
  17830. var rights = 0, r = mesh.links[userid];
  17831. if (r != null) {
  17832. if (r.rights == 0xFFFFFFFF) { return removeUserRights(0xFFFFFFFF, userid); } // User has full rights thru a device group link, stop here.
  17833. rights = r.rights;
  17834. }
  17835. // Check permissions thru user groups
  17836. var user = null;
  17837. if (userid == userinfo._id) { user = userinfo; } else { if (users != null) { user = users[userid]; } }
  17838. if (user != null) {
  17839. for (var i in user.links) {
  17840. if (i.startsWith('ugrp/')) {
  17841. r = mesh.links[i];
  17842. if (r != null) {
  17843. if (r.rights == 0xFFFFFFFF) { return removeUserRights(0xFFFFFFFF, userid); } // User has full rights thru a user group, stop here.
  17844. rights |= r.rights; // TODO: Deal with reverse permissions
  17845. }
  17846. }
  17847. }
  17848. }
  17849. return removeUserRights(rights, userid);
  17850. }
  17851. // Returns true if the user can view the given device group
  17852. function IsMeshViewable(mesh, userid) {
  17853. if (mesh == null) { return false; }
  17854. if (userid == null) { userid = userinfo._id; }
  17855. if (typeof mesh == 'string') { mesh = meshes[mesh] }
  17856. if ((mesh == null) || (mesh.links == null)) { return false; }
  17857. if (mesh.links[userid] != null) { return true; } // User has visilibity thru a direct link
  17858. // Check if user user
  17859. if (serverinfo.manageAllDeviceGroups && (userid == userinfo._id)) return true;
  17860. // Check permissions thru user groups
  17861. var user = null;
  17862. if (userid == userinfo._id) { user = userinfo; } else { if (users != null) { user = users[userid]; } }
  17863. if (user != null) {
  17864. for (var i in user.links) {
  17865. if ((i.startsWith('ugrp/')) && (mesh.links[i] != null)) { return true; } // User has visilibity thru a user group
  17866. }
  17867. }
  17868. return false;
  17869. }
  17870. // Return the user rights for a given node
  17871. function GetNodeRights(node, userid) {
  17872. if (node == null) { return 0; }
  17873. if (userid == null) { userid = userinfo._id; }
  17874. if (typeof node == 'string') { node = getNodeFromId(node); if (node == null) { return 0; } }
  17875. var r = GetMeshRights(node.meshid, userid);
  17876. if (r == 0xFFFFFFFF) return removeUserRights(r, userid);
  17877. // Check direct device rights using device data
  17878. if ((node.links != null) && (node.links[userid] != null)) { r |= node.links[userid].rights; } // TODO: Deal with reverse permissions
  17879. // Check direct device rights thru user groups
  17880. if ((node.links != null) && (userinfo.links != null)) {
  17881. for (var i in node.links) {
  17882. if (i.startsWith('ugrp/') && (userinfo.links[i] != null) && (node.links[i].rights != null)) { r |= node.links[i].rights; }
  17883. }
  17884. }
  17885. // Check direct device rights using user data
  17886. /*
  17887. var user = null;
  17888. if (userid == userinfo._id) { user = userinfo; } else { if (users != null) { user = users[userid]; } }
  17889. if ((user != null) && (user.links != null)) {
  17890. var r2 = user.links[node._id];
  17891. if (r2 != null) {
  17892. if (r2.rights == 0xFFFFFFFF) { return 0xFFFFFFFF; } // User has full rights thru a device link, stop here.
  17893. r |= r2.rights; // TODO: Deal with reverse permissions
  17894. }
  17895. }
  17896. */
  17897. return removeUserRights(r, userid);
  17898. }
  17899. // Return true if the device is visible to the user
  17900. function IsNodeViewable(node, userid) {
  17901. if (node == null) { return false; }
  17902. if (userid == null) { userid = userinfo._id; }
  17903. if (typeof node == 'string') { node = getNodeFromId(node); if (node == null) { return false; } }
  17904. if (IsMeshViewable(node.meshid, userid)) return true;
  17905. // Check direct device visibility using device data
  17906. if ((node.links != null) && (node.links[userid] != null)) { return true; }
  17907. // Check direct device visibility thru user groups
  17908. if ((node.links != null) && (userinfo.links != null)) {
  17909. for (var i in node.links) { if (i.startsWith('ugrp/') && (userinfo.links[i] != null) && (node.links[i].rights != null)) { return true; } }
  17910. }
  17911. return false;
  17912. }
  17913. //
  17914. // Generic methods
  17915. //
  17916. // Converts string to UTF8 byte array, polyfill for IE.
  17917. // Following method is code from Mozilla: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
  17918. if (typeof TextEncoder === 'undefined') {
  17919. window.TextEncoder=function TextEncoder(){};
  17920. TextEncoder.prototype.encode = function encode(str) {
  17921. 'use strict';
  17922. var Len = str.length, resPos = -1;
  17923. var resArr = typeof Uint8Array === 'undefined' ? new Array(Len * 1.5) : new Uint8Array(Len * 3);
  17924. for (var point=0, nextcode=0, i = 0; i !== Len; ) {
  17925. point = str.charCodeAt(i), i += 1;
  17926. if (point >= 0xD800 && point <= 0xDBFF) {
  17927. if (i === Len) { resArr[resPos += 1] = 0xef; resArr[resPos += 1] = 0xbf; resArr[resPos += 1] = 0xbd; break; }
  17928. nextcode = str.charCodeAt(i);
  17929. if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) {
  17930. point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000;
  17931. i += 1;
  17932. if (point > 0xffff) { resArr[resPos += 1] = (0x1e<<3) | (point>>>18); resArr[resPos += 1] = (0x2<<6) | ((point>>>12)&0x3f); resArr[resPos += 1] = (0x2<<6) | ((point>>>6)&0x3f); resArr[resPos += 1] = (0x2<<6) | (point&0x3f); continue; }
  17933. } else { resArr[resPos += 1] = 0xef; resArr[resPos += 1] = 0xbf; resArr[resPos += 1] = 0xbd; continue; }
  17934. }
  17935. if (point <= 0x007f) {
  17936. resArr[resPos += 1] = (0x0<<7) | point;
  17937. } else if (point <= 0x07ff) {
  17938. resArr[resPos += 1] = (0x6<<5) | (point>>>6); resArr[resPos += 1] = (0x2<<6) | (point&0x3f);
  17939. } else {
  17940. resArr[resPos += 1] = (0xe<<4) | (point>>>12); resArr[resPos += 1] = (0x2<<6) | ((point>>>6)&0x3f); resArr[resPos += 1] = (0x2<<6) | (point&0x3f);
  17941. }
  17942. }
  17943. if (typeof Uint8Array !== 'undefined') return resArr.subarray(0, resPos + 1);
  17944. resArr.length = resPos + 1;
  17945. return resArr;
  17946. };
  17947. TextEncoder.prototype.toString = function(){return '[object TextEncoder]'};
  17948. try {
  17949. Object.defineProperty(TextEncoder.prototype,'encoding',{
  17950. get:function(){ if(TextEncoder.prototype.isPrototypeOf(this)) return'utf-8'; else throw TypeError('Illegal invocation'); }
  17951. });
  17952. } catch(e) { TextEncoder.prototype.encoding = 'utf-8'; }
  17953. if (typeof Symbol!=='undefined')TextEncoder.prototype[Symbol.toStringTag]='TextEncoder';
  17954. }
  17955. // Used to convert Base64 public VAPID key to bytearray.
  17956. function urlBase64ToUint8Array(base64String) {
  17957. var padding = '='.repeat((4 - base64String.length % 4) % 4);
  17958. var base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
  17959. var rawData = atob(base64);
  17960. var outputArray = new Uint8Array(rawData.length);
  17961. for (var i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); }
  17962. return outputArray;
  17963. }
  17964. function putstore(name, val) {
  17965. try {
  17966. if ((typeof (localStorage) === 'undefined') || (localStorage.getItem(name) == val)) return;
  17967. if (val == null) { localStorage.removeItem(name); } else { localStorage.setItem(name, val); }
  17968. } catch (ex) { }
  17969. if (name[0] != '_') {
  17970. var s = {};
  17971. try {
  17972. for (var i = 0, len = localStorage.length; i < len; ++i) {
  17973. var k = localStorage.key(i);
  17974. if (k[0] != '_') {
  17975. s[k] = localStorage.getItem(k);
  17976. if ((k != 'desktopsettings') && (k != 'stars') && (k != 'deskKeyShortcuts') && (k != 'deskStrings') && (k != 'cmdopt') && (typeof s[k] == 'string') && (s[k].length > 64)) { delete s[k]; }
  17977. }
  17978. }
  17979. } catch (ex) {}
  17980. meshserver.send({ action: 'userWebState', state: JSON.stringify(s) });
  17981. }
  17982. }
  17983. // Convert a string into a UTF8 blob with the UTF8 header in front of it.
  17984. function stringToUtf8Blob(str) {
  17985. const bytes = new TextEncoder().encode(str);
  17986. var bytes2 = new Uint8Array(3 + bytes.length);
  17987. bytes2[0] = 0xEF; // This is the UTF-8 header for CSV files, add it to the start of the file.
  17988. bytes2[1] = 0xBB;
  17989. bytes2[2] = 0xBF;
  17990. for (var i = 0; i < bytes.length; i++) { bytes2[i + 3] = bytes[i]; }
  17991. return new Blob([bytes2], { type: 'application/octet-stream' }) // application/json;charset=utf-8
  17992. }
  17993. // Convert a string into a UTF8 blob
  17994. function stringToUtf8BlobNoHeader(str) {
  17995. return new Blob([new TextEncoder().encode(str)], { type: 'application/octet-stream' }) // application/json;charset=utf-8
  17996. }
  17997. function multiTranslate(s) { var i = s.indexOf(']|'); return s.substring(i + 2); } // Used when an English string can have different meanings, so "[MEANING]|word" is used instead as translation key.
  17998. function getLang() { if (navigator.languages != undefined) { return navigator.languages[0]; } return navigator.language; }
  17999. 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); }
  18000. 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; } }
  18001. function addLink(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/link5.png></span>'; }
  18002. function addLinkConditional(x, f, c) { if (c) return addLink(x, f); return x; }
  18003. 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>'; }
  18004. function addKeyLinkConditional(x, t, c) { if (c) return '<span title=\'' + t + '\'>' + x + ' <img class=hoverButton src=images/key16.png></span>'; return x }
  18005. function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
  18006. function addOption(q, t, i) { var option = document.createElement('option'); option.text = t; option.value = i; Q(q).add(option); }
  18007. function passwordcheck(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); }
  18008. function methodcheck(r) { if (r && r != null && r.Body && r.Body.ReturnValueStr != 'SUCCESS') { messagebox("Call Error", r.Header.Method + ': ' + r.Body.ReturnValueStr.replace('_', ' ')); return true; } return false; }
  18009. function TableStart() { return '<table cellpadding=0 cellspacing=0 style=width:100%;border-radius:8px><tr><td width=200px><p><td>'; }
  18010. function TableStart2() { return '<table cellpadding=0 cellspacing=0 style=width:100%;border-radius:8px><tr><td><p><td>'; }
  18011. function TableEntry(n, v) { return '<tr><td><p>' + n + '<td>' + v; }
  18012. function FullTable(x, e) { var r = TableStart(); for (i in x) { if (i && x[i]) r += TableEntry(i, x[i]); } return r + TableEnd(e); }
  18013. function TableEnd(n) { return '<tr><td colspan=2><p>' + (n?n:'') + '</table>'; }
  18014. function AddButton(v, f) { return '<input type=button value="' + v + '" onclick="' + f + '" style=margin:4px>'; }
  18015. function AddButton2(v, f) { return '<input type=button value="' + v + '" onclick="' + f + '">'; }
  18016. function AddRefreshButton(f) { return '<input type=button name=refreshbtn value=Refresh onclick="refreshButtons(false);' + f + '" style=margin:4px ' + (refreshButtonsState==false?'disabled':'') + '>'; }
  18017. function MoreStart() { return '<div id=idx_dlgMoreButtons3 style=display:none><hr>'; };
  18018. function MoreEnd() { return '</div>'; };
  18019. function MoreToggle(v) { QV('idx_dlgMoreButtons1',!v); QV('idx_dlgMoreButtons2',v); QV('idx_dlgMoreButtons3',v); }
  18020. function getSelectedOptions(sel) { var opts = [], opt; for (var i = 0, len = sel.options.length; i < len; i++) { opt = sel.options[i]; if (opt.selected) { opts.push(opt.value); } } return opts; }
  18021. function getInstance(x, y) { for (var i in x) { if (x[i]['InstanceID'] == y) return x[i]; } return null; }
  18022. function getItem(x, y, z) { for (var i in x) { if (x[i][y] == z) return x[i]; } return null; }
  18023. function guidToStr(g) { return g.substring(6, 8) + g.substring(4, 6) + g.substring(2, 4) + g.substring(0, 2) + '-' + g.substring(10, 12) + g.substring(8, 10) + '-' + g.substring(14, 16) + g.substring(12, 14) + '-' + g.substring(16, 20) + '-' + g.substring(20); }
  18024. function getUrlVars() { var j, hash, vars = [], hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&'); for (var i = 0; i < hashes.length; i++) { j = hashes[i].indexOf('='); if (j > 0) { vars[hashes[i].substring(0, j)] = hashes[i].substring(j + 1, hashes[i].length); } } return vars; }
  18025. //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; }
  18026. //function addHtmlValue(t, v) { return '<div style=height:20px><div style=float:right;width:220px><b>' + v + '</b></div><div>' + t + '</div></div>'; }
  18027. function addHtmlValue(t, v) { return '<table><td style=width:120px>' + t + '<td><b>' + v + '</b></table>'; }
  18028. function addHtmlValue2(t, v) { return '<div><div style=display:inline-block;float:right>' + v + '</div><div style=display:inline-block>' + t + '</div></div>'; }
  18029. function addHtmlValue3(t, v) { return '<div><b>' + t + '</b></div><div style=margin-left:16px>' + v + '</div>'; }
  18030. function addHtmlValue4(t, v) { return '<table style=width:100%><td style=width:120px>' + t + '<td style=text-align:right><b>' + v + '</b></table>'; }
  18031. function addHtmlValue5(t, v) { return '<div style=padding:4px><div style=display:inline-block;float:right><b>' + v + '</b></div><div style=display:inline-block>' + t + '</div></div>'; }
  18032. function focusTextBox(x) { setTimeout(function(){ Q(x).selectionStart = Q(x).selectionEnd = 65535; Q(x).focus(); }, 0); }
  18033. 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); } // New version
  18034. function isPrivateIP(a) { return (a.startsWith('10.') || a.startsWith('172.16.') || a.startsWith('192.168.')); }
  18035. function u2fSupported() { return (window.u2f && ((navigator.userAgent.indexOf('Chrome/') > 0) || (navigator.userAgent.indexOf('Firefox/') > 0) || (navigator.userAgent.indexOf('Opera/') > 0) || (navigator.userAgent.indexOf('Safari/') > 0))); }
  18036. function findOne(arr1, arr2) { if ((arr1 == null) || (arr2 == null)) return false; return arr2.some(function (v) { return arr1.indexOf(v) >= 0; }); };
  18037. function copyTextToClip(txt) { function selectElementText(e) { if (document.selection) { var range = document.body.createTextRange(); range.moveToElementText(e); range.select(); } else if (window.getSelection) { var range = document.createRange(); range.selectNode(e); window.getSelection().removeAllRanges(); window.getSelection().addRange(range); } } var e = document.createElement('DIV'); e.textContent = txt; document.body.appendChild(e); selectElementText(e); document.execCommand('copy'); e.remove(); }
  18038. function copyTextToClip2(txt) { function selectElementText(e) { if (document.selection) { var range = document.body.createTextRange(); range.moveToElementText(e); range.select(); } else if (window.getSelection) { var range = document.createRange(); range.selectNode(e); window.getSelection().removeAllRanges(); window.getSelection().addRange(range); } } var e = document.createElement('DIV'); e.textContent = decodeURIComponent(txt); document.body.appendChild(e); selectElementText(e); document.execCommand('copy'); e.remove(); }
  18039. function capitalizeFirstLetter(x) { return x.charAt(0).toUpperCase() + x.slice(1); }
  18040. function printDate(d) { return d.toLocaleDateString(args.locale); }
  18041. function printTime(d) { return d.toLocaleTimeString(args.locale); }
  18042. function printDateTime(d) { return d.toLocaleString(args.locale); }
  18043. function printFlexDateTime(d) { if (printDate(new Date()) == printDate(d)) { return printTime(d); } else { return printDateTime(d); } }
  18044. function addDetailItem(title, value, state) { return '<table style=width:100%><td>' + nobreak(title) + '<td style=text-align:right>' + value + '</table>'; }
  18045. 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; }); };
  18046. function addTextLink(subtext, text, link) { var i = text.toLowerCase().indexOf(subtext.toLowerCase()); if (i == -1) { return text; } return text.substring(0, i) + '<a href="' + link + '">' + subtext + '</a>' + text.substring(i + subtext.length); }
  18047. function getOrderedList(objList, oname) { var r = []; for (var i in objList) { r.push(objList[i]); } r.sort(function(a, b) { var aa = a[oname].toLowerCase(), bb = b[oname].toLowerCase(); if (aa > bb) return 1; if (aa < bb) return -1; return 0; }); return r; }
  18048. function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); }
  18049. function nobreak(x) { return x.split(' ').join('&nbsp;'); }
  18050. function pad2(num) { var s = '00' + num; return s.substr(s.length - 2); }
  18051. function encodeURIComponentEx(txt) { return encodeURIComponent(txt).replace(/'/g,'%27'); };
  18052. function getUserName(userid) {
  18053. var useridsplit = userid.split('/'), userid2 = useridsplit[0] + '/' + useridsplit[1] + '/' + useridsplit[2], guestname = '';
  18054. if ((useridsplit.length == 4) && (useridsplit[3].startsWith('guest:'))) { guestname = ' - ' + decode_utf8(atob(useridsplit[3].substring(6))); }
  18055. if (users && users[userid2] != null) { if (users[userid2].realname != null) return (users[userid2].realname + guestname); else return (users[userid2].name + guestname); }
  18056. if (currentNode && currentNode.links && currentNode.links[userid] && currentNode.links[userid].name != null) { return (currentNode.links[userid].name + guestname); }
  18057. if (userid == userinfo._id) { return (userinfo.name + guestname); }
  18058. 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); } } } }
  18059. 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); } } } }
  18060. return (useridsplit[2] + guestname);
  18061. }
  18062. function round(value, precision) { var multiplier = Math.pow(10, precision || 0); return Math.round(value * multiplier) / multiplier; }
  18063. function safeNewWindow(url, target) { var newWindow = window.open(url, target, 'noopener,noreferrer'); if (newWindow) { newWindow.opener = null; } }
  18064. 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); }
  18065. function isMacNode(node) { if ((node.mtype != 2) || (node.agent == null) || (node.agent.id == null)) return false; return ([11,16,29].indexOf(node.agent.id) >= 0); }
  18066. function downloadFile(link, name, closeDialog) {
  18067. var element = document.createElement('iframe');
  18068. element.style.cssText = 'display:none;width:0;height:0;border:0;visibility:hidden;';
  18069. element.src = link;
  18070. document.body.appendChild(element);
  18071. setTimeout(function() { try { if (element.parentNode) { document.body.removeChild(element); } } catch(e) { } }, 10000);
  18072. if (closeDialog) { setDialogMode(0); }
  18073. }
  18074. // Make the dialog box movable
  18075. function dialogBoxDrag() {
  18076. var elmnt = Q('dialog');
  18077. var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  18078. Q('dialogHeader').onmousedown = dragMouseDown;
  18079. function dragMouseDown(e) {
  18080. e = e || window.event;
  18081. e.preventDefault();
  18082. pos3 = e.clientX;
  18083. pos4 = e.clientY;
  18084. document.onmouseup = closeDragElement;
  18085. document.onmousemove = elementDrag;
  18086. }
  18087. function elementDrag(e) {
  18088. e = e || window.event;
  18089. e.preventDefault();
  18090. pos1 = pos3 - e.clientX;
  18091. pos2 = pos4 - e.clientY;
  18092. pos3 = e.clientX;
  18093. pos4 = e.clientY;
  18094. elmnt.style.top = (elmnt.offsetTop - pos2) + 'px';
  18095. elmnt.style.left = (elmnt.offsetLeft - pos1) + 'px';
  18096. }
  18097. function closeDragElement() {
  18098. document.onmouseup = null;
  18099. document.onmousemove = null;
  18100. }
  18101. }
  18102. // Request Confirmation if closing while a desktop, terminal session is active
  18103. window.addEventListener('beforeunload', function (e) {
  18104. if (((desktop != null) && (xxcurrentView == 11)) || ((terminal != null) && (xxcurrentView == 12))) { e.preventDefault(); e.returnValue = ''; }
  18105. });
  18106. </script>
  18107. </body>
  18108. </html>