main.js 186 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163
  1. // Main JavaScript file for the Netdata GUI.
  2. // Codacy declarations
  3. /* global NETDATA */
  4. // netdata snapshot data
  5. var netdataSnapshotData = null;
  6. // enable alarms checking and notifications
  7. var netdataShowAlarms = true;
  8. // enable registry updates
  9. var netdataRegistry = true;
  10. // forward definition only - not used here
  11. var netdataServer = undefined;
  12. var netdataServerStatic = undefined;
  13. var netdataCheckXSS = undefined;
  14. // control the welcome modal and analytics
  15. var this_is_demo = null;
  16. function escapeUserInputHTML(s) {
  17. return s.toString()
  18. .replace(/&/g, '&')
  19. .replace(/</g, '&lt;')
  20. .replace(/>/g, '&gt;')
  21. .replace(/"/g, '&quot;')
  22. .replace(/#/g, '&#35;')
  23. .replace(/'/g, '&#39;')
  24. .replace(/\(/g, '&#40;')
  25. .replace(/\)/g, '&#41;')
  26. .replace(/\//g, '&#47;');
  27. }
  28. function verifyURL(s) {
  29. if (typeof (s) === 'string' && (s.startsWith('http://') || s.startsWith('https://'))) {
  30. return s
  31. .replace(/'/g, '%22')
  32. .replace(/"/g, '%27')
  33. .replace(/\)/g, '%28')
  34. .replace(/\(/g, '%29');
  35. }
  36. console.log('invalid URL detected:');
  37. console.log(s);
  38. return 'javascript:alert("invalid url");';
  39. }
  40. // --------------------------------------------------------------------
  41. // urlOptions
  42. var urlOptions = {
  43. hash: '#',
  44. theme: null,
  45. help: null,
  46. mode: 'live', // 'live', 'print'
  47. update_always: false,
  48. pan_and_zoom: false,
  49. server: null,
  50. after: 0,
  51. before: 0,
  52. highlight: false,
  53. highlight_after: 0,
  54. highlight_before: 0,
  55. nowelcome: false,
  56. show_alarms: false,
  57. chart: null,
  58. family: null,
  59. alarm: null,
  60. alarm_unique_id: 0,
  61. alarm_id: 0,
  62. alarm_event_id: 0,
  63. alarm_when: 0,
  64. hasProperty: function (property) {
  65. // console.log('checking property ' + property + ' of type ' + typeof(this[property]));
  66. return typeof this[property] !== 'undefined';
  67. },
  68. genHash: function (forReload) {
  69. var hash = urlOptions.hash;
  70. if (urlOptions.pan_and_zoom === true) {
  71. hash += ';after=' + urlOptions.after.toString() +
  72. ';before=' + urlOptions.before.toString();
  73. }
  74. if (urlOptions.highlight === true) {
  75. hash += ';highlight_after=' + urlOptions.highlight_after.toString() +
  76. ';highlight_before=' + urlOptions.highlight_before.toString();
  77. }
  78. if (urlOptions.theme !== null) {
  79. hash += ';theme=' + urlOptions.theme.toString();
  80. }
  81. if (urlOptions.help !== null) {
  82. hash += ';help=' + urlOptions.help.toString();
  83. }
  84. if (urlOptions.update_always === true) {
  85. hash += ';update_always=true';
  86. }
  87. if (forReload === true && urlOptions.server !== null) {
  88. hash += ';server=' + urlOptions.server.toString();
  89. }
  90. if (urlOptions.mode !== 'live') {
  91. hash += ';mode=' + urlOptions.mode;
  92. }
  93. return hash;
  94. },
  95. parseHash: function () {
  96. var variables = document.location.hash.split(';');
  97. var len = variables.length;
  98. while (len--) {
  99. if (len !== 0) {
  100. var p = variables[len].split('=');
  101. if (urlOptions.hasProperty(p[0]) && typeof p[1] !== 'undefined') {
  102. urlOptions[p[0]] = decodeURIComponent(p[1]);
  103. }
  104. } else {
  105. if (variables[len].length > 0) {
  106. urlOptions.hash = variables[len];
  107. }
  108. }
  109. }
  110. var booleans = ['nowelcome', 'show_alarms', 'update_always'];
  111. len = booleans.length;
  112. while (len--) {
  113. if (urlOptions[booleans[len]] === 'true' || urlOptions[booleans[len]] === true || urlOptions[booleans[len]] === '1' || urlOptions[booleans[len]] === 1) {
  114. urlOptions[booleans[len]] = true;
  115. } else {
  116. urlOptions[booleans[len]] = false;
  117. }
  118. }
  119. var numeric = ['after', 'before', 'highlight_after', 'highlight_before', 'alarm_when'];
  120. len = numeric.length;
  121. while (len--) {
  122. if (typeof urlOptions[numeric[len]] === 'string') {
  123. try {
  124. urlOptions[numeric[len]] = parseInt(urlOptions[numeric[len]]);
  125. }
  126. catch (e) {
  127. console.log('failed to parse URL hash parameter ' + numeric[len]);
  128. urlOptions[numeric[len]] = 0;
  129. }
  130. }
  131. }
  132. if (urlOptions.alarm_when) {
  133. // if alarm_when exists, create after/before params
  134. // -/+ 2 minutes from the alarm, and reload the page
  135. const alarmTime = new Date(urlOptions.alarm_when * 1000).valueOf();
  136. const timeMarginMs = 120000; // 2 mins
  137. const after = alarmTime - timeMarginMs;
  138. const before = alarmTime + timeMarginMs;
  139. const newHash = document.location.hash.replace(
  140. /;alarm_when=[0-9]*/i,
  141. ";after=" + after + ";before=" + before,
  142. );
  143. history.replaceState(null, '', newHash);
  144. location.reload();
  145. }
  146. if (urlOptions.server !== null && urlOptions.server !== '') {
  147. netdataServerStatic = document.location.origin.toString() + document.location.pathname.toString();
  148. netdataServer = urlOptions.server;
  149. netdataCheckXSS = true;
  150. } else {
  151. urlOptions.server = null;
  152. }
  153. if (urlOptions.before > 0 && urlOptions.after > 0) {
  154. urlOptions.pan_and_zoom = true;
  155. urlOptions.nowelcome = true;
  156. } else {
  157. urlOptions.pan_and_zoom = false;
  158. }
  159. if (urlOptions.highlight_before > 0 && urlOptions.highlight_after > 0) {
  160. urlOptions.highlight = true;
  161. } else {
  162. urlOptions.highlight = false;
  163. }
  164. switch (urlOptions.mode) {
  165. case 'print':
  166. urlOptions.theme = 'white';
  167. urlOptions.welcome = false;
  168. urlOptions.help = false;
  169. urlOptions.show_alarms = false;
  170. if (urlOptions.pan_and_zoom === false) {
  171. urlOptions.pan_and_zoom = true;
  172. urlOptions.before = Date.now();
  173. urlOptions.after = urlOptions.before - 600000;
  174. }
  175. netdataShowAlarms = false;
  176. netdataRegistry = false;
  177. this_is_demo = false;
  178. break;
  179. case 'live':
  180. default:
  181. urlOptions.mode = 'live';
  182. break;
  183. }
  184. // console.log(urlOptions);
  185. },
  186. hashUpdate: function () {
  187. history.replaceState(null, '', urlOptions.genHash(true));
  188. },
  189. netdataPanAndZoomCallback: function (status, after, before) {
  190. //console.log(1);
  191. //console.log(new Error().stack);
  192. if (netdataSnapshotData === null) {
  193. urlOptions.pan_and_zoom = status;
  194. urlOptions.after = after;
  195. urlOptions.before = before;
  196. urlOptions.hashUpdate();
  197. }
  198. },
  199. netdataHighlightCallback: function (status, after, before) {
  200. //console.log(2);
  201. //console.log(new Error().stack);
  202. if (status === true && (after === null || before === null || after <= 0 || before <= 0 || after >= before)) {
  203. status = false;
  204. after = 0;
  205. before = 0;
  206. }
  207. if (netdataSnapshotData === null) {
  208. urlOptions.highlight = status;
  209. } else {
  210. urlOptions.highlight = false;
  211. }
  212. urlOptions.highlight_after = Math.round(after);
  213. urlOptions.highlight_before = Math.round(before);
  214. urlOptions.hashUpdate();
  215. var show_eye = NETDATA.globalChartUnderlay.hasViewport();
  216. if (status === true && after > 0 && before > 0 && after < before) {
  217. var d1 = NETDATA.dateTime.localeDateString(after);
  218. var d2 = NETDATA.dateTime.localeDateString(before);
  219. if (d1 === d2) {
  220. d2 = '';
  221. }
  222. document.getElementById('navbar-highlight-content').innerHTML =
  223. ((show_eye === true) ? '<span class="navbar-highlight-bar highlight-tooltip" onclick="urlOptions.showHighlight();" title="restore the highlighted view" data-toggle="tooltip" data-placement="bottom">' : '<span>').toString()
  224. + 'highlighted time-frame'
  225. + ' <b>' + d1 + ' <code>' + NETDATA.dateTime.localeTimeString(after) + '</code></b> to '
  226. + ' <b>' + d2 + ' <code>' + NETDATA.dateTime.localeTimeString(before) + '</code></b>, '
  227. + 'duration <b>' + NETDATA.seconds4human(Math.round((before - after) / 1000)) + '</b>'
  228. + '</span>'
  229. + '<span class="navbar-highlight-button-right highlight-tooltip" onclick="urlOptions.clearHighlight();" title="clear the highlighted time-frame" data-toggle="tooltip" data-placement="bottom"><i class="fas fa-times"></i></span>';
  230. $('.navbar-highlight').show();
  231. $('.highlight-tooltip').tooltip({
  232. html: true,
  233. delay: { show: 500, hide: 0 },
  234. container: 'body'
  235. });
  236. } else {
  237. $('.navbar-highlight').hide();
  238. }
  239. },
  240. clearHighlight: function () {
  241. NETDATA.globalChartUnderlay.clear();
  242. if (NETDATA.globalPanAndZoom.isActive() === true) {
  243. NETDATA.globalPanAndZoom.clearMaster();
  244. }
  245. },
  246. showHighlight: function () {
  247. NETDATA.globalChartUnderlay.focus();
  248. }
  249. };
  250. urlOptions.parseHash();
  251. // --------------------------------------------------------------------
  252. // check options that should be processed before loading netdata.js
  253. var localStorageTested = -1;
  254. function localStorageTest() {
  255. if (localStorageTested !== -1) {
  256. return localStorageTested;
  257. }
  258. if (typeof Storage !== "undefined" && typeof localStorage === 'object') {
  259. var test = 'test';
  260. try {
  261. localStorage.setItem(test, test);
  262. localStorage.removeItem(test);
  263. localStorageTested = true;
  264. }
  265. catch (e) {
  266. console.log(e);
  267. localStorageTested = false;
  268. }
  269. } else {
  270. localStorageTested = false;
  271. }
  272. return localStorageTested;
  273. }
  274. function loadLocalStorage(name) {
  275. var ret = null;
  276. try {
  277. if (localStorageTest() === true) {
  278. ret = localStorage.getItem(name);
  279. } else {
  280. console.log('localStorage is not available');
  281. }
  282. }
  283. catch (error) {
  284. console.log(error);
  285. return null;
  286. }
  287. if (typeof ret === 'undefined' || ret === null) {
  288. return null;
  289. }
  290. // console.log('loaded: ' + name.toString() + ' = ' + ret.toString());
  291. return ret;
  292. }
  293. function saveLocalStorage(name, value) {
  294. // console.log('saving: ' + name.toString() + ' = ' + value.toString());
  295. try {
  296. if (localStorageTest() === true) {
  297. localStorage.setItem(name, value.toString());
  298. return true;
  299. }
  300. }
  301. catch (error) {
  302. console.log(error);
  303. }
  304. return false;
  305. }
  306. function getTheme(def) {
  307. if (urlOptions.mode === 'print') {
  308. return 'white';
  309. }
  310. var ret = loadLocalStorage('netdataTheme');
  311. if (typeof ret === 'undefined' || ret === null || ret === 'undefined') {
  312. return def;
  313. } else {
  314. return ret;
  315. }
  316. }
  317. function setTheme(theme) {
  318. if (urlOptions.mode === 'print') {
  319. return false;
  320. }
  321. if (theme === netdataTheme) {
  322. return false;
  323. }
  324. return saveLocalStorage('netdataTheme', theme);
  325. }
  326. var netdataTheme = getTheme('slate');
  327. var netdataShowHelp = true;
  328. if (urlOptions.theme !== null) {
  329. setTheme(urlOptions.theme);
  330. netdataTheme = urlOptions.theme;
  331. } else {
  332. urlOptions.theme = netdataTheme;
  333. }
  334. if (urlOptions.help !== null) {
  335. saveLocalStorage('options.show_help', urlOptions.help);
  336. netdataShowHelp = urlOptions.help;
  337. } else {
  338. urlOptions.help = loadLocalStorage('options.show_help');
  339. }
  340. // --------------------------------------------------------------------
  341. // natural sorting
  342. // http://www.davekoelle.com/files/alphanum.js - LGPL
  343. function naturalSortChunkify(t) {
  344. var tz = [];
  345. var x = 0, y = -1, n = 0, i, j;
  346. while (i = (j = t.charAt(x++)).charCodeAt(0)) {
  347. var m = (i >= 48 && i <= 57);
  348. if (m !== n) {
  349. tz[++y] = "";
  350. n = m;
  351. }
  352. tz[y] += j;
  353. }
  354. return tz;
  355. }
  356. function naturalSortCompare(a, b) {
  357. var aa = naturalSortChunkify(a.toLowerCase());
  358. var bb = naturalSortChunkify(b.toLowerCase());
  359. for (var x = 0; aa[x] && bb[x]; x++) {
  360. if (aa[x] !== bb[x]) {
  361. var c = Number(aa[x]), d = Number(bb[x]);
  362. if (c.toString() === aa[x] && d.toString() === bb[x]) {
  363. return c - d;
  364. } else {
  365. return (aa[x] > bb[x]) ? 1 : -1;
  366. }
  367. }
  368. }
  369. return aa.length - bb.length;
  370. }
  371. // --------------------------------------------------------------------
  372. // saving files to client
  373. function saveTextToClient(data, filename) {
  374. var blob = new Blob([data], {
  375. type: 'application/octet-stream'
  376. });
  377. var url = URL.createObjectURL(blob);
  378. var link = document.createElement('a');
  379. link.setAttribute('href', url);
  380. link.setAttribute('download', filename);
  381. var el = document.getElementById('hiddenDownloadLinks');
  382. el.innerHTML = '';
  383. el.appendChild(link);
  384. setTimeout(function () {
  385. el.removeChild(link);
  386. URL.revokeObjectURL(url);
  387. }, 60);
  388. link.click();
  389. }
  390. function saveObjectToClient(data, filename) {
  391. saveTextToClient(JSON.stringify(data), filename);
  392. }
  393. // -----------------------------------------------------------------------------
  394. // registry call back to render my-netdata menu
  395. function toggleExpandIcon(svgEl) {
  396. if (svgEl.getAttribute('data-icon') === 'caret-down') {
  397. svgEl.setAttribute('data-icon', 'caret-up');
  398. } else {
  399. svgEl.setAttribute('data-icon', 'caret-down');
  400. }
  401. }
  402. function toggleAgentItem(e, guid) {
  403. e.stopPropagation();
  404. e.preventDefault();
  405. toggleExpandIcon(e.currentTarget.children[0]);
  406. const el = document.querySelector(`.agent-alternate-urls.agent-${guid}`);
  407. if (el) {
  408. el.classList.toggle('collapsed');
  409. }
  410. }
  411. // When you stream metrics from netdata to netdata, the receiving netdata now
  412. // has multiple host databases. It's own, and multiple mirrored. Mirrored databases
  413. // can be accessed with <http://localhost:19999/host/NAME/>
  414. const OLD_DASHBOARD_SUFFIX = "old"
  415. let isOldSuffix = true
  416. try {
  417. const currentScriptMainJs = document.currentScript;
  418. const mainJsSrc = currentScriptMainJs.getAttribute("src")
  419. isOldSuffix = mainJsSrc.startsWith("../main.js")
  420. } catch {
  421. console.warn("current script not detecting, assuming the dashboard is running with /old suffix")
  422. }
  423. function transformWithOldSuffix(url) {
  424. return isOldSuffix ? `../${url}` : url
  425. }
  426. function renderStreamedHosts(options) {
  427. let html = `<div class="info-item">Databases streamed to this agent</div>`;
  428. var base = document.location.origin.toString() +
  429. document.location.pathname.toString()
  430. .replace(isOldSuffix ? `/${OLD_DASHBOARD_SUFFIX}` : "", "");
  431. if (base.endsWith("/host/" + options.hostname + "/")) {
  432. base = base.substring(0, base.length - ("/host/" + options.hostname + "/").toString().length);
  433. }
  434. if (base.endsWith("/")) {
  435. base = base.substring(0, base.length - 1);
  436. }
  437. var master = options.hosts[0].hostname;
  438. // We sort a clone of options.hosts, to keep the master as the first element
  439. // for future calls.
  440. var sorted = options.hosts.slice(0).sort(function (a, b) {
  441. if (a.hostname === master) {
  442. return -1;
  443. }
  444. return naturalSortCompare(a.hostname, b.hostname);
  445. });
  446. let displayedDatabases = false;
  447. for (var s of sorted) {
  448. let url, icon;
  449. const hostname = s.hostname;
  450. if (myNetdataMenuFilterValue !== "") {
  451. if (!hostname.includes(myNetdataMenuFilterValue)) {
  452. continue;
  453. }
  454. }
  455. displayedDatabases = true;
  456. if (hostname === master) {
  457. url = isOldSuffix ? `${base}/${OLD_DASHBOARD_SUFFIX}/` : `${base}/`;
  458. icon = 'home';
  459. } else {
  460. url = isOldSuffix ? `${base}/host/${hostname}/${OLD_DASHBOARD_SUFFIX}/` : `${base}/host/${hostname}/`;
  461. icon = 'window-restore';
  462. }
  463. html += (
  464. `<div class="agent-item">
  465. <a class="registry_link" href="${url}#" onClick="return gotoHostedModalHandler('${url}');">
  466. <i class="fas fa-${icon}" style="color: #999;"></i>
  467. </a>
  468. <span class="__title" onClick="return gotoHostedModalHandler('${url}');">
  469. <a class="registry_link" href="${url}#">${hostname}</a>
  470. </span>
  471. <div></div>
  472. </div>`
  473. )
  474. }
  475. if (!displayedDatabases) {
  476. html += (
  477. `<div class="info-item">
  478. <i class="fas fa-filter"></i>
  479. <span style="margin-left: 8px">no databases match the filter criteria.<span>
  480. </div>`
  481. )
  482. }
  483. return html;
  484. }
  485. function renderMachines(machinesArray) {
  486. let html = `<div class="info-item">My nodes</div>`;
  487. if (machinesArray === null) {
  488. let ret = loadLocalStorage("registryCallback");
  489. if (ret) {
  490. machinesArray = JSON.parse(ret);
  491. console.log("failed to contact the registry - loaded registry data from browser local storage");
  492. }
  493. }
  494. let found = false;
  495. let displayedAgents = false;
  496. const maskedURL = NETDATA.registry.MASKED_DATA;
  497. if (machinesArray) {
  498. saveLocalStorage("registryCallback", JSON.stringify(machinesArray));
  499. var machines = machinesArray.sort(function (a, b) {
  500. return naturalSortCompare(a.name, b.name);
  501. });
  502. for (var machine of machines) {
  503. found = true;
  504. if (myNetdataMenuFilterValue !== "") {
  505. if (!machine.name.includes(myNetdataMenuFilterValue)) {
  506. continue;
  507. }
  508. }
  509. displayedAgents = true;
  510. const alternateUrlItems = (
  511. `<div class="agent-alternate-urls agent-${machine.guid} collapsed">
  512. ${machine.alternate_urls.reduce((str, url) => {
  513. if (url === maskedURL) {
  514. return str
  515. }
  516. return str + (
  517. `<div class="agent-item agent-item--alternate">
  518. <div></div>
  519. <a href="${url}" title="${url}">${truncateString(url, 64)}</a>
  520. <a href="#" onclick="deleteRegistryModalHandler('${machine.guid}', '${machine.name}', '${url}'); return false;">
  521. <i class="fas fa-trash" style="color: #777;"></i>
  522. </a>
  523. </div>`
  524. )
  525. },
  526. ''
  527. )}
  528. </div>`
  529. )
  530. html += (
  531. `<div class="agent-item agent-${machine.guid}">
  532. <i class="fas fa-chart-bar" color: #fff"></i>
  533. <span class="__title" onClick="return gotoServerModalHandler('${machine.guid}');">
  534. <a class="registry_link" href="${machine.url}#">${machine.name}</a>
  535. </span>
  536. <a href="#" onClick="toggleAgentItem(event, '${machine.guid}');">
  537. <i class="fas fa-caret-down" style="color: #999"></i>
  538. </a>
  539. </div>
  540. ${alternateUrlItems}`
  541. )
  542. }
  543. if (found && (!displayedAgents)) {
  544. html += (
  545. `<div class="info-item">
  546. <i class="fas fa-filter"></i>
  547. <span style="margin-left: 8px">zero nodes are matching the filter value.<span>
  548. </div>`
  549. )
  550. }
  551. }
  552. if (!found) {
  553. if (machines) {
  554. html += (
  555. `<div class="info-item">
  556. <a href="https://github.com/netdata/netdata/tree/master/registry#registry" target="_blank">Your nodes list is empty</a>
  557. </div>`
  558. )
  559. } else {
  560. html += (
  561. `<div class="info-item">
  562. <a href="https://github.com/netdata/netdata/tree/master/registry#registry" target="_blank">Failed to contact the registry</a>
  563. </div>`
  564. )
  565. }
  566. html += `<hr />`;
  567. html += `<div class="info-item">Demo netdata nodes</div>`;
  568. const demoServers = [
  569. { url: "//london.netdata.rocks/default.html", title: "UK - London (DigitalOcean.com)" },
  570. { url: "//newyork.netdata.rocks/default.html", title: "US - New York (DigitalOcean.com)" },
  571. { url: "//sanfrancisco.netdata.rocks/default.html", title: "US - San Francisco (DigitalOcean.com)" },
  572. { url: "//atlanta.netdata.rocks/default.html", title: "US - Atlanta (CDN77.com)" },
  573. { url: "//frankfurt.netdata.rocks/default.html", title: "Germany - Frankfurt (DigitalOcean.com)" },
  574. { url: "//toronto.netdata.rocks/default.html", title: "Canada - Toronto (DigitalOcean.com)" },
  575. { url: "//singapore.netdata.rocks/default.html", title: "Japan - Singapore (DigitalOcean.com)" },
  576. { url: "//bangalore.netdata.rocks/default.html", title: "India - Bangalore (DigitalOcean.com)" },
  577. ]
  578. for (var server of demoServers) {
  579. html += (
  580. `<div class="agent-item">
  581. <i class="fas fa-chart-bar" style="color: #fff"></i>
  582. <a href="${server.url}">${server.title}</a>
  583. <div></div>
  584. </div>
  585. `
  586. );
  587. }
  588. }
  589. return html;
  590. }
  591. function setMyNetdataMenu(html) {
  592. const el = document.getElementById('my-netdata-dropdown-content')
  593. el.innerHTML = html;
  594. }
  595. function clearMyNetdataMenu() {
  596. setMyNetdataMenu(`<div class="agent-item" style="white-space: nowrap">
  597. <i class="fas fa-hourglass-half"></i>
  598. Loading, please wait...
  599. <div></div>
  600. </div>`);
  601. }
  602. function errorMyNetdataMenu() {
  603. setMyNetdataMenu(`<div class="agent-item" style="padding: 0 8px">
  604. <i class="fas fa-exclamation-triangle" style="color: red"></i>
  605. Cannot load known Netdata agents from Netdata Cloud! Please make sure you have the latest version of Netdata.
  606. </div>`);
  607. }
  608. function restrictMyNetdataMenu() {
  609. setMyNetdataMenu(`<div class="info-item" style="white-space: nowrap">
  610. <span>Please <a href="#" onclick="signInDidClick(event); return false">sign in to netdata.cloud</a> to view your nodes!</span>
  611. <div></div>
  612. </div>`);
  613. }
  614. function openAuthenticatedUrl(url) {
  615. if (isSignedIn()) {
  616. window.open(url);
  617. } else {
  618. window.open(`${NETDATA.registry.cloudBaseURL}/account/sign-in-agent?id=${NETDATA.registry.machine_guid}&name=${encodeURIComponent(NETDATA.registry.hostname)}&origin=${encodeURIComponent(window.location.origin + "/")}&redirect_uri=${encodeURIComponent(window.location.origin + "/" + url)}`);
  619. }
  620. }
  621. function renderMyNetdataMenu(machinesArray) {
  622. const el = document.getElementById('my-netdata-dropdown-content');
  623. el.classList.add(`theme-${netdataTheme}`);
  624. if (machinesArray == registryAgents) {
  625. console.log("Rendering my-netdata menu from registry");
  626. } else {
  627. console.log("Rendering my-netdata menu from netdata.cloud", machinesArray);
  628. }
  629. let html = '';
  630. if (!isSignedIn()) {
  631. if (!NETDATA.registry.isRegistryEnabled()) {
  632. html += (
  633. `<div class="info-item" style="white-space: nowrap">
  634. <span>Please <a href="#" onclick="signInDidClick(event); return false">sign in to netdata.cloud</a> to view your nodes!</span>
  635. <div></div>
  636. </div>
  637. <hr />`
  638. );
  639. }
  640. }
  641. if (isSignedIn()) {
  642. html += (
  643. `<div class="filter-control">
  644. <input
  645. id="my-netdata-menu-filter-input"
  646. type="text"
  647. placeholder="filter nodes..."
  648. autofocus
  649. autocomplete="off"
  650. value="${myNetdataMenuFilterValue}"
  651. onkeydown="myNetdataFilterDidChange(event)"
  652. />
  653. <span class="filter-control__clear" onclick="myNetdataFilterClearDidClick(event)"><i class="fas fa-times"></i><span>
  654. </div>
  655. <hr />`
  656. );
  657. }
  658. // options.hosts = [
  659. // {
  660. // hostname: "streamed1",
  661. // },
  662. // {
  663. // hostname: "streamed2",
  664. // },
  665. // ]
  666. if (options.hosts.length > 1) {
  667. html += `<div id="my-netdata-menu-streamed">${renderStreamedHosts(options)}</div><hr />`;
  668. }
  669. if (isSignedIn() || NETDATA.registry.isRegistryEnabled()) {
  670. html += `<div id="my-netdata-menu-machines">${renderMachines(machinesArray)}</div><hr />`;
  671. }
  672. if (!isSignedIn()) {
  673. html += (
  674. `<div class="agent-item">
  675. <i class="fas fa-cog""></i>
  676. <a href="#" onclick="switchRegistryModalHandler(); return false;">Switch Identity</a>
  677. <div></div>
  678. </div>
  679. <div class="agent-item">
  680. <i class="fas fa-question-circle""></i>
  681. <a href="https://github.com/netdata/netdata/tree/master/registry#registry" target="_blank">What is this?</a>
  682. <div></div>
  683. </div>`
  684. )
  685. } else {
  686. html += (
  687. `<div class="agent-item">
  688. <i class="fas fa-tv"></i>
  689. <a onclick="openAuthenticatedUrl('console.html');" target="_blank">Nodes<sup class="beta"> beta</sup></a>
  690. <div></div>
  691. </div>
  692. <div class="agent-item">
  693. <i class="fas fa-sync"></i>
  694. <a href="#" onclick="showSyncModal(); return false">Synchronize with netdata.cloud</a>
  695. <div></div>
  696. </div>
  697. <div class="agent-item">
  698. <i class="fas fa-question-circle""></i>
  699. <a href="https://netdata.cloud/about" target="_blank">What is this?</a>
  700. <div></div>
  701. </div>`
  702. )
  703. }
  704. el.innerHTML = html;
  705. gotoServerInit();
  706. }
  707. function isdemo() {
  708. if (this_is_demo !== null) {
  709. return this_is_demo;
  710. }
  711. this_is_demo = false;
  712. try {
  713. if (typeof document.location.hostname === 'string') {
  714. if (document.location.hostname.endsWith('.my-netdata.io') ||
  715. document.location.hostname.endsWith('.mynetdata.io') ||
  716. document.location.hostname.endsWith('.netdata.rocks') ||
  717. document.location.hostname.endsWith('.netdata.ai') ||
  718. document.location.hostname.endsWith('.netdata.live') ||
  719. document.location.hostname.endsWith('.firehol.org') ||
  720. document.location.hostname.endsWith('.netdata.online') ||
  721. document.location.hostname.endsWith('.netdata.cloud')) {
  722. this_is_demo = true;
  723. }
  724. }
  725. }
  726. catch (error) {
  727. }
  728. return this_is_demo;
  729. }
  730. function netdataURL(url, forReload) {
  731. if (typeof url === 'undefined')
  732. // url = document.location.toString();
  733. {
  734. url = '';
  735. }
  736. if (url.indexOf('#') !== -1) {
  737. url = url.substring(0, url.indexOf('#'));
  738. }
  739. var hash = urlOptions.genHash(forReload);
  740. // console.log('netdataURL: ' + url + hash);
  741. return url + hash;
  742. }
  743. function netdataReload(url) {
  744. document.location = verifyURL(netdataURL(url, true));
  745. // since we play with hash
  746. // this is needed to reload the page
  747. location.reload();
  748. }
  749. function gotoHostedModalHandler(url) {
  750. document.location = verifyURL(url + urlOptions.genHash());
  751. return false;
  752. }
  753. var gotoServerValidateRemaining = 0;
  754. var gotoServerMiddleClick = false;
  755. var gotoServerStop = false;
  756. function gotoServerValidateUrl(id, guid, url) {
  757. var penalty = 0;
  758. var error = 'failed';
  759. if (document.location.toString().startsWith('http://') && url.toString().startsWith('https://'))
  760. // we penalize https only if the current url is http
  761. // to allow the user walk through all its servers.
  762. {
  763. penalty = 500;
  764. } else if (document.location.toString().startsWith('https://') && url.toString().startsWith('http://')) {
  765. error = 'can\'t check';
  766. }
  767. var finalURL = netdataURL(url);
  768. setTimeout(function () {
  769. document.getElementById('gotoServerList').innerHTML += '<tr><td style="padding-left: 20px;"><a href="' + verifyURL(finalURL) + '" target="_blank">' + escapeUserInputHTML(url) + '</a></td><td style="padding-left: 30px;"><code id="' + guid + '-' + id + '-status">checking...</code></td></tr>';
  770. NETDATA.registry.hello(url, function (data) {
  771. if (typeof data !== 'undefined' && data !== null && typeof data.machine_guid === 'string' && data.machine_guid === guid) {
  772. // console.log('OK ' + id + ' URL: ' + url);
  773. document.getElementById(guid + '-' + id + '-status').innerHTML = "OK";
  774. if (!gotoServerStop) {
  775. gotoServerStop = true;
  776. if (gotoServerMiddleClick) {
  777. window.open(verifyURL(finalURL), '_blank');
  778. gotoServerMiddleClick = false;
  779. document.getElementById('gotoServerResponse').innerHTML = '<b>Opening new window to ' + NETDATA.registry.machines[guid].name + '<br/><a href="' + verifyURL(finalURL) + '">' + escapeUserInputHTML(url) + '</a></b><br/>(check your pop-up blocker if it fails)';
  780. } else {
  781. document.getElementById('gotoServerResponse').innerHTML += 'found it! It is at:<br/><small>' + escapeUserInputHTML(url) + '</small>';
  782. document.location = verifyURL(finalURL);
  783. $('#gotoServerModal').modal('hide');
  784. }
  785. }
  786. } else {
  787. if (typeof data !== 'undefined' && data !== null && typeof data.machine_guid === 'string' && data.machine_guid !== guid) {
  788. error = 'wrong machine';
  789. }
  790. document.getElementById(guid + '-' + id + '-status').innerHTML = error;
  791. gotoServerValidateRemaining--;
  792. if (gotoServerValidateRemaining <= 0) {
  793. gotoServerMiddleClick = false;
  794. document.getElementById('gotoServerResponse').innerHTML = '<b>Sorry! I cannot find any operational URL for this server</b>';
  795. }
  796. }
  797. });
  798. }, (id * 50) + penalty);
  799. }
  800. function gotoServerModalHandler(guid) {
  801. // console.log('goto server: ' + guid);
  802. gotoServerStop = false;
  803. var checked = {};
  804. var len = NETDATA.registry.machines[guid].alternate_urls.length;
  805. var count = 0;
  806. document.getElementById('gotoServerResponse').innerHTML = '';
  807. document.getElementById('gotoServerList').innerHTML = '';
  808. document.getElementById('gotoServerName').innerHTML = NETDATA.registry.machines[guid].name;
  809. $('#gotoServerModal').modal('show');
  810. gotoServerValidateRemaining = len;
  811. while (len--) {
  812. var url = NETDATA.registry.machines[guid].alternate_urls[len];
  813. checked[url] = true;
  814. gotoServerValidateUrl(count++, guid, url);
  815. }
  816. if (!isSignedIn()) {
  817. // When the registry is enabled, if the user's known URLs are not working
  818. // we consult the registry to get additional URLs.
  819. setTimeout(function () {
  820. if (gotoServerStop === false) {
  821. document.getElementById('gotoServerResponse').innerHTML = '<b>Added all the known URLs for this machine.</b>';
  822. NETDATA.registry.search(guid, function (data) {
  823. // console.log(data);
  824. len = data.urls.length;
  825. while (len--) {
  826. var url = data.urls[len][1];
  827. // console.log(url);
  828. if (typeof checked[url] === 'undefined') {
  829. gotoServerValidateRemaining++;
  830. checked[url] = true;
  831. gotoServerValidateUrl(count++, guid, url);
  832. }
  833. }
  834. });
  835. }
  836. }, 2000);
  837. }
  838. return false;
  839. }
  840. function gotoServerInit() {
  841. $(".registry_link").on('click', function (e) {
  842. if (e.which === 2) {
  843. e.preventDefault();
  844. gotoServerMiddleClick = true;
  845. } else {
  846. gotoServerMiddleClick = false;
  847. }
  848. return true;
  849. });
  850. }
  851. function switchRegistryModalHandler() {
  852. document.getElementById('switchRegistryPersonGUID').value = NETDATA.registry.person_guid;
  853. document.getElementById('switchRegistryURL').innerHTML = NETDATA.registry.server;
  854. document.getElementById('switchRegistryResponse').innerHTML = '';
  855. $('#switchRegistryModal').modal('show');
  856. }
  857. function notifyForSwitchRegistry() {
  858. var n = document.getElementById('switchRegistryPersonGUID').value;
  859. if (n !== '' && n.length === 36) {
  860. NETDATA.registry.switch(n, function (result) {
  861. if (result !== null) {
  862. $('#switchRegistryModal').modal('hide');
  863. NETDATA.registry.init();
  864. } else {
  865. document.getElementById('switchRegistryResponse').innerHTML = "<b>Sorry! The registry rejected your request.</b>";
  866. }
  867. });
  868. } else {
  869. document.getElementById('switchRegistryResponse').innerHTML = "<b>The ID you have entered is not a GUID.</b>";
  870. }
  871. }
  872. var deleteRegistryGuid = null;
  873. var deleteRegistryUrl = null;
  874. function deleteRegistryModalHandler(guid, name, url) {
  875. // void (guid);
  876. deleteRegistryGuid = guid;
  877. deleteRegistryUrl = url;
  878. document.getElementById('deleteRegistryServerName').innerHTML = name;
  879. document.getElementById('deleteRegistryServerName2').innerHTML = name;
  880. document.getElementById('deleteRegistryServerURL').innerHTML = url;
  881. document.getElementById('deleteRegistryResponse').innerHTML = '';
  882. $('#deleteRegistryModal').modal('show');
  883. }
  884. function notifyForDeleteRegistry() {
  885. const responseEl = document.getElementById('deleteRegistryResponse');
  886. if (deleteRegistryUrl) {
  887. if (isSignedIn()) {
  888. deleteCloudAgentURL(deleteRegistryGuid, deleteRegistryUrl)
  889. .then((count) => {
  890. if (!count) {
  891. responseEl.innerHTML = "<b>Sorry, this command was rejected by netdata.cloud!</b>";
  892. return;
  893. }
  894. NETDATA.registry.delete(deleteRegistryUrl, function (result) {
  895. if (result === null) {
  896. console.log("Received error from registry", result);
  897. }
  898. deleteRegistryUrl = null;
  899. $('#deleteRegistryModal').modal('hide');
  900. NETDATA.registry.init();
  901. });
  902. });
  903. } else {
  904. NETDATA.registry.delete(deleteRegistryUrl, function (result) {
  905. if (result !== null) {
  906. deleteRegistryUrl = null;
  907. $('#deleteRegistryModal').modal('hide');
  908. NETDATA.registry.init();
  909. } else {
  910. responseEl.innerHTML = "<b>Sorry, this command was rejected by the registry server!</b>";
  911. }
  912. });
  913. }
  914. }
  915. }
  916. var options = {
  917. menus: {},
  918. submenu_names: {},
  919. data: null,
  920. hostname: 'netdata_server', // will be overwritten by the netdata server
  921. version: 'unknown',
  922. release_channel: 'unknown',
  923. hosts: [],
  924. duration: 0, // the default duration of the charts
  925. update_every: 1,
  926. chartsPerRow: 0,
  927. // chartsMinWidth: 1450,
  928. chartsHeight: 180,
  929. };
  930. function chartsPerRow(total) {
  931. void (total);
  932. if (options.chartsPerRow === 0) {
  933. return 1;
  934. //var width = Math.floor(total / options.chartsMinWidth);
  935. //if(width === 0) width = 1;
  936. //return width;
  937. } else {
  938. return options.chartsPerRow;
  939. }
  940. }
  941. function prioritySort(a, b) {
  942. if (a.priority < b.priority) {
  943. return -1;
  944. }
  945. if (a.priority > b.priority) {
  946. return 1;
  947. }
  948. return naturalSortCompare(a.name, b.name);
  949. }
  950. function sortObjectByPriority(object) {
  951. var idx = {};
  952. var sorted = [];
  953. for (var i in object) {
  954. if (!object.hasOwnProperty(i)) {
  955. continue;
  956. }
  957. if (typeof idx[i] === 'undefined') {
  958. idx[i] = object[i];
  959. sorted.push(i);
  960. }
  961. }
  962. sorted.sort(function (a, b) {
  963. if (idx[a].priority < idx[b].priority) {
  964. return -1;
  965. }
  966. if (idx[a].priority > idx[b].priority) {
  967. return 1;
  968. }
  969. return naturalSortCompare(a, b);
  970. });
  971. return sorted;
  972. }
  973. // ----------------------------------------------------------------------------
  974. // scroll to a section, without changing the browser history
  975. function scrollToId(hash) {
  976. if (hash && hash !== '' && document.getElementById(hash) !== null) {
  977. var offset = $('#' + hash).offset();
  978. if (typeof offset !== 'undefined') {
  979. //console.log('scrolling to ' + hash + ' at ' + offset.top.toString());
  980. $('html, body').animate({ scrollTop: offset.top - 30 }, 0);
  981. }
  982. }
  983. // we must return false to prevent the default action
  984. return false;
  985. }
  986. // ----------------------------------------------------------------------------
  987. // user editable information
  988. var customDashboard = {
  989. menu: {},
  990. submenu: {},
  991. context: {}
  992. };
  993. // netdata standard information
  994. var netdataDashboard = {
  995. sparklines_registry: {},
  996. os: 'unknown',
  997. menu: {},
  998. submenu: {},
  999. context: {},
  1000. // generate a sparkline
  1001. // used in the documentation
  1002. sparkline: function (prefix, chart, dimension, units, suffix) {
  1003. if (options.data === null || typeof options.data.charts === 'undefined') {
  1004. return '';
  1005. }
  1006. if (typeof options.data.charts[chart] === 'undefined') {
  1007. return '';
  1008. }
  1009. if (typeof options.data.charts[chart].dimensions === 'undefined') {
  1010. return '';
  1011. }
  1012. if (typeof options.data.charts[chart].dimensions[dimension] === 'undefined') {
  1013. return '';
  1014. }
  1015. var key = chart + '.' + dimension;
  1016. if (typeof units === 'undefined') {
  1017. units = '';
  1018. }
  1019. if (typeof this.sparklines_registry[key] === 'undefined') {
  1020. this.sparklines_registry[key] = { count: 1 };
  1021. } else {
  1022. this.sparklines_registry[key].count++;
  1023. }
  1024. key = key + '.' + this.sparklines_registry[key].count;
  1025. return prefix + '<div class="netdata-container" data-netdata="' + chart + '" data-after="-120" data-width="25%" data-height="15px" data-chart-library="dygraph" data-dygraph-theme="sparkline" data-dimensions="' + dimension + '" data-show-value-of-' + dimension + '-at="' + key + '"></div> (<span id="' + key + '" style="display: inline-block; min-width: 50px; text-align: right;">X</span>' + units + ')' + suffix;
  1026. },
  1027. gaugeChart: function (title, width, dimensions, colors) {
  1028. if (typeof colors === 'undefined') {
  1029. colors = '';
  1030. }
  1031. if (typeof dimensions === 'undefined') {
  1032. dimensions = '';
  1033. }
  1034. return '<div class="netdata-container" data-netdata="CHART_UNIQUE_ID"'
  1035. + ' data-dimensions="' + dimensions + '"'
  1036. + ' data-chart-library="gauge"'
  1037. + ' data-gauge-adjust="width"'
  1038. + ' data-title="' + title + '"'
  1039. + ' data-width="' + width + '"'
  1040. + ' data-before="0"'
  1041. + ' data-after="-CHART_DURATION"'
  1042. + ' data-points="CHART_DURATION"'
  1043. + ' data-colors="' + colors + '"'
  1044. + ' role="application"></div>';
  1045. },
  1046. anyAttribute: function (obj, attr, key, def) {
  1047. if (typeof (obj[key]) !== 'undefined') {
  1048. var x = obj[key][attr];
  1049. if (typeof (x) === 'undefined') {
  1050. return def;
  1051. }
  1052. if (typeof (x) === 'function') {
  1053. return x(netdataDashboard.os);
  1054. }
  1055. return x;
  1056. }
  1057. return def;
  1058. },
  1059. menuTitle: function (chart) {
  1060. if (typeof chart.menu_pattern !== 'undefined') {
  1061. return (this.anyAttribute(this.menu, 'title', chart.menu_pattern, chart.menu_pattern).toString()
  1062. + '&nbsp;' + chart.type.slice(-(chart.type.length - chart.menu_pattern.length - 1)).toString()).replace(/_/g, ' ');
  1063. }
  1064. return (this.anyAttribute(this.menu, 'title', chart.menu, chart.menu)).toString().replace(/_/g, ' ');
  1065. },
  1066. menuIcon: function (chart) {
  1067. if (typeof chart.menu_pattern !== 'undefined') {
  1068. return this.anyAttribute(this.menu, 'icon', chart.menu_pattern, '<i class="fas fa-puzzle-piece"></i>').toString();
  1069. }
  1070. return this.anyAttribute(this.menu, 'icon', chart.menu, '<i class="fas fa-puzzle-piece"></i>');
  1071. },
  1072. menuInfo: function (chart) {
  1073. if (typeof chart.menu_pattern !== 'undefined') {
  1074. return this.anyAttribute(this.menu, 'info', chart.menu_pattern, null);
  1075. }
  1076. return this.anyAttribute(this.menu, 'info', chart.menu, null);
  1077. },
  1078. menuHeight: function (chart) {
  1079. if (typeof chart.menu_pattern !== 'undefined') {
  1080. return this.anyAttribute(this.menu, 'height', chart.menu_pattern, 1.0);
  1081. }
  1082. return this.anyAttribute(this.menu, 'height', chart.menu, 1.0);
  1083. },
  1084. submenuTitle: function (menu, submenu) {
  1085. var key = menu + '.' + submenu;
  1086. // console.log(key);
  1087. var title = this.anyAttribute(this.submenu, 'title', key, submenu).toString().replace(/_/g, ' ');
  1088. if (title.length > 28) {
  1089. var a = title.substring(0, 13);
  1090. var b = title.substring(title.length - 12, title.length);
  1091. return a + '...' + b;
  1092. }
  1093. return title;
  1094. },
  1095. submenuInfo: function (menu, submenu) {
  1096. var key = menu + '.' + submenu;
  1097. return this.anyAttribute(this.submenu, 'info', key, null);
  1098. },
  1099. submenuHeight: function (menu, submenu, relative) {
  1100. var key = menu + '.' + submenu;
  1101. return this.anyAttribute(this.submenu, 'height', key, 1.0) * relative;
  1102. },
  1103. contextInfo: function (id) {
  1104. var x = this.anyAttribute(this.context, 'info', id, null);
  1105. if (x !== null) {
  1106. return '<div class="shorten dashboard-context-info netdata-chart-alignment" role="document">' + x + '</div>';
  1107. } else {
  1108. return '';
  1109. }
  1110. },
  1111. contextValueRange: function (id) {
  1112. if (typeof this.context[id] !== 'undefined' && typeof this.context[id].valueRange !== 'undefined') {
  1113. return this.context[id].valueRange;
  1114. } else {
  1115. return '[null, null]';
  1116. }
  1117. },
  1118. contextHeight: function (id, def) {
  1119. if (typeof this.context[id] !== 'undefined' && typeof this.context[id].height !== 'undefined') {
  1120. return def * this.context[id].height;
  1121. } else {
  1122. return def;
  1123. }
  1124. },
  1125. contextDecimalDigits: function (id, def) {
  1126. if (typeof this.context[id] !== 'undefined' && typeof this.context[id].decimalDigits !== 'undefined') {
  1127. return this.context[id].decimalDigits;
  1128. } else {
  1129. return def;
  1130. }
  1131. }
  1132. };
  1133. // ----------------------------------------------------------------------------
  1134. // enrich the data structure returned by netdata
  1135. // to reflect our menu system and content
  1136. // TODO: this is a shame - we should fix charts naming (issue #807)
  1137. function enrichChartData(chart) {
  1138. var parts = chart.type.split('_');
  1139. var tmp = parts[0];
  1140. switch (tmp) {
  1141. case 'ap':
  1142. case 'net':
  1143. case 'disk':
  1144. case 'powersupply':
  1145. case 'statsd':
  1146. chart.menu = tmp;
  1147. break;
  1148. case 'apache':
  1149. chart.menu = chart.type;
  1150. if (parts.length > 2 && parts[1] === 'cache') {
  1151. chart.menu_pattern = tmp + '_' + parts[1];
  1152. } else if (parts.length > 1) {
  1153. chart.menu_pattern = tmp;
  1154. }
  1155. break;
  1156. case 'bind':
  1157. chart.menu = chart.type;
  1158. if (parts.length > 2 && parts[1] === 'rndc') {
  1159. chart.menu_pattern = tmp + '_' + parts[1];
  1160. } else if (parts.length > 1) {
  1161. chart.menu_pattern = tmp;
  1162. }
  1163. break;
  1164. case 'cgroup':
  1165. chart.menu = chart.type;
  1166. if (chart.id.match(/.*[\._\/-:]qemu[\._\/-:]*/) || chart.id.match(/.*[\._\/-:]kvm[\._\/-:]*/)) {
  1167. chart.menu_pattern = 'cgqemu';
  1168. } else {
  1169. chart.menu_pattern = 'cgroup';
  1170. }
  1171. break;
  1172. case 'go':
  1173. chart.menu = chart.type;
  1174. if (parts.length > 2 && parts[1] === 'expvar') {
  1175. chart.menu_pattern = tmp + '_' + parts[1];
  1176. } else if (parts.length > 1) {
  1177. chart.menu_pattern = tmp;
  1178. }
  1179. break;
  1180. case 'mount':
  1181. if (parts.length > 2) {
  1182. chart.menu = tmp + '_' + parts[1];
  1183. } else {
  1184. chart.menu = tmp;
  1185. }
  1186. break;
  1187. case 'isc':
  1188. chart.menu = chart.type;
  1189. if (parts.length > 2 && parts[1] === 'dhcpd') {
  1190. chart.menu_pattern = tmp + '_' + parts[1];
  1191. } else if (parts.length > 1) {
  1192. chart.menu_pattern = tmp;
  1193. }
  1194. break;
  1195. case 'ovpn':
  1196. chart.menu = chart.type;
  1197. if (parts.length > 3 && parts[1] === 'status' && parts[2] === 'log') {
  1198. chart.menu_pattern = tmp + '_' + parts[1];
  1199. } else if (parts.length > 1) {
  1200. chart.menu_pattern = tmp;
  1201. }
  1202. break;
  1203. case 'smartd':
  1204. case 'web':
  1205. chart.menu = chart.type;
  1206. if (parts.length > 2 && parts[1] === 'log') {
  1207. chart.menu_pattern = tmp + '_' + parts[1];
  1208. } else if (parts.length > 1) {
  1209. chart.menu_pattern = tmp;
  1210. }
  1211. break;
  1212. case 'tc':
  1213. chart.menu = tmp;
  1214. // find a name for this device from fireqos info
  1215. // we strip '_(in|out)' or '(in|out)_'
  1216. if (chart.context === 'tc.qos' && (typeof options.submenu_names[chart.family] === 'undefined' || options.submenu_names[chart.family] === chart.family)) {
  1217. var n = chart.name.split('.')[1];
  1218. if (n.endsWith('_in')) {
  1219. options.submenu_names[chart.family] = n.slice(0, n.lastIndexOf('_in'));
  1220. } else if (n.endsWith('_out')) {
  1221. options.submenu_names[chart.family] = n.slice(0, n.lastIndexOf('_out'));
  1222. } else if (n.startsWith('in_')) {
  1223. options.submenu_names[chart.family] = n.slice(3, n.length);
  1224. } else if (n.startsWith('out_')) {
  1225. options.submenu_names[chart.family] = n.slice(4, n.length);
  1226. } else {
  1227. options.submenu_names[chart.family] = n;
  1228. }
  1229. }
  1230. // increase the priority of IFB devices
  1231. // to have inbound appear before outbound
  1232. if (chart.id.match(/.*-ifb$/)) {
  1233. chart.priority--;
  1234. }
  1235. break;
  1236. default:
  1237. chart.menu = chart.type;
  1238. if (parts.length > 1) {
  1239. chart.menu_pattern = tmp;
  1240. }
  1241. break;
  1242. }
  1243. chart.submenu = chart.family;
  1244. }
  1245. // ----------------------------------------------------------------------------
  1246. function headMain(os, charts, duration) {
  1247. void (os);
  1248. if (urlOptions.mode === 'print') {
  1249. return '';
  1250. }
  1251. var head = '';
  1252. if (typeof charts['system.swap'] !== 'undefined') {
  1253. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.swap"'
  1254. + ' data-dimensions="used"'
  1255. + ' data-append-options="percentage"'
  1256. + ' data-chart-library="easypiechart"'
  1257. + ' data-title="Used Swap"'
  1258. + ' data-units="%"'
  1259. + ' data-easypiechart-max-value="100"'
  1260. + ' data-width="9%"'
  1261. + ' data-before="0"'
  1262. + ' data-after="-' + duration.toString() + '"'
  1263. + ' data-points="' + duration.toString() + '"'
  1264. + ' data-colors="#DD4400"'
  1265. + ' role="application"></div>';
  1266. }
  1267. if (typeof charts['system.io'] !== 'undefined') {
  1268. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.io"'
  1269. + ' data-dimensions="in"'
  1270. + ' data-chart-library="easypiechart"'
  1271. + ' data-title="Disk Read"'
  1272. + ' data-width="11%"'
  1273. + ' data-before="0"'
  1274. + ' data-after="-' + duration.toString() + '"'
  1275. + ' data-points="' + duration.toString() + '"'
  1276. + ' data-common-units="system.io.mainhead"'
  1277. + ' role="application"></div>';
  1278. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.io"'
  1279. + ' data-dimensions="out"'
  1280. + ' data-chart-library="easypiechart"'
  1281. + ' data-title="Disk Write"'
  1282. + ' data-width="11%"'
  1283. + ' data-before="0"'
  1284. + ' data-after="-' + duration.toString() + '"'
  1285. + ' data-points="' + duration.toString() + '"'
  1286. + ' data-common-units="system.io.mainhead"'
  1287. + ' role="application"></div>';
  1288. }
  1289. else if (typeof charts['system.pgpgio'] !== 'undefined') {
  1290. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.pgpgio"'
  1291. + ' data-dimensions="in"'
  1292. + ' data-chart-library="easypiechart"'
  1293. + ' data-title="Disk Read"'
  1294. + ' data-width="11%"'
  1295. + ' data-before="0"'
  1296. + ' data-after="-' + duration.toString() + '"'
  1297. + ' data-points="' + duration.toString() + '"'
  1298. + ' data-common-units="system.pgpgio.mainhead"'
  1299. + ' role="application"></div>';
  1300. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.pgpgio"'
  1301. + ' data-dimensions="out"'
  1302. + ' data-chart-library="easypiechart"'
  1303. + ' data-title="Disk Write"'
  1304. + ' data-width="11%"'
  1305. + ' data-before="0"'
  1306. + ' data-after="-' + duration.toString() + '"'
  1307. + ' data-points="' + duration.toString() + '"'
  1308. + ' data-common-units="system.pgpgio.mainhead"'
  1309. + ' role="application"></div>';
  1310. }
  1311. if (typeof charts['system.cpu'] !== 'undefined') {
  1312. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.cpu"'
  1313. + ' data-chart-library="gauge"'
  1314. + ' data-title="CPU"'
  1315. + ' data-units="%"'
  1316. + ' data-gauge-max-value="100"'
  1317. + ' data-width="20%"'
  1318. + ' data-after="-' + duration.toString() + '"'
  1319. + ' data-points="' + duration.toString() + '"'
  1320. + ' data-colors="' + NETDATA.colors[12] + '"'
  1321. + ' role="application"></div>';
  1322. }
  1323. if (typeof charts['system.net'] !== 'undefined') {
  1324. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.net"'
  1325. + ' data-dimensions="received"'
  1326. + ' data-chart-library="easypiechart"'
  1327. + ' data-title="Net Inbound"'
  1328. + ' data-width="11%"'
  1329. + ' data-before="0"'
  1330. + ' data-after="-' + duration.toString() + '"'
  1331. + ' data-points="' + duration.toString() + '"'
  1332. + ' data-common-units="system.net.mainhead"'
  1333. + ' role="application"></div>';
  1334. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.net"'
  1335. + ' data-dimensions="sent"'
  1336. + ' data-chart-library="easypiechart"'
  1337. + ' data-title="Net Outbound"'
  1338. + ' data-width="11%"'
  1339. + ' data-before="0"'
  1340. + ' data-after="-' + duration.toString() + '"'
  1341. + ' data-points="' + duration.toString() + '"'
  1342. + ' data-common-units="system.net.mainhead"'
  1343. + ' role="application"></div>';
  1344. }
  1345. else if (typeof charts['system.ip'] !== 'undefined') {
  1346. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.ip"'
  1347. + ' data-dimensions="received"'
  1348. + ' data-chart-library="easypiechart"'
  1349. + ' data-title="IP Inbound"'
  1350. + ' data-width="11%"'
  1351. + ' data-before="0"'
  1352. + ' data-after="-' + duration.toString() + '"'
  1353. + ' data-points="' + duration.toString() + '"'
  1354. + ' data-common-units="system.ip.mainhead"'
  1355. + ' role="application"></div>';
  1356. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.ip"'
  1357. + ' data-dimensions="sent"'
  1358. + ' data-chart-library="easypiechart"'
  1359. + ' data-title="IP Outbound"'
  1360. + ' data-width="11%"'
  1361. + ' data-before="0"'
  1362. + ' data-after="-' + duration.toString() + '"'
  1363. + ' data-points="' + duration.toString() + '"'
  1364. + ' data-common-units="system.ip.mainhead"'
  1365. + ' role="application"></div>';
  1366. }
  1367. else if (typeof charts['system.ipv4'] !== 'undefined') {
  1368. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.ipv4"'
  1369. + ' data-dimensions="received"'
  1370. + ' data-chart-library="easypiechart"'
  1371. + ' data-title="IPv4 Inbound"'
  1372. + ' data-width="11%"'
  1373. + ' data-before="0"'
  1374. + ' data-after="-' + duration.toString() + '"'
  1375. + ' data-points="' + duration.toString() + '"'
  1376. + ' data-common-units="system.ipv4.mainhead"'
  1377. + ' role="application"></div>';
  1378. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.ipv4"'
  1379. + ' data-dimensions="sent"'
  1380. + ' data-chart-library="easypiechart"'
  1381. + ' data-title="IPv4 Outbound"'
  1382. + ' data-width="11%"'
  1383. + ' data-before="0"'
  1384. + ' data-after="-' + duration.toString() + '"'
  1385. + ' data-points="' + duration.toString() + '"'
  1386. + ' data-common-units="system.ipv4.mainhead"'
  1387. + ' role="application"></div>';
  1388. }
  1389. else if (typeof charts['system.ipv6'] !== 'undefined') {
  1390. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.ipv6"'
  1391. + ' data-dimensions="received"'
  1392. + ' data-chart-library="easypiechart"'
  1393. + ' data-title="IPv6 Inbound"'
  1394. + ' data-units="kbps"'
  1395. + ' data-width="11%"'
  1396. + ' data-before="0"'
  1397. + ' data-after="-' + duration.toString() + '"'
  1398. + ' data-points="' + duration.toString() + '"'
  1399. + ' data-common-units="system.ipv6.mainhead"'
  1400. + ' role="application"></div>';
  1401. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.ipv6"'
  1402. + ' data-dimensions="sent"'
  1403. + ' data-chart-library="easypiechart"'
  1404. + ' data-title="IPv6 Outbound"'
  1405. + ' data-units="kbps"'
  1406. + ' data-width="11%"'
  1407. + ' data-before="0"'
  1408. + ' data-after="-' + duration.toString() + '"'
  1409. + ' data-points="' + duration.toString() + '"'
  1410. + ' data-common-units="system.ipv6.mainhead"'
  1411. + ' role="application"></div>';
  1412. }
  1413. if (typeof charts['system.ram'] !== 'undefined') {
  1414. head += '<div class="netdata-container" style="margin-right: 10px;" data-netdata="system.ram"'
  1415. + ' data-dimensions="used|buffers|active|wired"' // active and wired are FreeBSD stats
  1416. + ' data-append-options="percentage"'
  1417. + ' data-chart-library="easypiechart"'
  1418. + ' data-title="Used RAM"'
  1419. + ' data-units="%"'
  1420. + ' data-easypiechart-max-value="100"'
  1421. + ' data-width="9%"'
  1422. + ' data-after="-' + duration.toString() + '"'
  1423. + ' data-points="' + duration.toString() + '"'
  1424. + ' data-colors="' + NETDATA.colors[7] + '"'
  1425. + ' role="application"></div>';
  1426. }
  1427. return head;
  1428. }
  1429. function generateHeadCharts(type, chart, duration) {
  1430. if (urlOptions.mode === 'print') {
  1431. return '';
  1432. }
  1433. var head = '';
  1434. var hcharts = netdataDashboard.anyAttribute(netdataDashboard.context, type, chart.context, []);
  1435. if (hcharts.length > 0) {
  1436. var hi = 0, hlen = hcharts.length;
  1437. while (hi < hlen) {
  1438. if (typeof hcharts[hi] === 'function') {
  1439. head += hcharts[hi](netdataDashboard.os, chart.id).replace(/CHART_DURATION/g, duration.toString()).replace(/CHART_UNIQUE_ID/g, chart.id);
  1440. } else {
  1441. head += hcharts[hi].replace(/CHART_DURATION/g, duration.toString()).replace(/CHART_UNIQUE_ID/g, chart.id);
  1442. }
  1443. hi++;
  1444. }
  1445. }
  1446. return head;
  1447. }
  1448. function renderPage(menus, data) {
  1449. var div = document.getElementById('charts_div');
  1450. var pcent_width = Math.floor(100 / chartsPerRow($(div).width()));
  1451. // find the proper duration for per-second updates
  1452. var duration = Math.round(($(div).width() * pcent_width / 100 * data.update_every / 3) / 60) * 60;
  1453. options.duration = duration;
  1454. options.update_every = data.update_every;
  1455. var html = '';
  1456. var sidebar = '<ul class="nav dashboard-sidenav" data-spy="affix" id="sidebar_ul">';
  1457. var mainhead = headMain(netdataDashboard.os, data.charts, duration);
  1458. // sort the menus
  1459. var main = sortObjectByPriority(menus);
  1460. var i = 0, len = main.length;
  1461. while (i < len) {
  1462. var menu = main[i++];
  1463. // generate an entry at the main menu
  1464. var menuid = NETDATA.name2id('menu_' + menu);
  1465. sidebar += '<li class=""><a href="#' + menuid + '" onClick="return scrollToId(\'' + menuid + '\');">' + menus[menu].icon + ' ' + menus[menu].title + '</a><ul class="nav">';
  1466. html += '<div role="section" class="dashboard-section"><div role="sectionhead"><h1 id="' + menuid + '" role="heading">' + menus[menu].icon + ' ' + menus[menu].title + '</h1></div><div role="section" class="dashboard-subsection">';
  1467. if (menus[menu].info !== null) {
  1468. html += menus[menu].info;
  1469. }
  1470. // console.log(' >> ' + menu + ' (' + menus[menu].priority + '): ' + menus[menu].title);
  1471. var shtml = '';
  1472. var mhead = '<div class="netdata-chart-row">' + mainhead;
  1473. mainhead = '';
  1474. // sort the submenus of this menu
  1475. var sub = sortObjectByPriority(menus[menu].submenus);
  1476. var si = 0, slen = sub.length;
  1477. while (si < slen) {
  1478. var submenu = sub[si++];
  1479. // generate an entry at the submenu
  1480. var submenuid = NETDATA.name2id('menu_' + menu + '_submenu_' + submenu);
  1481. sidebar += '<li class><a href="#' + submenuid + '" onClick="return scrollToId(\'' + submenuid + '\');">' + menus[menu].submenus[submenu].title + '</a></li>';
  1482. shtml += '<div role="section" class="dashboard-section-container" id="' + submenuid + '"><h2 id="' + submenuid + '" class="netdata-chart-alignment" role="heading">' + menus[menu].submenus[submenu].title + '</h2>';
  1483. if (menus[menu].submenus[submenu].info !== null) {
  1484. shtml += '<div class="dashboard-submenu-info netdata-chart-alignment" role="document">' + menus[menu].submenus[submenu].info + '</div>';
  1485. }
  1486. var head = '<div class="netdata-chart-row">';
  1487. var chtml = '';
  1488. // console.log(' \------- ' + submenu + ' (' + menus[menu].submenus[submenu].priority + '): ' + menus[menu].submenus[submenu].title);
  1489. // sort the charts in this submenu of this menu
  1490. menus[menu].submenus[submenu].charts.sort(prioritySort);
  1491. var ci = 0, clen = menus[menu].submenus[submenu].charts.length;
  1492. while (ci < clen) {
  1493. var chart = menus[menu].submenus[submenu].charts[ci++];
  1494. // generate the submenu heading charts
  1495. mhead += generateHeadCharts('mainheads', chart, duration);
  1496. head += generateHeadCharts('heads', chart, duration);
  1497. function chartCommonMin(family, context, units) {
  1498. var x = netdataDashboard.anyAttribute(netdataDashboard.context, 'commonMin', context, undefined);
  1499. if (typeof x !== 'undefined') {
  1500. return ' data-common-min="' + family + '/' + context + '/' + units + '"';
  1501. } else {
  1502. return '';
  1503. }
  1504. }
  1505. function chartCommonMax(family, context, units) {
  1506. var x = netdataDashboard.anyAttribute(netdataDashboard.context, 'commonMax', context, undefined);
  1507. if (typeof x !== 'undefined') {
  1508. return ' data-common-max="' + family + '/' + context + '/' + units + '"';
  1509. } else {
  1510. return '';
  1511. }
  1512. }
  1513. // generate the chart
  1514. if (urlOptions.mode === 'print') {
  1515. chtml += '<div role="row" class="dashboard-print-row">';
  1516. }
  1517. chtml += '<div class="netdata-chartblock-container" style="width: ' + pcent_width.toString() + '%;">' + netdataDashboard.contextInfo(chart.context) + '<div class="netdata-container" id="chart_' + NETDATA.name2id(chart.id) + '" data-netdata="' + chart.id + '"'
  1518. + ' data-width="100%"'
  1519. + ' data-height="' + netdataDashboard.contextHeight(chart.context, options.chartsHeight).toString() + 'px"'
  1520. + ' data-dygraph-valuerange="' + netdataDashboard.contextValueRange(chart.context) + '"'
  1521. + ' data-before="0"'
  1522. + ' data-after="-' + duration.toString() + '"'
  1523. + ' data-id="' + NETDATA.name2id(options.hostname + '/' + chart.id) + '"'
  1524. + ' data-colors="' + netdataDashboard.anyAttribute(netdataDashboard.context, 'colors', chart.context, '') + '"'
  1525. + ' data-decimal-digits="' + netdataDashboard.contextDecimalDigits(chart.context, -1) + '"'
  1526. + chartCommonMin(chart.family, chart.context, chart.units)
  1527. + chartCommonMax(chart.family, chart.context, chart.units)
  1528. + ' role="application"></div></div>';
  1529. if (urlOptions.mode === 'print') {
  1530. chtml += '</div>';
  1531. }
  1532. }
  1533. head += '</div>';
  1534. shtml += head + chtml + '</div>';
  1535. }
  1536. mhead += '</div>';
  1537. sidebar += '</ul></li>';
  1538. html += mhead + shtml + '</div></div><hr role="separator"/>';
  1539. }
  1540. const isMemoryModeDbEngine = data.memory_mode === "dbengine";
  1541. sidebar += '<li class="" style="padding-top:15px;"><a href="https://learn.netdata.cloud/docs/agent/collectors/quickstart/" target="_blank"><i class="fas fa-plus"></i> Add more charts</a></li>';
  1542. sidebar += '<li class=""><a href="https://learn.netdata.cloud/docs/agent/health/quickstart/" target="_blank"><i class="fas fa-plus"></i> Add more alarms</a></li>';
  1543. sidebar += '<li class="" style="margin:20px;color:#666;"><small>Every ' +
  1544. ((data.update_every === 1) ? 'second' : data.update_every.toString() + ' seconds') + ', ' +
  1545. 'Netdata collects <strong>' + data.dimensions_count.toLocaleString() + '</strong> metrics on ' +
  1546. data.hostname.toString() + ', presents them in <strong>' +
  1547. data.charts_count.toLocaleString() + '</strong> charts' +
  1548. (isMemoryModeDbEngine ? '' : ',') + // oxford comma
  1549. ' and monitors them with <strong>' +
  1550. data.alarms_count.toLocaleString() + '</strong> alarms.';
  1551. if (!isMemoryModeDbEngine) {
  1552. sidebar += '<br />&nbsp;<br />Get more history by ' +
  1553. '<a href="https://learn.netdata.cloud/guides/longer-metrics-storage#using-the-round-robin-database" target=_blank>configuring Netdata\'s <strong>history</strong></a> or using the <a href="https://learn.netdata.cloud/docs/agent/database/engine/" target=_blank>DB engine.</a>';
  1554. }
  1555. sidebar += '<br/>&nbsp;<br/><strong>netdata</strong><br/>' + data.version.toString() + '</small></li>';
  1556. sidebar += '</ul>';
  1557. div.innerHTML = html;
  1558. document.getElementById('sidebar').innerHTML = sidebar;
  1559. if (urlOptions.highlight === true) {
  1560. NETDATA.globalChartUnderlay.init(null
  1561. , urlOptions.highlight_after
  1562. , urlOptions.highlight_before
  1563. , (urlOptions.after > 0) ? urlOptions.after : null
  1564. , (urlOptions.before > 0) ? urlOptions.before : null
  1565. );
  1566. } else {
  1567. NETDATA.globalChartUnderlay.clear();
  1568. }
  1569. if (urlOptions.mode === 'print') {
  1570. printPage();
  1571. } else {
  1572. finalizePage();
  1573. }
  1574. }
  1575. function renderChartsAndMenu(data) {
  1576. options.menus = {};
  1577. options.submenu_names = {};
  1578. var menus = options.menus;
  1579. var charts = data.charts;
  1580. var m, menu_key;
  1581. for (var c in charts) {
  1582. if (!charts.hasOwnProperty(c)) {
  1583. continue;
  1584. }
  1585. var chart = charts[c];
  1586. enrichChartData(chart);
  1587. m = chart.menu;
  1588. // create the menu
  1589. if (typeof menus[m] === 'undefined') {
  1590. menus[m] = {
  1591. menu_pattern: chart.menu_pattern,
  1592. priority: chart.priority,
  1593. submenus: {},
  1594. title: netdataDashboard.menuTitle(chart),
  1595. icon: netdataDashboard.menuIcon(chart),
  1596. info: netdataDashboard.menuInfo(chart),
  1597. height: netdataDashboard.menuHeight(chart) * options.chartsHeight
  1598. };
  1599. } else {
  1600. if (typeof (menus[m].menu_pattern) === 'undefined') {
  1601. menus[m].menu_pattern = chart.menu_pattern;
  1602. }
  1603. if (chart.priority < menus[m].priority) {
  1604. menus[m].priority = chart.priority;
  1605. }
  1606. }
  1607. menu_key = (typeof (menus[m].menu_pattern) !== 'undefined') ? menus[m].menu_pattern : m;
  1608. // create the submenu
  1609. if (typeof menus[m].submenus[chart.submenu] === 'undefined') {
  1610. menus[m].submenus[chart.submenu] = {
  1611. priority: chart.priority,
  1612. charts: [],
  1613. title: null,
  1614. info: netdataDashboard.submenuInfo(menu_key, chart.submenu),
  1615. height: netdataDashboard.submenuHeight(menu_key, chart.submenu, menus[m].height)
  1616. };
  1617. } else {
  1618. if (chart.priority < menus[m].submenus[chart.submenu].priority) {
  1619. menus[m].submenus[chart.submenu].priority = chart.priority;
  1620. }
  1621. }
  1622. // index the chart in the menu/submenu
  1623. menus[m].submenus[chart.submenu].charts.push(chart);
  1624. }
  1625. // propagate the descriptive subname given to QoS
  1626. // to all the other submenus with the same name
  1627. for (var m in menus) {
  1628. if (!menus.hasOwnProperty(m)) {
  1629. continue;
  1630. }
  1631. for (var s in menus[m].submenus) {
  1632. if (!menus[m].submenus.hasOwnProperty(s)) {
  1633. continue;
  1634. }
  1635. // set the family using a name
  1636. if (typeof options.submenu_names[s] !== 'undefined') {
  1637. menus[m].submenus[s].title = s + ' (' + options.submenu_names[s] + ')';
  1638. } else {
  1639. menu_key = (typeof (menus[m].menu_pattern) !== 'undefined') ? menus[m].menu_pattern : m;
  1640. menus[m].submenus[s].title = netdataDashboard.submenuTitle(menu_key, s);
  1641. }
  1642. }
  1643. }
  1644. renderPage(menus, data);
  1645. }
  1646. // ----------------------------------------------------------------------------
  1647. function loadJs(url, callback) {
  1648. $.ajax({
  1649. url: url.startsWith("http") ? url : transformWithOldSuffix(url),
  1650. cache: true,
  1651. dataType: "script",
  1652. xhrFields: { withCredentials: true } // required for the cookie
  1653. })
  1654. .fail(function () {
  1655. alert('Cannot load required JS library: ' + url);
  1656. })
  1657. .always(function () {
  1658. if (typeof callback === 'function') {
  1659. callback();
  1660. }
  1661. })
  1662. }
  1663. var clipboardLoaded = false;
  1664. function loadClipboard(callback) {
  1665. if (clipboardLoaded === false) {
  1666. clipboardLoaded = true;
  1667. loadJs('lib/clipboard-polyfill-be05dad.js', callback);
  1668. } else {
  1669. callback();
  1670. }
  1671. }
  1672. var bootstrapTableLoaded = false;
  1673. function loadBootstrapTable(callback) {
  1674. if (bootstrapTableLoaded === false) {
  1675. bootstrapTableLoaded = true;
  1676. loadJs('lib/bootstrap-table-1.11.0.min.js', function () {
  1677. loadJs('lib/bootstrap-table-export-1.11.0.min.js', function () {
  1678. loadJs('lib/tableExport-1.6.0.min.js', callback);
  1679. })
  1680. });
  1681. } else {
  1682. callback();
  1683. }
  1684. }
  1685. var bootstrapSliderLoaded = false;
  1686. function loadBootstrapSlider(callback) {
  1687. if (bootstrapSliderLoaded === false) {
  1688. bootstrapSliderLoaded = true;
  1689. loadJs('lib/bootstrap-slider-10.0.0.min.js', function () {
  1690. NETDATA._loadCSS(transformWithOldSuffix("css/bootstrap-slider-10.0.0.min.css"));
  1691. callback();
  1692. });
  1693. } else {
  1694. callback();
  1695. }
  1696. }
  1697. var lzStringLoaded = false;
  1698. function loadLzString(callback) {
  1699. if (lzStringLoaded === false) {
  1700. lzStringLoaded = true;
  1701. loadJs('lib/lz-string-1.4.4.min.js', callback);
  1702. } else {
  1703. callback();
  1704. }
  1705. }
  1706. var pakoLoaded = false;
  1707. function loadPako(callback) {
  1708. if (pakoLoaded === false) {
  1709. pakoLoaded = true;
  1710. loadJs('lib/pako-1.0.6.min.js', callback);
  1711. } else {
  1712. callback();
  1713. }
  1714. }
  1715. // ----------------------------------------------------------------------------
  1716. function clipboardCopy(text) {
  1717. clipboard.writeText(text);
  1718. }
  1719. function clipboardCopyBadgeEmbed(url) {
  1720. clipboard.writeText('<embed src="' + url + '" type="image/svg+xml" height="20"/>');
  1721. }
  1722. // ----------------------------------------------------------------------------
  1723. function alarmsUpdateModal() {
  1724. var active = '<h3>Raised Alarms</h3><table class="table">';
  1725. var all = '<h3>All Running Alarms</h3><div class="panel-group" id="alarms_all_accordion" role="tablist" aria-multiselectable="true">';
  1726. var footer = '<hr/><a href="https://github.com/netdata/netdata/tree/master/web/api/badges#netdata-badges" target="_blank">netdata badges</a> refresh automatically. Their color indicates the state of the alarm: <span style="color: #e05d44"><b>&nbsp;red&nbsp;</b></span> is critical, <span style="color:#fe7d37"><b>&nbsp;orange&nbsp;</b></span> is warning, <span style="color: #4c1"><b>&nbsp;bright green&nbsp;</b></span> is ok, <span style="color: #9f9f9f"><b>&nbsp;light grey&nbsp;</b></span> is undefined (i.e. no data or no status), <span style="color: #000"><b>&nbsp;black&nbsp;</b></span> is not initialized. You can copy and paste their URLs to embed them in any web page.<br/>netdata can send notifications for these alarms. Check <a href="https://github.com/netdata/netdata/blob/master/health/notifications/health_alarm_notify.conf" target="_blank">this configuration file</a> for more information.';
  1727. loadClipboard(function () {
  1728. });
  1729. NETDATA.alarms.get('all', function (data) {
  1730. options.alarm_families = [];
  1731. alarmsCallback(data);
  1732. if (data === null) {
  1733. document.getElementById('alarms_active').innerHTML =
  1734. document.getElementById('alarms_all').innerHTML =
  1735. document.getElementById('alarms_log').innerHTML =
  1736. 'failed to load alarm data!';
  1737. return;
  1738. }
  1739. function alarmid4human(id) {
  1740. if (id === 0) {
  1741. return '-';
  1742. }
  1743. return id.toString();
  1744. }
  1745. function timestamp4human(timestamp, space) {
  1746. if (timestamp === 0) {
  1747. return '-';
  1748. }
  1749. if (typeof space === 'undefined') {
  1750. space = '&nbsp;';
  1751. }
  1752. var t = new Date(timestamp * 1000);
  1753. var now = new Date();
  1754. if (t.toDateString() === now.toDateString()) {
  1755. return t.toLocaleTimeString();
  1756. }
  1757. return t.toLocaleDateString() + space + t.toLocaleTimeString();
  1758. }
  1759. function alarm_lookup_explain(alarm, chart) {
  1760. var dimensions = ' of all values ';
  1761. if (chart.dimensions.length > 1) {
  1762. dimensions = ' of the sum of all dimensions ';
  1763. }
  1764. if (typeof alarm.lookup_dimensions !== 'undefined') {
  1765. var d = alarm.lookup_dimensions.replace(/|/g, ',');
  1766. var x = d.split(',');
  1767. if (x.length > 1) {
  1768. dimensions = 'of the sum of dimensions <code>' + alarm.lookup_dimensions + '</code> ';
  1769. } else {
  1770. dimensions = 'of all values of dimension <code>' + alarm.lookup_dimensions + '</code> ';
  1771. }
  1772. }
  1773. return '<code>' + alarm.lookup_method + '</code> '
  1774. + dimensions + ', of chart <code>' + alarm.chart + '</code>'
  1775. + ', starting <code>' + NETDATA.seconds4human(alarm.lookup_after + alarm.lookup_before, { space: '&nbsp;' }) + '</code> and up to <code>' + NETDATA.seconds4human(alarm.lookup_before, { space: '&nbsp;' }) + '</code>'
  1776. + ((alarm.lookup_options) ? (', with options <code>' + alarm.lookup_options.replace(/ /g, ',&nbsp;') + '</code>') : '')
  1777. + '.';
  1778. }
  1779. function alarm_to_html(alarm, full) {
  1780. var chart = options.data.charts[alarm.chart];
  1781. if (typeof (chart) === 'undefined') {
  1782. chart = options.data.charts_by_name[alarm.chart];
  1783. if (typeof (chart) === 'undefined') {
  1784. // this means the charts loaded are incomplete
  1785. // probably netdata was restarted and more alarms
  1786. // are now available.
  1787. console.log('Cannot find chart ' + alarm.chart + ', you probably need to refresh the page.');
  1788. return '';
  1789. }
  1790. }
  1791. var has_alarm = (typeof alarm.warn !== 'undefined' || typeof alarm.crit !== 'undefined');
  1792. var badge_url = NETDATA.alarms.server + '/api/v1/badge.svg?chart=' + alarm.chart + '&alarm=' + alarm.name + '&refresh=auto';
  1793. var action_buttons = '<br/>&nbsp;<br/>role: <b>' + alarm.recipient + '</b><br/>&nbsp;<br/>'
  1794. + '<div class="action-button ripple" title="click to scroll the dashboard to the chart of this alarm" data-toggle="tooltip" data-placement="bottom" onClick="scrollToChartAfterHidingModal(\'' + alarm.chart + '\', ' + alarm.last_status_change * 1000 + ', \'' + alarm.status + '\'); $(\'#alarmsModal\').modal(\'hide\'); return false;"><i class="fab fa-periscope"></i></div>'
  1795. + '<div class="action-button ripple" title="click to copy to the clipboard the URL of this badge" data-toggle="tooltip" data-placement="bottom" onClick="clipboardCopy(\'' + badge_url + '\'); return false;"><i class="far fa-copy"></i></div>'
  1796. + '<div class="action-button ripple" title="click to copy to the clipboard an auto-refreshing <code>embed</code> html element for this badge" data-toggle="tooltip" data-placement="bottom" onClick="clipboardCopyBadgeEmbed(\'' + badge_url + '\'); return false;"><i class="fas fa-copy"></i></div>';
  1797. var html = '<tr><td class="text-center" style="vertical-align:middle" width="40%"><b>' + alarm.chart + '</b><br/>&nbsp;<br/><embed src="' + badge_url + '" type="image/svg+xml" height="20"/><br/>&nbsp;<br/><span style="font-size: 18px">' + alarm.info + '</span>' + action_buttons + '</td>'
  1798. + '<td><table class="table">'
  1799. + ((typeof alarm.warn !== 'undefined') ? ('<tr><td width="10%" style="text-align:right">warning&nbsp;when</td><td><span style="font-family: monospace; color:#fe7d37; font-weight: bold;">' + alarm.warn + '</span></td></tr>') : '')
  1800. + ((typeof alarm.crit !== 'undefined') ? ('<tr><td width="10%" style="text-align:right">critical&nbsp;when</td><td><span style="font-family: monospace; color: #e05d44; font-weight: bold;">' + alarm.crit + '</span></td></tr>') : '');
  1801. if (full === true) {
  1802. var units = chart.units;
  1803. if (units === '%') {
  1804. units = '&#37;';
  1805. }
  1806. html += ((typeof alarm.lookup_after !== 'undefined') ? ('<tr><td width="10%" style="text-align:right">db&nbsp;lookup</td><td>' + alarm_lookup_explain(alarm, chart) + '</td></tr>') : '')
  1807. + ((typeof alarm.calc !== 'undefined') ? ('<tr><td width="10%" style="text-align:right">calculation</td><td><span style="font-family: monospace;">' + alarm.calc + '</span></td></tr>') : '')
  1808. + ((chart.green !== null) ? ('<tr><td width="10%" style="text-align:right">green&nbsp;threshold</td><td><code>' + chart.green + ' ' + units + '</code></td></tr>') : '')
  1809. + ((chart.red !== null) ? ('<tr><td width="10%" style="text-align:right">red&nbsp;threshold</td><td><code>' + chart.red + ' ' + units + '</code></td></tr>') : '');
  1810. }
  1811. if (alarm.warn_repeat_every > 0) {
  1812. html += '<tr><td width="10%" style="text-align:right">repeat&nbsp;warning</td><td>' + NETDATA.seconds4human(alarm.warn_repeat_every) + '</td></tr>';
  1813. }
  1814. if (alarm.crit_repeat_every > 0) {
  1815. html += '<tr><td width="10%" style="text-align:right">repeat&nbsp;critical</td><td>' + NETDATA.seconds4human(alarm.crit_repeat_every) + '</td></tr>';
  1816. }
  1817. var delay = '';
  1818. if ((alarm.delay_up_duration > 0 || alarm.delay_down_duration > 0) && alarm.delay_multiplier !== 0 && alarm.delay_max_duration > 0) {
  1819. if (alarm.delay_up_duration === alarm.delay_down_duration) {
  1820. delay += '<small><br/>hysteresis ' + NETDATA.seconds4human(alarm.delay_up_duration, {
  1821. space: '&nbsp;',
  1822. negative_suffix: ''
  1823. });
  1824. } else {
  1825. delay = '<small><br/>hysteresis ';
  1826. if (alarm.delay_up_duration > 0) {
  1827. delay += 'on&nbsp;escalation&nbsp;<code>' + NETDATA.seconds4human(alarm.delay_up_duration, {
  1828. space: '&nbsp;',
  1829. negative_suffix: ''
  1830. }) + '</code>, ';
  1831. }
  1832. if (alarm.delay_down_duration > 0) {
  1833. delay += 'on&nbsp;recovery&nbsp;<code>' + NETDATA.seconds4human(alarm.delay_down_duration, {
  1834. space: '&nbsp;',
  1835. negative_suffix: ''
  1836. }) + '</code>, ';
  1837. }
  1838. }
  1839. if (alarm.delay_multiplier !== 1.0) {
  1840. delay += 'multiplied&nbsp;by&nbsp;<code>' + alarm.delay_multiplier.toString() + '</code>';
  1841. delay += ',&nbsp;up&nbsp;to&nbsp;<code>' + NETDATA.seconds4human(alarm.delay_max_duration, {
  1842. space: '&nbsp;',
  1843. negative_suffix: ''
  1844. }) + '</code>';
  1845. }
  1846. delay += '</small>';
  1847. }
  1848. html += '<tr><td width="10%" style="text-align:right">check&nbsp;every</td><td>' + NETDATA.seconds4human(alarm.update_every, {
  1849. space: '&nbsp;',
  1850. negative_suffix: ''
  1851. }) + '</td></tr>'
  1852. + ((has_alarm === true) ? ('<tr><td width="10%" style="text-align:right">execute</td><td><span style="font-family: monospace;">' + alarm.exec + '</span>' + delay + '</td></tr>') : '')
  1853. + '<tr><td width="10%" style="text-align:right">source</td><td><span style="font-family: monospace;">' + alarm.source + '</span></td></tr>'
  1854. + '</table></td></tr>';
  1855. return html;
  1856. }
  1857. function alarm_family_show(id) {
  1858. var html = '<table class="table">';
  1859. var family = options.alarm_families[id];
  1860. var len = family.arr.length;
  1861. while (len--) {
  1862. var alarm = family.arr[len];
  1863. html += alarm_to_html(alarm, true);
  1864. }
  1865. html += '</table>';
  1866. $('#alarm_all_' + id.toString()).html(html);
  1867. enableTooltipsAndPopovers();
  1868. }
  1869. // find the proper family of each alarm
  1870. var x, family, alarm;
  1871. var count_active = 0;
  1872. var count_all = 0;
  1873. var families = {};
  1874. var families_sort = [];
  1875. for (x in data.alarms) {
  1876. if (!data.alarms.hasOwnProperty(x)) {
  1877. continue;
  1878. }
  1879. alarm = data.alarms[x];
  1880. family = alarm.family;
  1881. // find the chart
  1882. var chart = options.data.charts[alarm.chart];
  1883. if (typeof chart === 'undefined') {
  1884. chart = options.data.charts_by_name[alarm.chart];
  1885. }
  1886. // not found - this should never happen!
  1887. if (typeof chart === 'undefined') {
  1888. console.log('WARNING: alarm ' + x + ' is linked to chart ' + alarm.chart + ', which is not found in the list of chart got from the server.');
  1889. chart = { priority: 9999999 };
  1890. }
  1891. else if (typeof chart.menu !== 'undefined' && typeof chart.submenu !== 'undefined')
  1892. // the family based on the chart
  1893. {
  1894. family = chart.menu + ' - ' + chart.submenu;
  1895. }
  1896. if (typeof families[family] === 'undefined') {
  1897. families[family] = {
  1898. name: family,
  1899. arr: [],
  1900. priority: chart.priority
  1901. };
  1902. families_sort.push(families[family]);
  1903. }
  1904. if (chart.priority < families[family].priority) {
  1905. families[family].priority = chart.priority;
  1906. }
  1907. families[family].arr.unshift(alarm);
  1908. }
  1909. // sort the families, like the dashboard menu does
  1910. var families_sorted = families_sort.sort(function (a, b) {
  1911. if (a.priority < b.priority) {
  1912. return -1;
  1913. }
  1914. if (a.priority > b.priority) {
  1915. return 1;
  1916. }
  1917. return naturalSortCompare(a.name, b.name);
  1918. });
  1919. var i = 0;
  1920. var fc = 0;
  1921. var len = families_sorted.length;
  1922. while (len--) {
  1923. family = families_sorted[i++].name;
  1924. var active_family_added = false;
  1925. var expanded = 'true';
  1926. var collapsed = '';
  1927. var cin = 'in';
  1928. if (fc !== 0) {
  1929. all += "</table></div></div></div>";
  1930. expanded = 'false';
  1931. collapsed = 'class="collapsed"';
  1932. cin = '';
  1933. }
  1934. all += '<div class="panel panel-default"><div class="panel-heading" role="tab" id="alarm_all_heading_' + fc.toString() + '"><h4 class="panel-title"><a ' + collapsed + ' role="button" data-toggle="collapse" data-parent="#alarms_all_accordion" href="#alarm_all_' + fc.toString() + '" aria-expanded="' + expanded + '" aria-controls="alarm_all_' + fc.toString() + '">' + family.toString() + '</a></h4></div><div id="alarm_all_' + fc.toString() + '" class="panel-collapse collapse ' + cin + '" role="tabpanel" aria-labelledby="alarm_all_heading_' + fc.toString() + '" data-alarm-id="' + fc.toString() + '"><div class="panel-body" id="alarm_all_body_' + fc.toString() + '">';
  1935. options.alarm_families[fc] = families[family];
  1936. fc++;
  1937. var arr = families[family].arr;
  1938. var c = arr.length;
  1939. while (c--) {
  1940. alarm = arr[c];
  1941. if (alarm.status === 'WARNING' || alarm.status === 'CRITICAL') {
  1942. if (!active_family_added) {
  1943. active_family_added = true;
  1944. active += '<tr><th class="text-center" colspan="2"><h4>' + family + '</h4></th></tr>';
  1945. }
  1946. count_active++;
  1947. active += alarm_to_html(alarm, true);
  1948. }
  1949. count_all++;
  1950. }
  1951. }
  1952. active += "</table>";
  1953. if (families_sorted.length > 0) {
  1954. all += "</div></div></div>";
  1955. }
  1956. all += "</div>";
  1957. if (!count_active) {
  1958. active += '<div style="width:100%; height: 100px; text-align: center;"><span style="font-size: 50px;"><i class="fas fa-thumbs-up"></i></span><br/>Everything is normal. No raised alarms.</div>';
  1959. } else {
  1960. active += footer;
  1961. }
  1962. if (!count_all) {
  1963. all += "<h4>No alarms are running in this system.</h4>";
  1964. } else {
  1965. all += footer;
  1966. }
  1967. document.getElementById('alarms_active').innerHTML = active;
  1968. document.getElementById('alarms_all').innerHTML = all;
  1969. enableTooltipsAndPopovers();
  1970. if (families_sorted.length > 0) {
  1971. alarm_family_show(0);
  1972. }
  1973. // register bootstrap events
  1974. var $accordion = $('#alarms_all_accordion');
  1975. $accordion.on('show.bs.collapse', function (d) {
  1976. var target = $(d.target);
  1977. var id = $(target).data('alarm-id');
  1978. alarm_family_show(id);
  1979. });
  1980. $accordion.on('hidden.bs.collapse', function (d) {
  1981. var target = $(d.target);
  1982. var id = $(target).data('alarm-id');
  1983. $('#alarm_all_' + id.toString()).html('');
  1984. });
  1985. document.getElementById('alarms_log').innerHTML = '<h3>Alarm Log</h3><table id="alarms_log_table"></table>';
  1986. loadBootstrapTable(function () {
  1987. $('#alarms_log_table').bootstrapTable({
  1988. url: NETDATA.alarms.server + '/api/v1/alarm_log?all',
  1989. cache: false,
  1990. pagination: true,
  1991. pageSize: 10,
  1992. showPaginationSwitch: false,
  1993. search: true,
  1994. searchTimeOut: 300,
  1995. searchAlign: 'left',
  1996. showColumns: true,
  1997. showExport: true,
  1998. exportDataType: 'basic',
  1999. exportOptions: {
  2000. fileName: 'netdata_alarm_log'
  2001. },
  2002. onClickRow: function (row, $element,field) {
  2003. void (field);
  2004. void ($element);
  2005. let main_url;
  2006. let common_url = "&host=" + encodeURIComponent(row['hostname']) + "&chart=" + encodeURIComponent(row['chart']) + "&family=" + encodeURIComponent(row['family']) + "&alarm=" + encodeURIComponent(row['name']) + "&alarm_unique_id=" + row['unique_id'] + "&alarm_id=" + row['alarm_id'] + "&alarm_event_id=" + row['alarm_event_id'] + "&alarm_when=" + row['when'];
  2007. if (NETDATA.registry.isUsingGlobalRegistry() && NETDATA.registry.machine_guid != null) {
  2008. main_url = "https://netdata.cloud/alarms/redirect?agentID=" + NETDATA.registry.machine_guid + common_url;
  2009. } else {
  2010. main_url = NETDATA.registry.server + "/goto-host-from-alarm.html?" + common_url ;
  2011. }
  2012. window.open(main_url,"_blank");
  2013. },
  2014. rowStyle: function (row, index) {
  2015. void (index);
  2016. switch (row.status) {
  2017. case 'CRITICAL':
  2018. return { classes: 'danger' };
  2019. break;
  2020. case 'WARNING':
  2021. return { classes: 'warning' };
  2022. break;
  2023. case 'UNDEFINED':
  2024. return { classes: 'info' };
  2025. break;
  2026. case 'CLEAR':
  2027. return { classes: 'success' };
  2028. break;
  2029. }
  2030. return {};
  2031. },
  2032. showFooter: false,
  2033. showHeader: true,
  2034. showRefresh: true,
  2035. showToggle: false,
  2036. sortable: true,
  2037. silentSort: false,
  2038. columns: [
  2039. {
  2040. field: 'when',
  2041. title: 'Event Date',
  2042. valign: 'middle',
  2043. titleTooltip: 'The date and time the even took place',
  2044. formatter: function (value, row, index) {
  2045. void (row);
  2046. void (index);
  2047. return timestamp4human(value, ' ');
  2048. },
  2049. align: 'center',
  2050. switchable: false,
  2051. sortable: true
  2052. },
  2053. {
  2054. field: 'hostname',
  2055. title: 'Host',
  2056. valign: 'middle',
  2057. titleTooltip: 'The host that generated this event',
  2058. align: 'center',
  2059. visible: false,
  2060. sortable: true
  2061. },
  2062. {
  2063. field: 'unique_id',
  2064. title: 'Unique ID',
  2065. titleTooltip: 'The host unique ID for this event',
  2066. formatter: function (value, row, index) {
  2067. void (row);
  2068. void (index);
  2069. return alarmid4human(value);
  2070. },
  2071. align: 'center',
  2072. valign: 'middle',
  2073. visible: false,
  2074. sortable: true
  2075. },
  2076. {
  2077. field: 'alarm_id',
  2078. title: 'Alarm ID',
  2079. titleTooltip: 'The ID of the alarm that generated this event',
  2080. formatter: function (value, row, index) {
  2081. void (row);
  2082. void (index);
  2083. return alarmid4human(value);
  2084. },
  2085. align: 'center',
  2086. valign: 'middle',
  2087. visible: false,
  2088. sortable: true
  2089. },
  2090. {
  2091. field: 'alarm_event_id',
  2092. title: 'Alarm Event ID',
  2093. titleTooltip: 'The incremental ID of this event for the given alarm',
  2094. formatter: function (value, row, index) {
  2095. void (row);
  2096. void (index);
  2097. return alarmid4human(value);
  2098. },
  2099. align: 'center',
  2100. valign: 'middle',
  2101. visible: false,
  2102. sortable: true
  2103. },
  2104. {
  2105. field: 'chart',
  2106. title: 'Chart',
  2107. titleTooltip: 'The chart the alarm is attached to',
  2108. align: 'center',
  2109. valign: 'middle',
  2110. switchable: false,
  2111. sortable: true
  2112. },
  2113. {
  2114. field: 'family',
  2115. title: 'Family',
  2116. titleTooltip: 'The family of the chart the alarm is attached to',
  2117. align: 'center',
  2118. valign: 'middle',
  2119. visible: false,
  2120. sortable: true
  2121. },
  2122. {
  2123. field: 'name',
  2124. title: 'Alarm',
  2125. titleTooltip: 'The alarm name that generated this event',
  2126. formatter: function (value, row, index) {
  2127. void (row);
  2128. void (index);
  2129. return value.toString().replace(/_/g, ' ');
  2130. },
  2131. align: 'center',
  2132. valign: 'middle',
  2133. switchable: false,
  2134. sortable: true
  2135. },
  2136. {
  2137. field: 'value_string',
  2138. title: 'Friendly Value',
  2139. titleTooltip: 'The value of the alarm, that triggered this event',
  2140. align: 'right',
  2141. valign: 'middle',
  2142. sortable: true
  2143. },
  2144. {
  2145. field: 'old_value_string',
  2146. title: 'Friendly Old Value',
  2147. titleTooltip: 'The value of the alarm, just before this event',
  2148. align: 'right',
  2149. valign: 'middle',
  2150. visible: false,
  2151. sortable: true
  2152. },
  2153. {
  2154. field: 'old_value',
  2155. title: 'Old Value',
  2156. titleTooltip: 'The value of the alarm, just before this event',
  2157. formatter: function (value, row, index) {
  2158. void (row);
  2159. void (index);
  2160. return ((value !== null) ? Math.round(value * 100) / 100 : 'NaN').toString();
  2161. },
  2162. align: 'center',
  2163. valign: 'middle',
  2164. visible: false,
  2165. sortable: true
  2166. },
  2167. {
  2168. field: 'value',
  2169. title: 'Value',
  2170. titleTooltip: 'The value of the alarm, that triggered this event',
  2171. formatter: function (value, row, index) {
  2172. void (row);
  2173. void (index);
  2174. return ((value !== null) ? Math.round(value * 100) / 100 : 'NaN').toString();
  2175. },
  2176. align: 'right',
  2177. valign: 'middle',
  2178. visible: false,
  2179. sortable: true
  2180. },
  2181. {
  2182. field: 'units',
  2183. title: 'Units',
  2184. titleTooltip: 'The units of the value of the alarm',
  2185. align: 'left',
  2186. valign: 'middle',
  2187. visible: false,
  2188. sortable: true
  2189. },
  2190. {
  2191. field: 'old_status',
  2192. title: 'Old Status',
  2193. titleTooltip: 'The status of the alarm, just before this event',
  2194. align: 'center',
  2195. valign: 'middle',
  2196. visible: false,
  2197. sortable: true
  2198. },
  2199. {
  2200. field: 'status',
  2201. title: 'Status',
  2202. titleTooltip: 'The status of the alarm, that was set due to this event',
  2203. align: 'center',
  2204. valign: 'middle',
  2205. switchable: false,
  2206. sortable: true
  2207. },
  2208. {
  2209. field: 'duration',
  2210. title: 'Last Duration',
  2211. titleTooltip: 'The duration the alarm was at its previous state, just before this event',
  2212. formatter: function (value, row, index) {
  2213. void (row);
  2214. void (index);
  2215. return NETDATA.seconds4human(value, { negative_suffix: '', space: ' ', now: 'no time' });
  2216. },
  2217. align: 'center',
  2218. valign: 'middle',
  2219. visible: false,
  2220. sortable: true
  2221. },
  2222. {
  2223. field: 'non_clear_duration',
  2224. title: 'Raised Duration',
  2225. titleTooltip: 'The duration the alarm was raised, just before this event',
  2226. formatter: function (value, row, index) {
  2227. void (row);
  2228. void (index);
  2229. return NETDATA.seconds4human(value, { negative_suffix: '', space: ' ', now: 'no time' });
  2230. },
  2231. align: 'center',
  2232. valign: 'middle',
  2233. visible: false,
  2234. sortable: true
  2235. },
  2236. {
  2237. field: 'recipient',
  2238. title: 'Recipient',
  2239. titleTooltip: 'The recipient of this event',
  2240. align: 'center',
  2241. valign: 'middle',
  2242. visible: false,
  2243. sortable: true
  2244. },
  2245. {
  2246. field: 'processed',
  2247. title: 'Processed Status',
  2248. titleTooltip: 'True when this event is processed',
  2249. formatter: function (value, row, index) {
  2250. void (row);
  2251. void (index);
  2252. if (value === true) {
  2253. return 'DONE';
  2254. } else {
  2255. return 'PENDING';
  2256. }
  2257. },
  2258. align: 'center',
  2259. valign: 'middle',
  2260. visible: false,
  2261. sortable: true
  2262. },
  2263. {
  2264. field: 'updated',
  2265. title: 'Updated Status',
  2266. titleTooltip: 'True when this event has been updated by another event',
  2267. formatter: function (value, row, index) {
  2268. void (row);
  2269. void (index);
  2270. if (value === true) {
  2271. return 'UPDATED';
  2272. } else {
  2273. return 'CURRENT';
  2274. }
  2275. },
  2276. align: 'center',
  2277. valign: 'middle',
  2278. visible: false,
  2279. sortable: true
  2280. },
  2281. {
  2282. field: 'updated_by_id',
  2283. title: 'Updated By ID',
  2284. titleTooltip: 'The unique ID of the event that obsoleted this one',
  2285. formatter: function (value, row, index) {
  2286. void (row);
  2287. void (index);
  2288. return alarmid4human(value);
  2289. },
  2290. align: 'center',
  2291. valign: 'middle',
  2292. visible: false,
  2293. sortable: true
  2294. },
  2295. {
  2296. field: 'updates_id',
  2297. title: 'Updates ID',
  2298. titleTooltip: 'The unique ID of the event obsoleted because of this event',
  2299. formatter: function (value, row, index) {
  2300. void (row);
  2301. void (index);
  2302. return alarmid4human(value);
  2303. },
  2304. align: 'center',
  2305. valign: 'middle',
  2306. visible: false,
  2307. sortable: true
  2308. },
  2309. {
  2310. field: 'exec',
  2311. title: 'Script',
  2312. titleTooltip: 'The script to handle the event notification',
  2313. align: 'center',
  2314. valign: 'middle',
  2315. visible: false,
  2316. sortable: true
  2317. },
  2318. {
  2319. field: 'exec_run',
  2320. title: 'Script Run At',
  2321. titleTooltip: 'The date and time the script has been ran',
  2322. formatter: function (value, row, index) {
  2323. void (row);
  2324. void (index);
  2325. return timestamp4human(value, ' ');
  2326. },
  2327. align: 'center',
  2328. valign: 'middle',
  2329. visible: false,
  2330. sortable: true
  2331. },
  2332. {
  2333. field: 'exec_code',
  2334. title: 'Script Return Value',
  2335. titleTooltip: 'The return code of the script',
  2336. formatter: function (value, row, index) {
  2337. void (row);
  2338. void (index);
  2339. if (value === 0) {
  2340. return 'OK (returned 0)';
  2341. } else {
  2342. return 'FAILED (with code ' + value.toString() + ')';
  2343. }
  2344. },
  2345. align: 'center',
  2346. valign: 'middle',
  2347. visible: false,
  2348. sortable: true
  2349. },
  2350. {
  2351. field: 'delay',
  2352. title: 'Script Delay',
  2353. titleTooltip: 'The hysteresis of the notification',
  2354. formatter: function (value, row, index) {
  2355. void (row);
  2356. void (index);
  2357. return NETDATA.seconds4human(value, { negative_suffix: '', space: ' ', now: 'no time' });
  2358. },
  2359. align: 'center',
  2360. valign: 'middle',
  2361. visible: false,
  2362. sortable: true
  2363. },
  2364. {
  2365. field: 'delay_up_to_timestamp',
  2366. title: 'Script Delay Run At',
  2367. titleTooltip: 'The date and time the script should be run, after hysteresis',
  2368. formatter: function (value, row, index) {
  2369. void (row);
  2370. void (index);
  2371. return timestamp4human(value, ' ');
  2372. },
  2373. align: 'center',
  2374. valign: 'middle',
  2375. visible: false,
  2376. sortable: true
  2377. },
  2378. {
  2379. field: 'info',
  2380. title: 'Description',
  2381. titleTooltip: 'A short description of the alarm',
  2382. align: 'center',
  2383. valign: 'middle',
  2384. visible: false,
  2385. sortable: true
  2386. },
  2387. {
  2388. field: 'source',
  2389. title: 'Alarm Source',
  2390. titleTooltip: 'The source of configuration of the alarm',
  2391. align: 'center',
  2392. valign: 'middle',
  2393. visible: false,
  2394. sortable: true
  2395. }
  2396. ]
  2397. });
  2398. // console.log($('#alarms_log_table').bootstrapTable('getOptions'));
  2399. });
  2400. });
  2401. }
  2402. function alarmsCallback(data) {
  2403. var count = 0, x;
  2404. for (x in data.alarms) {
  2405. if (!data.alarms.hasOwnProperty(x)) {
  2406. continue;
  2407. }
  2408. var alarm = data.alarms[x];
  2409. if (alarm.status === 'WARNING' || alarm.status === 'CRITICAL') {
  2410. count++;
  2411. }
  2412. }
  2413. if (count > 0) {
  2414. document.getElementById('alarms_count_badge').innerHTML = count.toString();
  2415. } else {
  2416. document.getElementById('alarms_count_badge').innerHTML = '';
  2417. }
  2418. }
  2419. function initializeDynamicDashboardWithData(data) {
  2420. if (data !== null) {
  2421. options.hostname = data.hostname;
  2422. options.data = data;
  2423. options.version = data.version;
  2424. options.release_channel = data.release_channel;
  2425. netdataDashboard.os = data.os;
  2426. if (typeof data.hosts !== 'undefined') {
  2427. options.hosts = data.hosts;
  2428. }
  2429. // update the dashboard hostname
  2430. document.getElementById('hostname').innerHTML = '<span id="hostnametext">' + options.hostname + ((netdataSnapshotData !== null) ? ' (snap)' : '').toString() + '</span>&nbsp;&nbsp;<strong class="caret">';
  2431. document.getElementById('hostname').href = NETDATA.serverDefault;
  2432. document.getElementById('netdataVersion').innerHTML = options.version;
  2433. if (netdataSnapshotData !== null) {
  2434. $('#alarmsButton').hide();
  2435. $('#updateButton').hide();
  2436. // $('#loadButton').hide();
  2437. $('#saveButton').hide();
  2438. $('#printButton').hide();
  2439. }
  2440. // update the dashboard title
  2441. document.title = options.hostname + ' netdata dashboard';
  2442. // close the splash screen
  2443. $("#loadOverlay").css("display", "none");
  2444. // create a chart_by_name index
  2445. data.charts_by_name = {};
  2446. var charts = data.charts;
  2447. var x;
  2448. for (x in charts) {
  2449. if (!charts.hasOwnProperty(x)) {
  2450. continue;
  2451. }
  2452. var chart = charts[x];
  2453. data.charts_by_name[chart.name] = chart;
  2454. }
  2455. // render all charts
  2456. renderChartsAndMenu(data);
  2457. // Ensure MyNetdata menu is rendered with latest host info #5370
  2458. renderMyNetdataMenu(isSignedIn() ? cloudAgents : registryAgents);
  2459. }
  2460. }
  2461. // an object to keep initialization configuration
  2462. // needed due to the async nature of the XSS modal
  2463. var initializeConfig = {
  2464. url: null,
  2465. custom_info: true,
  2466. };
  2467. function loadCustomDashboardInfo(url, callback) {
  2468. loadJs(url, function () {
  2469. $.extend(true, netdataDashboard, customDashboard);
  2470. callback();
  2471. });
  2472. }
  2473. function initializeChartsAndCustomInfo() {
  2474. NETDATA.alarms.callback = alarmsCallback;
  2475. // download all the charts the server knows
  2476. NETDATA.chartRegistry.downloadAll(initializeConfig.url, function (data) {
  2477. if (data !== null) {
  2478. if (initializeConfig.custom_info === true && typeof data.custom_info !== 'undefined' && data.custom_info !== "" && netdataSnapshotData === null) {
  2479. //console.log('loading custom dashboard decorations from server ' + initializeConfig.url);
  2480. loadCustomDashboardInfo(NETDATA.serverDefault + data.custom_info, function () {
  2481. initializeDynamicDashboardWithData(data);
  2482. });
  2483. } else {
  2484. //console.log('not loading custom dashboard decorations from server ' + initializeConfig.url);
  2485. initializeDynamicDashboardWithData(data);
  2486. }
  2487. }
  2488. });
  2489. }
  2490. function xssModalDisableXss() {
  2491. //console.log('disabling xss checks');
  2492. NETDATA.xss.enabled = false;
  2493. NETDATA.xss.enabled_for_data = false;
  2494. initializeConfig.custom_info = true;
  2495. initializeChartsAndCustomInfo();
  2496. return false;
  2497. }
  2498. function xssModalKeepXss() {
  2499. //console.log('keeping xss checks');
  2500. NETDATA.xss.enabled = true;
  2501. NETDATA.xss.enabled_for_data = true;
  2502. initializeConfig.custom_info = false;
  2503. initializeChartsAndCustomInfo();
  2504. return false;
  2505. }
  2506. function initializeDynamicDashboard(netdata_url) {
  2507. if (typeof netdata_url === 'undefined' || netdata_url === null) {
  2508. netdata_url = NETDATA.serverDefault;
  2509. }
  2510. initializeConfig.url = netdata_url;
  2511. // initialize clickable alarms
  2512. NETDATA.alarms.chart_div_offset = -50;
  2513. NETDATA.alarms.chart_div_id_prefix = 'chart_';
  2514. NETDATA.alarms.chart_div_animation_duration = 0;
  2515. NETDATA.pause(function () {
  2516. if (typeof netdataCheckXSS !== 'undefined' && netdataCheckXSS === true) {
  2517. //$("#loadOverlay").css("display","none");
  2518. document.getElementById('netdataXssModalServer').innerText = initializeConfig.url;
  2519. $('#xssModal').modal('show');
  2520. } else {
  2521. initializeChartsAndCustomInfo();
  2522. }
  2523. });
  2524. }
  2525. // ----------------------------------------------------------------------------
  2526. function versionLog(msg) {
  2527. document.getElementById('versionCheckLog').innerHTML = msg;
  2528. }
  2529. // New way of checking for updates, based only on versions
  2530. function versionsMatch(v1, v2) {
  2531. if (v1 == v2) {
  2532. return true;
  2533. } else {
  2534. let s1 = v1.split('.');
  2535. let s2 = v2.split('.');
  2536. // Check major version
  2537. let n1 = parseInt(s1[0].substring(1, 2), 10);
  2538. let n2 = parseInt(s2[0].substring(1, 2), 10);
  2539. if (n1 < n2) return false;
  2540. else if (n1 > n2) return true;
  2541. // Check minor version
  2542. n1 = parseInt(s1[1], 10);
  2543. n2 = parseInt(s2[1], 10);
  2544. if (n1 < n2) return false;
  2545. else if (n1 > n2) return true;
  2546. // Split patch: format could be e.g. 0-22-nightly
  2547. s1 = s1[2].split('-');
  2548. s2 = s2[2].split('-');
  2549. n1 = parseInt(s1[0], 10);
  2550. n2 = parseInt(s2[0], 10);
  2551. if (n1 < n2) return false;
  2552. else if (n1 > n2) return true;
  2553. n1 = (s1.length > 1) ? parseInt(s1[1], 10) : 0;
  2554. n2 = (s2.length > 1) ? parseInt(s2[1], 10) : 0;
  2555. if (n1 < n2) return false;
  2556. else return true;
  2557. }
  2558. }
  2559. function getGithubLatestVersion(callback) {
  2560. versionLog('Downloading latest version id from github...');
  2561. $.ajax({
  2562. url: 'https://api.github.com/repos/netdata/netdata/releases/latest',
  2563. async: true,
  2564. cache: false
  2565. })
  2566. .done(function (data) {
  2567. data = data.tag_name.replace(/(\r\n|\n|\r| |\t)/gm, "");
  2568. versionLog('Latest stable version from github is ' + data);
  2569. callback(data);
  2570. })
  2571. .fail(function () {
  2572. versionLog('Failed to download the latest stable version id from github!');
  2573. callback(null);
  2574. });
  2575. }
  2576. function getGCSLatestVersion(callback) {
  2577. versionLog('Downloading latest version id from GCS...');
  2578. $.ajax({
  2579. url: "https://www.googleapis.com/storage/v1/b/netdata-nightlies/o/latest-version.txt",
  2580. async: true,
  2581. cache: false
  2582. })
  2583. .done(function (response) {
  2584. $.ajax({
  2585. url: response.mediaLink,
  2586. async: true,
  2587. cache: false
  2588. })
  2589. .done(function (data) {
  2590. data = data.replace(/(\r\n|\n|\r| |\t)/gm, "");
  2591. versionLog('Latest nightly version from GCS is ' + data);
  2592. callback(data);
  2593. })
  2594. .fail(function (xhr, textStatus, errorThrown) {
  2595. versionLog('Failed to download the latest nightly version id from GCS!');
  2596. callback(null);
  2597. });
  2598. })
  2599. .fail(function (xhr, textStatus, errorThrown) {
  2600. versionLog('Failed to download the latest nightly version from GCS!');
  2601. callback(null);
  2602. });
  2603. }
  2604. function checkForUpdateByVersion(force, callback) {
  2605. if (options.release_channel === 'stable') {
  2606. getGithubLatestVersion(function (sha2) {
  2607. callback(options.version, sha2);
  2608. });
  2609. } else {
  2610. getGCSLatestVersion(function (sha2) {
  2611. callback(options.version, sha2);
  2612. });
  2613. }
  2614. return null;
  2615. }
  2616. function notifyForUpdate(force) {
  2617. versionLog('<p>checking for updates...</p>');
  2618. var now = Date.now();
  2619. if (typeof force === 'undefined' || force !== true) {
  2620. var last = loadLocalStorage('last_update_check');
  2621. if (typeof last === 'string') {
  2622. last = parseInt(last);
  2623. } else {
  2624. last = 0;
  2625. }
  2626. if (now - last < 3600000 * 8) {
  2627. // no need to check it - too soon
  2628. return;
  2629. }
  2630. }
  2631. checkForUpdateByVersion(force, function (sha1, sha2) {
  2632. var save = false;
  2633. if (sha1 === null) {
  2634. save = false;
  2635. versionLog('<p><big>Failed to get your netdata version!</big></p><p>You can always get the latest netdata from <a href="https://github.com/netdata/netdata" target="_blank">its github page</a>.</p>');
  2636. } else if (sha2 === null) {
  2637. save = false;
  2638. versionLog('<p><big>Failed to get the latest netdata version.</big></p><p>You can always get the latest netdata from <a href="https://github.com/netdata/netdata" target="_blank">its github page</a>.</p>');
  2639. } else if (versionsMatch(sha1, sha2)) {
  2640. save = true;
  2641. versionLog('<p><big>You already have the latest netdata!</big></p><p>No update yet?<br/>We probably need some motivation to keep going on!</p><p>If you haven\'t already, <a href="https://github.com/netdata/netdata" target="_blank">give netdata a <b><i class="fas fa-star"></i></b> at its github page</a>.</p>');
  2642. } else {
  2643. save = true;
  2644. var compare = 'https://learn.netdata.cloud/docs/agent/changelog/';
  2645. versionLog('<p><big><strong>New version of netdata available!</strong></big></p><p>Latest version: <b><code>' + sha2 + '</code></b></p><p><a href="' + compare + '" target="_blank">Click here for the changes log</a> and<br/><a href="https://github.com/netdata/netdata/tree/master/packaging/installer/UPDATE.md" target="_blank">click here for directions on updating</a> your netdata installation.</p><p>We suggest to review the changes log for new features you may be interested, or important bug fixes you may need.<br/>Keeping your netdata updated is generally a good idea.</p>');
  2646. document.getElementById('update_badge').innerHTML = '!';
  2647. }
  2648. if (save) {
  2649. saveLocalStorage('last_update_check', now.toString());
  2650. }
  2651. });
  2652. }
  2653. // ----------------------------------------------------------------------------
  2654. // printing dashboards
  2655. function showPageFooter() {
  2656. document.getElementById('footer').style.display = 'block';
  2657. }
  2658. function printPreflight() {
  2659. var url = document.location.origin.toString() + document.location.pathname.toString() + document.location.search.toString() + urlOptions.genHash() + ';mode=print';
  2660. var width = 990;
  2661. var height = screen.height * 90 / 100;
  2662. //console.log(url);
  2663. //console.log(document.location);
  2664. window.open(url, '', 'width=' + width.toString() + ',height=' + height.toString() + ',menubar=no,toolbar=no,personalbar=no,location=no,resizable=no,scrollbars=yes,status=no,chrome=yes,centerscreen=yes,attention=yes,dialog=yes');
  2665. $('#printPreflightModal').modal('hide');
  2666. }
  2667. function printPage() {
  2668. var print_is_rendering = true;
  2669. $('#printModal').on('hide.bs.modal', function (e) {
  2670. if (print_is_rendering === true) {
  2671. e.preventDefault();
  2672. return false;
  2673. }
  2674. return true;
  2675. });
  2676. $('#printModal').on('show.bs.modal', function () {
  2677. var print_options = {
  2678. stop_updates_when_focus_is_lost: false,
  2679. update_only_visible: false,
  2680. sync_selection: false,
  2681. eliminate_zero_dimensions: false,
  2682. pan_and_zoom_data_padding: false,
  2683. show_help: false,
  2684. legend_toolbox: false,
  2685. resize_charts: false,
  2686. pixels_per_point: 1
  2687. };
  2688. var x;
  2689. for (x in print_options) {
  2690. if (print_options.hasOwnProperty(x)) {
  2691. NETDATA.options.current[x] = print_options[x];
  2692. }
  2693. }
  2694. NETDATA.parseDom();
  2695. showPageFooter();
  2696. NETDATA.globalSelectionSync.stop();
  2697. NETDATA.globalPanAndZoom.setMaster(NETDATA.options.targets[0], urlOptions.after, urlOptions.before);
  2698. // NETDATA.onresize();
  2699. var el = document.getElementById('printModalProgressBar');
  2700. var eltxt = document.getElementById('printModalProgressBarText');
  2701. function update_chart(idx) {
  2702. var state = NETDATA.options.targets[--idx];
  2703. var pcent = (NETDATA.options.targets.length - idx) * 100 / NETDATA.options.targets.length;
  2704. $(el).css('width', pcent + '%').attr('aria-valuenow', pcent);
  2705. eltxt.innerText = Math.round(pcent).toString() + '%, ' + state.id;
  2706. setTimeout(function () {
  2707. state.updateChart(function () {
  2708. NETDATA.options.targets[idx].resizeForPrint();
  2709. if (idx > 0) {
  2710. update_chart(idx);
  2711. } else {
  2712. print_is_rendering = false;
  2713. $('#printModal').modal('hide');
  2714. window.print();
  2715. window.close();
  2716. }
  2717. })
  2718. }, 0);
  2719. }
  2720. print_is_rendering = true;
  2721. update_chart(NETDATA.options.targets.length);
  2722. });
  2723. $('#printModal').modal('show');
  2724. }
  2725. // --------------------------------------------------------------------
  2726. function jsonStringifyFn(obj) {
  2727. return JSON.stringify(obj, function (key, value) {
  2728. return (typeof value === 'function') ? value.toString() : value;
  2729. });
  2730. }
  2731. function jsonParseFn(str) {
  2732. return JSON.parse(str, function (key, value) {
  2733. if (typeof value != 'string') {
  2734. return value;
  2735. }
  2736. return (value.substring(0, 8) == 'function') ? eval('(' + value + ')') : value;
  2737. });
  2738. }
  2739. // --------------------------------------------------------------------
  2740. var snapshotOptions = {
  2741. bytes_per_chart: 2048,
  2742. compressionDefault: 'pako.deflate.base64',
  2743. compressions: {
  2744. 'none': {
  2745. bytes_per_point_memory: 5.2,
  2746. bytes_per_point_disk: 5.6,
  2747. compress: function (s) {
  2748. return s;
  2749. },
  2750. compressed_length: function (s) {
  2751. return s.length;
  2752. },
  2753. uncompress: function (s) {
  2754. return s;
  2755. }
  2756. },
  2757. 'pako.deflate.base64': {
  2758. bytes_per_point_memory: 1.8,
  2759. bytes_per_point_disk: 1.9,
  2760. compress: function (s) {
  2761. return btoa(pako.deflate(s, { to: 'string' }));
  2762. },
  2763. compressed_length: function (s) {
  2764. return s.length;
  2765. },
  2766. uncompress: function (s) {
  2767. return pako.inflate(atob(s), { to: 'string' });
  2768. }
  2769. },
  2770. 'pako.deflate': {
  2771. bytes_per_point_memory: 1.4,
  2772. bytes_per_point_disk: 3.2,
  2773. compress: function (s) {
  2774. return pako.deflate(s, { to: 'string' });
  2775. },
  2776. compressed_length: function (s) {
  2777. return s.length;
  2778. },
  2779. uncompress: function (s) {
  2780. return pako.inflate(s, { to: 'string' });
  2781. }
  2782. },
  2783. 'lzstring.utf16': {
  2784. bytes_per_point_memory: 1.7,
  2785. bytes_per_point_disk: 2.6,
  2786. compress: function (s) {
  2787. return LZString.compressToUTF16(s);
  2788. },
  2789. compressed_length: function (s) {
  2790. return s.length * 2;
  2791. },
  2792. uncompress: function (s) {
  2793. return LZString.decompressFromUTF16(s);
  2794. }
  2795. },
  2796. 'lzstring.base64': {
  2797. bytes_per_point_memory: 2.1,
  2798. bytes_per_point_disk: 2.3,
  2799. compress: function (s) {
  2800. return LZString.compressToBase64(s);
  2801. },
  2802. compressed_length: function (s) {
  2803. return s.length;
  2804. },
  2805. uncompress: function (s) {
  2806. return LZString.decompressFromBase64(s);
  2807. }
  2808. },
  2809. 'lzstring.uri': {
  2810. bytes_per_point_memory: 2.1,
  2811. bytes_per_point_disk: 2.3,
  2812. compress: function (s) {
  2813. return LZString.compressToEncodedURIComponent(s);
  2814. },
  2815. compressed_length: function (s) {
  2816. return s.length;
  2817. },
  2818. uncompress: function (s) {
  2819. return LZString.decompressFromEncodedURIComponent(s);
  2820. }
  2821. }
  2822. }
  2823. };
  2824. // --------------------------------------------------------------------
  2825. // loading snapshots
  2826. function loadSnapshotModalLog(priority, msg) {
  2827. document.getElementById('loadSnapshotStatus').className = "alert alert-" + priority;
  2828. document.getElementById('loadSnapshotStatus').innerHTML = msg;
  2829. }
  2830. var tmpSnapshotData = null;
  2831. function loadSnapshot() {
  2832. $('#loadSnapshotImport').addClass('disabled');
  2833. if (tmpSnapshotData === null) {
  2834. loadSnapshotPreflightEmpty();
  2835. loadSnapshotModalLog('danger', 'no data have been loaded');
  2836. return;
  2837. }
  2838. loadPako(function () {
  2839. loadLzString(function () {
  2840. loadSnapshotModalLog('info', 'Please wait, activating snapshot...');
  2841. $('#loadSnapshotModal').modal('hide');
  2842. netdataShowAlarms = false;
  2843. netdataRegistry = false;
  2844. netdataServer = tmpSnapshotData.server;
  2845. NETDATA.serverDefault = netdataServer;
  2846. document.getElementById('charts_div').innerHTML = '';
  2847. document.getElementById('sidebar').innerHTML = '';
  2848. NETDATA.globalReset();
  2849. if (typeof tmpSnapshotData.hash !== 'undefined') {
  2850. urlOptions.hash = tmpSnapshotData.hash;
  2851. } else {
  2852. urlOptions.hash = '#';
  2853. }
  2854. if (typeof tmpSnapshotData.info !== 'undefined') {
  2855. var info = jsonParseFn(tmpSnapshotData.info);
  2856. if (typeof info.menu !== 'undefined') {
  2857. netdataDashboard.menu = info.menu;
  2858. }
  2859. if (typeof info.submenu !== 'undefined') {
  2860. netdataDashboard.submenu = info.submenu;
  2861. }
  2862. if (typeof info.context !== 'undefined') {
  2863. netdataDashboard.context = info.context;
  2864. }
  2865. }
  2866. if (typeof tmpSnapshotData.compression !== 'string') {
  2867. tmpSnapshotData.compression = 'none';
  2868. }
  2869. if (typeof snapshotOptions.compressions[tmpSnapshotData.compression] === 'undefined') {
  2870. alert('unknown compression method: ' + tmpSnapshotData.compression);
  2871. tmpSnapshotData.compression = 'none';
  2872. }
  2873. tmpSnapshotData.uncompress = snapshotOptions.compressions[tmpSnapshotData.compression].uncompress;
  2874. netdataSnapshotData = tmpSnapshotData;
  2875. urlOptions.after = tmpSnapshotData.after_ms;
  2876. urlOptions.before = tmpSnapshotData.before_ms;
  2877. if (typeof tmpSnapshotData.highlight_after_ms !== 'undefined'
  2878. && tmpSnapshotData.highlight_after_ms !== null
  2879. && tmpSnapshotData.highlight_after_ms > 0
  2880. && typeof tmpSnapshotData.highlight_before_ms !== 'undefined'
  2881. && tmpSnapshotData.highlight_before_ms !== null
  2882. && tmpSnapshotData.highlight_before_ms > 0
  2883. ) {
  2884. urlOptions.highlight_after = tmpSnapshotData.highlight_after_ms;
  2885. urlOptions.highlight_before = tmpSnapshotData.highlight_before_ms;
  2886. urlOptions.highlight = true;
  2887. } else {
  2888. urlOptions.highlight_after = 0;
  2889. urlOptions.highlight_before = 0;
  2890. urlOptions.highlight = false;
  2891. }
  2892. netdataCheckXSS = false; // disable the modal - this does not affect XSS checks, since dashboard.js is already loaded
  2893. NETDATA.xss.enabled = true; // we should not do any remote requests, but if we do, check them
  2894. NETDATA.xss.enabled_for_data = true; // check also snapshot data - that have been excluded from the initial check, due to compression
  2895. loadSnapshotPreflightEmpty();
  2896. initializeDynamicDashboard();
  2897. });
  2898. });
  2899. };
  2900. function loadSnapshotPreflightFile(file) {
  2901. var filename = NETDATA.xss.string(file.name);
  2902. var fr = new FileReader();
  2903. fr.onload = function (e) {
  2904. document.getElementById('loadSnapshotFilename').innerHTML = filename;
  2905. var result = null;
  2906. try {
  2907. result = NETDATA.xss.checkAlways('snapshot', JSON.parse(e.target.result), /^(snapshot\.info|snapshot\.data)$/);
  2908. //console.log(result);
  2909. var date_after = new Date(result.after_ms);
  2910. var date_before = new Date(result.before_ms);
  2911. if (typeof result.charts_ok === 'undefined') {
  2912. result.charts_ok = 'unknown';
  2913. }
  2914. if (typeof result.charts_failed === 'undefined') {
  2915. result.charts_failed = 0;
  2916. }
  2917. if (typeof result.compression === 'undefined') {
  2918. result.compression = 'none';
  2919. }
  2920. if (typeof result.data_size === 'undefined') {
  2921. result.data_size = 0;
  2922. }
  2923. document.getElementById('loadSnapshotFilename').innerHTML = '<code>' + filename + '</code>';
  2924. document.getElementById('loadSnapshotHostname').innerHTML = '<b>' + result.hostname + '</b>, netdata version: <b>' + result.netdata_version.toString() + '</b>';
  2925. document.getElementById('loadSnapshotURL').innerHTML = result.url;
  2926. document.getElementById('loadSnapshotCharts').innerHTML = result.charts.charts_count.toString() + ' charts, ' + result.charts.dimensions_count.toString() + ' dimensions, ' + result.data_points.toString() + ' points per dimension, ' + Math.round(result.duration_ms / result.data_points).toString() + ' ms per point';
  2927. document.getElementById('loadSnapshotInfo').innerHTML = 'version: <b>' + result.snapshot_version.toString() + '</b>, includes <b>' + result.charts_ok.toString() + '</b> unique chart data queries ' + ((result.charts_failed > 0) ? ('<b>' + result.charts_failed.toString() + '</b> failed') : '').toString() + ', compressed with <code>' + result.compression.toString() + '</code>, data size ' + (Math.round(result.data_size * 100 / 1024 / 1024) / 100).toString() + ' MB';
  2928. document.getElementById('loadSnapshotTimeRange').innerHTML = '<b>' + NETDATA.dateTime.localeDateString(date_after) + ' ' + NETDATA.dateTime.localeTimeString(date_after) + '</b> to <b>' + NETDATA.dateTime.localeDateString(date_before) + ' ' + NETDATA.dateTime.localeTimeString(date_before) + '</b>';
  2929. document.getElementById('loadSnapshotComments').innerHTML = ((result.comments) ? result.comments : '').toString();
  2930. loadSnapshotModalLog('success', 'File loaded, click <b>Import</b> to render it!');
  2931. $('#loadSnapshotImport').removeClass('disabled');
  2932. tmpSnapshotData = result;
  2933. }
  2934. catch (e) {
  2935. console.log(e);
  2936. document.getElementById('loadSnapshotStatus').className = "alert alert-danger";
  2937. document.getElementById('loadSnapshotStatus').innerHTML = "Failed to parse this file!";
  2938. $('#loadSnapshotImport').addClass('disabled');
  2939. }
  2940. }
  2941. //console.log(file);
  2942. fr.readAsText(file);
  2943. };
  2944. function loadSnapshotPreflightEmpty() {
  2945. document.getElementById('loadSnapshotFilename').innerHTML = '';
  2946. document.getElementById('loadSnapshotHostname').innerHTML = '';
  2947. document.getElementById('loadSnapshotURL').innerHTML = '';
  2948. document.getElementById('loadSnapshotCharts').innerHTML = '';
  2949. document.getElementById('loadSnapshotInfo').innerHTML = '';
  2950. document.getElementById('loadSnapshotTimeRange').innerHTML = '';
  2951. document.getElementById('loadSnapshotComments').innerHTML = '';
  2952. loadSnapshotModalLog('success', 'Browse for a snapshot file (or drag it and drop it here), then click <b>Import</b> to render it.');
  2953. $('#loadSnapshotImport').addClass('disabled');
  2954. };
  2955. var loadSnapshotDragAndDropInitialized = false;
  2956. function loadSnapshotDragAndDropSetup() {
  2957. if (loadSnapshotDragAndDropInitialized === false) {
  2958. loadSnapshotDragAndDropInitialized = true;
  2959. $('#loadSnapshotDragAndDrop')
  2960. .on('drag dragstart dragend dragover dragenter dragleave drop', function (e) {
  2961. e.preventDefault();
  2962. e.stopPropagation();
  2963. })
  2964. .on('drop', function (e) {
  2965. if (e.originalEvent.dataTransfer.files.length) {
  2966. loadSnapshotPreflightFile(e.originalEvent.dataTransfer.files.item(0));
  2967. } else {
  2968. loadSnapshotPreflightEmpty();
  2969. loadSnapshotModalLog('danger', 'No file selected');
  2970. }
  2971. });
  2972. }
  2973. };
  2974. function loadSnapshotPreflight() {
  2975. var files = document.getElementById('loadSnapshotSelectFiles').files;
  2976. if (files.length <= 0) {
  2977. loadSnapshotPreflightEmpty();
  2978. loadSnapshotModalLog('danger', 'No file selected');
  2979. return;
  2980. }
  2981. loadSnapshotModalLog('info', 'Loading file...');
  2982. loadSnapshotPreflightFile(files.item(0));
  2983. }
  2984. // --------------------------------------------------------------------
  2985. // saving snapshots
  2986. var saveSnapshotStop = false;
  2987. function saveSnapshotCancel() {
  2988. saveSnapshotStop = true;
  2989. }
  2990. var saveSnapshotModalInitialized = false;
  2991. function saveSnapshotModalSetup() {
  2992. if (saveSnapshotModalInitialized === false) {
  2993. saveSnapshotModalInitialized = true;
  2994. $('#saveSnapshotModal')
  2995. .on('hide.bs.modal', saveSnapshotCancel)
  2996. .on('show.bs.modal', saveSnapshotModalInit)
  2997. .on('shown.bs.modal', function () {
  2998. $('#saveSnapshotResolutionSlider').find(".slider-handle:first").attr("tabindex", 1);
  2999. document.getElementById('saveSnapshotComments').focus();
  3000. });
  3001. }
  3002. };
  3003. function saveSnapshotModalLog(priority, msg) {
  3004. document.getElementById('saveSnapshotStatus').className = "alert alert-" + priority;
  3005. document.getElementById('saveSnapshotStatus').innerHTML = msg;
  3006. }
  3007. function saveSnapshotModalShowExpectedSize() {
  3008. var points = Math.round(saveSnapshotViewDuration / saveSnapshotSelectedSecondsPerPoint);
  3009. var priority = 'info';
  3010. var msg = 'A moderate snapshot.';
  3011. var sizemb = Math.round(
  3012. (options.data.charts_count * snapshotOptions.bytes_per_chart
  3013. + options.data.dimensions_count * points * snapshotOptions.compressions[saveSnapshotCompression].bytes_per_point_disk)
  3014. * 10 / 1024 / 1024) / 10;
  3015. var memmb = Math.round(
  3016. (options.data.charts_count * snapshotOptions.bytes_per_chart
  3017. + options.data.dimensions_count * points * snapshotOptions.compressions[saveSnapshotCompression].bytes_per_point_memory)
  3018. * 10 / 1024 / 1024) / 10;
  3019. if (sizemb < 10) {
  3020. priority = 'success';
  3021. msg = 'A nice small snapshot!';
  3022. }
  3023. if (sizemb > 50) {
  3024. priority = 'warning';
  3025. msg = 'Will stress your browser...';
  3026. }
  3027. if (sizemb > 100) {
  3028. priority = 'danger';
  3029. msg = 'Hm... good luck...';
  3030. }
  3031. saveSnapshotModalLog(priority, 'The snapshot will have ' + points.toString() + ' points per dimension. Expected size on disk ' + sizemb + ' MB, at browser memory ' + memmb + ' MB.<br/>' + msg);
  3032. }
  3033. var saveSnapshotCompression = snapshotOptions.compressionDefault;
  3034. function saveSnapshotSetCompression(name) {
  3035. saveSnapshotCompression = name;
  3036. document.getElementById('saveSnapshotCompressionName').innerHTML = saveSnapshotCompression;
  3037. saveSnapshotModalShowExpectedSize();
  3038. }
  3039. var saveSnapshotSlider = null;
  3040. var saveSnapshotSelectedSecondsPerPoint = 1;
  3041. var saveSnapshotViewDuration = 1;
  3042. function saveSnapshotModalInit() {
  3043. $('#saveSnapshotModalProgressSection').hide();
  3044. $('#saveSnapshotResolutionRadio').show();
  3045. saveSnapshotModalLog('info', 'Select resolution and click <b>Save</b>');
  3046. $('#saveSnapshotExport').removeClass('disabled');
  3047. loadBootstrapSlider(function () {
  3048. saveSnapshotViewDuration = options.duration;
  3049. var start_ms = Math.round(Date.now() - saveSnapshotViewDuration * 1000);
  3050. if (NETDATA.globalPanAndZoom.isActive() === true) {
  3051. saveSnapshotViewDuration = Math.round((NETDATA.globalPanAndZoom.force_before_ms - NETDATA.globalPanAndZoom.force_after_ms) / 1000);
  3052. start_ms = NETDATA.globalPanAndZoom.force_after_ms;
  3053. }
  3054. var start_date = new Date(start_ms);
  3055. var yyyymmddhhssmm = start_date.getFullYear() + NETDATA.zeropad(start_date.getMonth() + 1) + NETDATA.zeropad(start_date.getDate()) + '-' + NETDATA.zeropad(start_date.getHours()) + NETDATA.zeropad(start_date.getMinutes()) + NETDATA.zeropad(start_date.getSeconds());
  3056. document.getElementById('saveSnapshotFilename').value = 'netdata-' + options.hostname.toString() + '-' + yyyymmddhhssmm.toString() + '-' + saveSnapshotViewDuration.toString() + '.snapshot';
  3057. saveSnapshotSetCompression(saveSnapshotCompression);
  3058. var min = options.update_every;
  3059. var max = Math.round(saveSnapshotViewDuration / 100);
  3060. if (NETDATA.globalPanAndZoom.isActive() === false) {
  3061. max = Math.round(saveSnapshotViewDuration / 50);
  3062. }
  3063. var view = Math.round(saveSnapshotViewDuration / Math.round($(document.getElementById('charts_div')).width() / 2));
  3064. // console.log('view duration: ' + saveSnapshotViewDuration + ', min: ' + min + ', max: ' + max + ', view: ' + view);
  3065. if (max < 10) {
  3066. max = 10;
  3067. }
  3068. if (max < min) {
  3069. max = min;
  3070. }
  3071. if (view < min) {
  3072. view = min;
  3073. }
  3074. if (view > max) {
  3075. view = max;
  3076. }
  3077. if (saveSnapshotSlider !== null) {
  3078. saveSnapshotSlider.destroy();
  3079. }
  3080. saveSnapshotSlider = new Slider('#saveSnapshotResolutionSlider', {
  3081. ticks: [min, view, max],
  3082. min: min,
  3083. max: max,
  3084. step: options.update_every,
  3085. value: view,
  3086. scale: (max > 100) ? 'logarithmic' : 'linear',
  3087. tooltip: 'always',
  3088. formatter: function (value) {
  3089. if (value < 1) {
  3090. value = 1;
  3091. }
  3092. if (value < options.data.update_every) {
  3093. value = options.data.update_every;
  3094. }
  3095. saveSnapshotSelectedSecondsPerPoint = value;
  3096. saveSnapshotModalShowExpectedSize();
  3097. var seconds = ' seconds ';
  3098. if (value === 1) {
  3099. seconds = ' second ';
  3100. }
  3101. return value + seconds + 'per point' + ((value === options.data.update_every) ? ', server default' : '').toString();
  3102. }
  3103. });
  3104. });
  3105. }
  3106. function saveSnapshot() {
  3107. loadPako(function () {
  3108. loadLzString(function () {
  3109. saveSnapshotStop = false;
  3110. $('#saveSnapshotModalProgressSection').show();
  3111. $('#saveSnapshotResolutionRadio').hide();
  3112. $('#saveSnapshotExport').addClass('disabled');
  3113. var filename = document.getElementById('saveSnapshotFilename').value;
  3114. // console.log(filename);
  3115. saveSnapshotModalLog('info', 'Generating snapshot as <code>' + filename.toString() + '</code>');
  3116. var save_options = {
  3117. stop_updates_when_focus_is_lost: false,
  3118. update_only_visible: false,
  3119. sync_selection: false,
  3120. eliminate_zero_dimensions: true,
  3121. pan_and_zoom_data_padding: false,
  3122. show_help: false,
  3123. legend_toolbox: false,
  3124. resize_charts: false,
  3125. pixels_per_point: 1
  3126. };
  3127. var backedup_options = {};
  3128. var x;
  3129. for (x in save_options) {
  3130. if (save_options.hasOwnProperty(x)) {
  3131. backedup_options[x] = NETDATA.options.current[x];
  3132. NETDATA.options.current[x] = save_options[x];
  3133. }
  3134. }
  3135. var el = document.getElementById('saveSnapshotModalProgressBar');
  3136. var eltxt = document.getElementById('saveSnapshotModalProgressBarText');
  3137. options.data.charts_by_name = null;
  3138. var saveData = {
  3139. hostname: options.hostname,
  3140. server: NETDATA.serverDefault,
  3141. netdata_version: options.data.version,
  3142. snapshot_version: 1,
  3143. after_ms: Date.now() - options.duration * 1000,
  3144. before_ms: Date.now(),
  3145. highlight_after_ms: urlOptions.highlight_after,
  3146. highlight_before_ms: urlOptions.highlight_before,
  3147. duration_ms: options.duration * 1000,
  3148. update_every_ms: options.update_every * 1000,
  3149. data_points: 0,
  3150. url: ((urlOptions.server !== null) ? urlOptions.server : document.location.origin.toString() + document.location.pathname.toString() + document.location.search.toString()).toString(),
  3151. comments: document.getElementById('saveSnapshotComments').value.toString(),
  3152. hash: urlOptions.hash,
  3153. charts: options.data,
  3154. info: jsonStringifyFn({
  3155. menu: netdataDashboard.menu,
  3156. submenu: netdataDashboard.submenu,
  3157. context: netdataDashboard.context
  3158. }),
  3159. charts_ok: 0,
  3160. charts_failed: 0,
  3161. compression: saveSnapshotCompression,
  3162. data_size: 0,
  3163. data: {}
  3164. };
  3165. if (typeof snapshotOptions.compressions[saveData.compression] === 'undefined') {
  3166. alert('unknown compression method: ' + saveData.compression);
  3167. saveData.compression = 'none';
  3168. }
  3169. var compress = snapshotOptions.compressions[saveData.compression].compress;
  3170. var compressed_length = snapshotOptions.compressions[saveData.compression].compressed_length;
  3171. function pack_api1_v1_chart_data(state) {
  3172. if (state.library_name === null || state.data === null) {
  3173. return;
  3174. }
  3175. var data = state.data;
  3176. state.data = null;
  3177. data.state = null;
  3178. var str = JSON.stringify(data);
  3179. if (typeof str === 'string') {
  3180. var cstr = compress(str);
  3181. saveData.data[state.chartDataUniqueID()] = cstr;
  3182. return compressed_length(cstr);
  3183. } else {
  3184. return 0;
  3185. }
  3186. }
  3187. var clearPanAndZoom = false;
  3188. if (NETDATA.globalPanAndZoom.isActive() === false) {
  3189. NETDATA.globalPanAndZoom.setMaster(NETDATA.options.targets[0], saveData.after_ms, saveData.before_ms);
  3190. clearPanAndZoom = true;
  3191. }
  3192. saveData.after_ms = NETDATA.globalPanAndZoom.force_after_ms;
  3193. saveData.before_ms = NETDATA.globalPanAndZoom.force_before_ms;
  3194. saveData.duration_ms = saveData.before_ms - saveData.after_ms;
  3195. saveData.data_points = Math.round((saveData.before_ms - saveData.after_ms) / (saveSnapshotSelectedSecondsPerPoint * 1000));
  3196. saveSnapshotModalLog('info', 'Generating snapshot with ' + saveData.data_points.toString() + ' data points per dimension...');
  3197. var charts_count = 0;
  3198. var charts_ok = 0;
  3199. var charts_failed = 0;
  3200. function saveSnapshotRestore() {
  3201. $('#saveSnapshotModal').modal('hide');
  3202. // restore the options
  3203. var x;
  3204. for (x in backedup_options) {
  3205. if (backedup_options.hasOwnProperty(x)) {
  3206. NETDATA.options.current[x] = backedup_options[x];
  3207. }
  3208. }
  3209. $(el).css('width', '0%').attr('aria-valuenow', 0);
  3210. eltxt.innerText = '0%';
  3211. if (clearPanAndZoom) {
  3212. NETDATA.globalPanAndZoom.clearMaster();
  3213. }
  3214. NETDATA.options.force_data_points = 0;
  3215. NETDATA.options.fake_chart_rendering = false;
  3216. NETDATA.onscroll_updater_enabled = true;
  3217. NETDATA.onresize();
  3218. NETDATA.unpause();
  3219. $('#saveSnapshotExport').removeClass('disabled');
  3220. }
  3221. NETDATA.globalSelectionSync.stop();
  3222. NETDATA.options.force_data_points = saveData.data_points;
  3223. NETDATA.options.fake_chart_rendering = true;
  3224. NETDATA.onscroll_updater_enabled = false;
  3225. NETDATA.abortAllRefreshes();
  3226. var size = 0;
  3227. var info = ' Resolution: <b>' + saveSnapshotSelectedSecondsPerPoint.toString() + ((saveSnapshotSelectedSecondsPerPoint === 1) ? ' second ' : ' seconds ').toString() + 'per point</b>.';
  3228. function update_chart(idx) {
  3229. if (saveSnapshotStop === true) {
  3230. saveSnapshotModalLog('info', 'Cancelled!');
  3231. saveSnapshotRestore();
  3232. return;
  3233. }
  3234. var state = NETDATA.options.targets[--idx];
  3235. var pcent = (NETDATA.options.targets.length - idx) * 100 / NETDATA.options.targets.length;
  3236. $(el).css('width', pcent + '%').attr('aria-valuenow', pcent);
  3237. eltxt.innerText = Math.round(pcent).toString() + '%, ' + state.id;
  3238. setTimeout(function () {
  3239. charts_count++;
  3240. state.isVisible(true);
  3241. state.current.force_after_ms = saveData.after_ms;
  3242. state.current.force_before_ms = saveData.before_ms;
  3243. state.updateChart(function (status, reason) {
  3244. state.current.force_after_ms = null;
  3245. state.current.force_before_ms = null;
  3246. if (status === true) {
  3247. charts_ok++;
  3248. // state.log('ok');
  3249. size += pack_api1_v1_chart_data(state);
  3250. } else {
  3251. charts_failed++;
  3252. state.log('failed to be updated: ' + reason);
  3253. }
  3254. saveSnapshotModalLog((charts_failed) ? 'danger' : 'info', 'Generated snapshot data size <b>' + (Math.round(size * 100 / 1024 / 1024) / 100).toString() + ' MB</b>. ' + ((charts_failed) ? (charts_failed.toString() + ' charts have failed to be downloaded') : '').toString() + info);
  3255. if (idx > 0) {
  3256. update_chart(idx);
  3257. } else {
  3258. saveData.charts_ok = charts_ok;
  3259. saveData.charts_failed = charts_failed;
  3260. saveData.data_size = size;
  3261. // console.log(saveData.compression + ': ' + (size / (options.data.dimensions_count * Math.round(saveSnapshotViewDuration / saveSnapshotSelectedSecondsPerPoint))).toString());
  3262. // save it
  3263. // console.log(saveData);
  3264. saveObjectToClient(saveData, filename);
  3265. if (charts_failed > 0) {
  3266. alert(charts_failed.toString() + ' failed to be downloaded');
  3267. }
  3268. saveSnapshotRestore();
  3269. saveData = null;
  3270. }
  3271. })
  3272. }, 0);
  3273. }
  3274. update_chart(NETDATA.options.targets.length);
  3275. });
  3276. });
  3277. }
  3278. // --------------------------------------------------------------------
  3279. // activate netdata on the page
  3280. function dashboardSettingsSetup() {
  3281. var update_options_modal = function () {
  3282. // console.log('update_options_modal');
  3283. var sync_option = function (option) {
  3284. var self = $('#' + option);
  3285. if (self.prop('checked') !== NETDATA.getOption(option)) {
  3286. // console.log('switching ' + option.toString());
  3287. self.bootstrapToggle(NETDATA.getOption(option) ? 'on' : 'off');
  3288. }
  3289. };
  3290. var theme_sync_option = function (option) {
  3291. var self = $('#' + option);
  3292. self.bootstrapToggle(netdataTheme === 'slate' ? 'on' : 'off');
  3293. };
  3294. var units_sync_option = function (option) {
  3295. var self = $('#' + option);
  3296. if (self.prop('checked') !== (NETDATA.getOption('units') === 'auto')) {
  3297. self.bootstrapToggle(NETDATA.getOption('units') === 'auto' ? 'on' : 'off');
  3298. }
  3299. if (self.prop('checked') === true) {
  3300. $('#settingsLocaleTempRow').show();
  3301. $('#settingsLocaleTimeRow').show();
  3302. } else {
  3303. $('#settingsLocaleTempRow').hide();
  3304. $('#settingsLocaleTimeRow').hide();
  3305. }
  3306. };
  3307. var temp_sync_option = function (option) {
  3308. var self = $('#' + option);
  3309. if (self.prop('checked') !== (NETDATA.getOption('temperature') === 'celsius')) {
  3310. self.bootstrapToggle(NETDATA.getOption('temperature') === 'celsius' ? 'on' : 'off');
  3311. }
  3312. };
  3313. var timezone_sync_option = function (option) {
  3314. var self = $('#' + option);
  3315. document.getElementById('browser_timezone').innerText = NETDATA.options.browser_timezone;
  3316. document.getElementById('server_timezone').innerText = NETDATA.options.server_timezone;
  3317. document.getElementById('current_timezone').innerText = (NETDATA.options.current.timezone === 'default') ? 'unset, using browser default' : NETDATA.options.current.timezone;
  3318. if (self.prop('checked') === NETDATA.dateTime.using_timezone) {
  3319. self.bootstrapToggle(NETDATA.dateTime.using_timezone ? 'off' : 'on');
  3320. }
  3321. };
  3322. sync_option('eliminate_zero_dimensions');
  3323. sync_option('destroy_on_hide');
  3324. sync_option('async_on_scroll');
  3325. sync_option('parallel_refresher');
  3326. sync_option('concurrent_refreshes');
  3327. sync_option('sync_selection');
  3328. sync_option('sync_pan_and_zoom');
  3329. sync_option('stop_updates_when_focus_is_lost');
  3330. sync_option('smooth_plot');
  3331. sync_option('pan_and_zoom_data_padding');
  3332. sync_option('show_help');
  3333. sync_option('seconds_as_time');
  3334. theme_sync_option('netdata_theme_control');
  3335. units_sync_option('units_conversion');
  3336. temp_sync_option('units_temp');
  3337. timezone_sync_option('local_timezone');
  3338. if (NETDATA.getOption('parallel_refresher') === false) {
  3339. $('#concurrent_refreshes_row').hide();
  3340. } else {
  3341. $('#concurrent_refreshes_row').show();
  3342. }
  3343. };
  3344. NETDATA.setOption('setOptionCallback', update_options_modal);
  3345. // handle options changes
  3346. $('#eliminate_zero_dimensions').change(function () {
  3347. NETDATA.setOption('eliminate_zero_dimensions', $(this).prop('checked'));
  3348. });
  3349. $('#destroy_on_hide').change(function () {
  3350. NETDATA.setOption('destroy_on_hide', $(this).prop('checked'));
  3351. });
  3352. $('#async_on_scroll').change(function () {
  3353. NETDATA.setOption('async_on_scroll', $(this).prop('checked'));
  3354. });
  3355. $('#parallel_refresher').change(function () {
  3356. NETDATA.setOption('parallel_refresher', $(this).prop('checked'));
  3357. });
  3358. $('#concurrent_refreshes').change(function () {
  3359. NETDATA.setOption('concurrent_refreshes', $(this).prop('checked'));
  3360. });
  3361. $('#sync_selection').change(function () {
  3362. NETDATA.setOption('sync_selection', $(this).prop('checked'));
  3363. });
  3364. $('#sync_pan_and_zoom').change(function () {
  3365. NETDATA.setOption('sync_pan_and_zoom', $(this).prop('checked'));
  3366. });
  3367. $('#stop_updates_when_focus_is_lost').change(function () {
  3368. urlOptions.update_always = !$(this).prop('checked');
  3369. urlOptions.hashUpdate();
  3370. NETDATA.setOption('stop_updates_when_focus_is_lost', !urlOptions.update_always);
  3371. });
  3372. $('#smooth_plot').change(function () {
  3373. NETDATA.setOption('smooth_plot', $(this).prop('checked'));
  3374. });
  3375. $('#pan_and_zoom_data_padding').change(function () {
  3376. NETDATA.setOption('pan_and_zoom_data_padding', $(this).prop('checked'));
  3377. });
  3378. $('#seconds_as_time').change(function () {
  3379. NETDATA.setOption('seconds_as_time', $(this).prop('checked'));
  3380. });
  3381. $('#local_timezone').change(function () {
  3382. if ($(this).prop('checked')) {
  3383. selected_server_timezone('default', true);
  3384. } else {
  3385. selected_server_timezone('default', false);
  3386. }
  3387. });
  3388. $('#units_conversion').change(function () {
  3389. NETDATA.setOption('units', $(this).prop('checked') ? 'auto' : 'original');
  3390. });
  3391. $('#units_temp').change(function () {
  3392. NETDATA.setOption('temperature', $(this).prop('checked') ? 'celsius' : 'fahrenheit');
  3393. });
  3394. $('#show_help').change(function () {
  3395. urlOptions.help = $(this).prop('checked');
  3396. urlOptions.hashUpdate();
  3397. NETDATA.setOption('show_help', urlOptions.help);
  3398. netdataReload();
  3399. });
  3400. // this has to be the last
  3401. // it reloads the page
  3402. $('#netdata_theme_control').change(function () {
  3403. urlOptions.theme = $(this).prop('checked') ? 'slate' : 'white';
  3404. urlOptions.hashUpdate();
  3405. if (setTheme(urlOptions.theme)) {
  3406. netdataReload();
  3407. }
  3408. });
  3409. }
  3410. function scrollDashboardTo() {
  3411. if (netdataSnapshotData !== null && typeof netdataSnapshotData.hash !== 'undefined') {
  3412. //console.log(netdataSnapshotData.hash);
  3413. scrollToId(netdataSnapshotData.hash.replace('#', ''));
  3414. } else {
  3415. // check if we have to jump to a specific section
  3416. scrollToId(urlOptions.hash.replace('#', ''));
  3417. if (urlOptions.chart !== null) {
  3418. NETDATA.alarms.scrollToChart(urlOptions.chart);
  3419. //urlOptions.hash = '#' + NETDATA.name2id('menu_' + charts[c].menu + '_submenu_' + charts[c].submenu);
  3420. //urlOptions.hash = '#chart_' + NETDATA.name2id(urlOptions.chart);
  3421. //console.log('hash = ' + urlOptions.hash);
  3422. }
  3423. }
  3424. }
  3425. var modalHiddenCallback = null;
  3426. function scrollToChartAfterHidingModal(chart, alarmDate, alarmStatus) {
  3427. modalHiddenCallback = function () {
  3428. NETDATA.alarms.scrollToChart(chart, alarmDate);
  3429. if (['WARNING', 'CRITICAL'].includes(alarmStatus)) {
  3430. const currentChartState = NETDATA.options.targets.find(
  3431. (chartState) => chartState.id === chart,
  3432. )
  3433. const twoMinutes = 2 * 60 * 1000
  3434. NETDATA.globalPanAndZoom.setMaster(
  3435. currentChartState,
  3436. alarmDate - twoMinutes,
  3437. alarmDate + twoMinutes,
  3438. )
  3439. }
  3440. };
  3441. }
  3442. // ----------------------------------------------------------------------------
  3443. function enableTooltipsAndPopovers() {
  3444. $('[data-toggle="tooltip"]').tooltip({
  3445. animated: 'fade',
  3446. trigger: 'hover',
  3447. html: true,
  3448. delay: { show: 500, hide: 0 },
  3449. container: 'body'
  3450. });
  3451. $('[data-toggle="popover"]').popover();
  3452. }
  3453. // ----------------------------------------------------------------------------
  3454. var runOnceOnDashboardLastRun = 0;
  3455. function runOnceOnDashboardWithjQuery() {
  3456. if (runOnceOnDashboardLastRun !== 0) {
  3457. scrollDashboardTo();
  3458. // restore the scrollspy at the proper position
  3459. $(document.body).scrollspy('refresh');
  3460. $(document.body).scrollspy('process');
  3461. return;
  3462. }
  3463. runOnceOnDashboardLastRun = Date.now();
  3464. // ------------------------------------------------------------------------
  3465. // bootstrap modals
  3466. // prevent bootstrap modals from scrolling the page
  3467. // maintains the current scroll position
  3468. // https://stackoverflow.com/a/34754029/4525767
  3469. var scrollPos = 0;
  3470. var modal_depth = 0; // how many modals are currently open
  3471. var modal_shown = false; // set to true, if a modal is shown
  3472. var netdata_paused_on_modal = false; // set to true, if the modal paused netdata
  3473. var scrollspyOffset = $(window).height() / 3; // will be updated below - the offset of scrollspy to select an item
  3474. $('.modal')
  3475. .on('show.bs.modal', function () {
  3476. if (modal_depth === 0) {
  3477. scrollPos = window.scrollY;
  3478. $('body').css({
  3479. overflow: 'hidden',
  3480. position: 'fixed',
  3481. top: -scrollPos
  3482. });
  3483. modal_shown = true;
  3484. if (NETDATA.options.pauseCallback === null) {
  3485. NETDATA.pause(function () {
  3486. });
  3487. netdata_paused_on_modal = true;
  3488. } else {
  3489. netdata_paused_on_modal = false;
  3490. }
  3491. }
  3492. modal_depth++;
  3493. //console.log(urlOptions.after);
  3494. })
  3495. .on('hide.bs.modal', function () {
  3496. modal_depth--;
  3497. if (modal_depth <= 0) {
  3498. modal_depth = 0;
  3499. $('body')
  3500. .css({
  3501. overflow: '',
  3502. position: '',
  3503. top: ''
  3504. });
  3505. // scroll to the position we had open before the modal
  3506. $('html, body')
  3507. .animate({ scrollTop: scrollPos }, 0);
  3508. // unpause netdata, if we paused it
  3509. if (netdata_paused_on_modal === true) {
  3510. NETDATA.unpause();
  3511. netdata_paused_on_modal = false;
  3512. }
  3513. // restore the scrollspy at the proper position
  3514. $(document.body).scrollspy('process');
  3515. }
  3516. //console.log(urlOptions.after);
  3517. })
  3518. .on('hidden.bs.modal', function () {
  3519. if (modal_depth === 0) {
  3520. modal_shown = false;
  3521. }
  3522. if (typeof modalHiddenCallback === 'function') {
  3523. modalHiddenCallback();
  3524. }
  3525. modalHiddenCallback = null;
  3526. //console.log(urlOptions.after);
  3527. });
  3528. // ------------------------------------------------------------------------
  3529. // sidebar / affix
  3530. if (shouldShowSignInBanner()) {
  3531. const el = document.getElementById("sign-in-banner");
  3532. if (el) {
  3533. el.style.display = "initial";
  3534. el.classList.add(`theme-${netdataTheme}`);
  3535. }
  3536. }
  3537. $('#sidebar')
  3538. .affix({
  3539. offset: {
  3540. top: (isdemo()) ? 150 : 0,
  3541. bottom: 0
  3542. }
  3543. })
  3544. .on('affixed.bs.affix', function () {
  3545. // fix scrolling of very long affix lists
  3546. // http://stackoverflow.com/questions/21691585/bootstrap-3-1-0-affix-too-long
  3547. $(this).removeAttr('style');
  3548. })
  3549. .on('affix-top.bs.affix', function () {
  3550. // fix bootstrap affix click bug
  3551. // https://stackoverflow.com/a/37847981/4525767
  3552. if (modal_shown) {
  3553. return false;
  3554. }
  3555. })
  3556. .on('activate.bs.scrollspy', function (e) {
  3557. // change the URL based on the current position of the screen
  3558. if (modal_shown === false) {
  3559. var el = $(e.target);
  3560. var hash = el.find('a').attr('href');
  3561. if (typeof hash === 'string' && hash.substring(0, 1) === '#' && urlOptions.hash.startsWith(hash + '_submenu_') === false) {
  3562. urlOptions.hash = hash;
  3563. urlOptions.hashUpdate();
  3564. }
  3565. }
  3566. });
  3567. Ps.initialize(document.getElementById('sidebar'), {
  3568. wheelSpeed: 0.5,
  3569. wheelPropagation: true,
  3570. swipePropagation: true,
  3571. minScrollbarLength: null,
  3572. maxScrollbarLength: null,
  3573. useBothWheelAxes: false,
  3574. suppressScrollX: true,
  3575. suppressScrollY: false,
  3576. scrollXMarginOffset: 0,
  3577. scrollYMarginOffset: 0,
  3578. theme: 'default'
  3579. });
  3580. // ------------------------------------------------------------------------
  3581. // scrollspy
  3582. if (scrollspyOffset > 250) {
  3583. scrollspyOffset = 250;
  3584. }
  3585. if (scrollspyOffset < 75) {
  3586. scrollspyOffset = 75;
  3587. }
  3588. document.body.setAttribute('data-offset', scrollspyOffset);
  3589. // scroll the dashboard, before activating the scrollspy, so that our
  3590. // hash will not be updated before we got the chance to scroll to it
  3591. scrollDashboardTo();
  3592. $(document.body).scrollspy({
  3593. target: '#sidebar',
  3594. offset: scrollspyOffset // controls the diff of the <hX> element to the top, to select it
  3595. });
  3596. // ------------------------------------------------------------------------
  3597. // my-netdata menu
  3598. Ps.initialize(document.getElementById('my-netdata-dropdown-content'), {
  3599. wheelSpeed: 1,
  3600. wheelPropagation: false,
  3601. swipePropagation: false,
  3602. minScrollbarLength: null,
  3603. maxScrollbarLength: null,
  3604. useBothWheelAxes: false,
  3605. suppressScrollX: true,
  3606. suppressScrollY: false,
  3607. scrollXMarginOffset: 0,
  3608. scrollYMarginOffset: 0,
  3609. theme: 'default'
  3610. });
  3611. $('#myNetdataDropdownParent')
  3612. .on('show.bs.dropdown', function () {
  3613. var hash = urlOptions.genHash();
  3614. $('.registry_link').each(function (idx) {
  3615. this.setAttribute('href', this.getAttribute("href").replace(/#.*$/, hash));
  3616. });
  3617. NETDATA.pause(function () {
  3618. });
  3619. })
  3620. .on('shown.bs.dropdown', function () {
  3621. Ps.update(document.getElementById('my-netdata-dropdown-content'));
  3622. myNetdataMenuDidShow();
  3623. })
  3624. .on('hidden.bs.dropdown', function () {
  3625. NETDATA.unpause();
  3626. });
  3627. $('#deleteRegistryModal')
  3628. .on('hidden.bs.modal', function () {
  3629. deleteRegistryGuid = null;
  3630. });
  3631. // ------------------------------------------------------------------------
  3632. // update modal
  3633. $('#updateModal')
  3634. .on('show.bs.modal', function () {
  3635. versionLog('checking, please wait...');
  3636. })
  3637. .on('shown.bs.modal', function () {
  3638. notifyForUpdate(true);
  3639. });
  3640. // ------------------------------------------------------------------------
  3641. // alarms modal
  3642. $('#alarmsModal')
  3643. .on('shown.bs.modal', function () {
  3644. alarmsUpdateModal();
  3645. })
  3646. .on('hidden.bs.modal', function () {
  3647. document.getElementById('alarms_active').innerHTML =
  3648. document.getElementById('alarms_all').innerHTML =
  3649. document.getElementById('alarms_log').innerHTML =
  3650. 'loading...';
  3651. });
  3652. // ------------------------------------------------------------------------
  3653. dashboardSettingsSetup();
  3654. loadSnapshotDragAndDropSetup();
  3655. saveSnapshotModalSetup();
  3656. showPageFooter();
  3657. // ------------------------------------------------------------------------
  3658. // https://github.com/viralpatel/jquery.shorten/blob/master/src/jquery.shorten.js
  3659. $.fn.shorten = function (settings) {
  3660. "use strict";
  3661. var config = {
  3662. showChars: 750,
  3663. minHideChars: 10,
  3664. ellipsesText: "...",
  3665. moreText: '<i class="fas fa-expand"></i> show more information',
  3666. lessText: '<i class="fas fa-compress"></i> show less information',
  3667. onLess: function () {
  3668. NETDATA.onscroll();
  3669. },
  3670. onMore: function () {
  3671. NETDATA.onscroll();
  3672. },
  3673. errMsg: null,
  3674. force: false
  3675. };
  3676. if (settings) {
  3677. $.extend(config, settings);
  3678. }
  3679. if ($(this).data('jquery.shorten') && !config.force) {
  3680. return false;
  3681. }
  3682. $(this).data('jquery.shorten', true);
  3683. $(document).off("click", '.morelink');
  3684. $(document).on({
  3685. click: function () {
  3686. var $this = $(this);
  3687. if ($this.hasClass('less')) {
  3688. $this.removeClass('less');
  3689. $this.html(config.moreText);
  3690. $this.parent().prev().animate({ 'height': '0' + '%' }, 0, function () {
  3691. $this.parent().prev().prev().show();
  3692. }).hide(0, function () {
  3693. config.onLess();
  3694. });
  3695. } else {
  3696. $this.addClass('less');
  3697. $this.html(config.lessText);
  3698. $this.parent().prev().animate({ 'height': '100' + '%' }, 0, function () {
  3699. $this.parent().prev().prev().hide();
  3700. }).show(0, function () {
  3701. config.onMore();
  3702. });
  3703. }
  3704. return false;
  3705. }
  3706. }, '.morelink');
  3707. return this.each(function () {
  3708. var $this = $(this);
  3709. var content = $this.html();
  3710. var contentlen = $this.text().length;
  3711. if (contentlen > config.showChars + config.minHideChars) {
  3712. var c = content.substr(0, config.showChars);
  3713. if (c.indexOf('<') >= 0) // If there's HTML don't want to cut it
  3714. {
  3715. var inTag = false; // I'm in a tag?
  3716. var bag = ''; // Put the characters to be shown here
  3717. var countChars = 0; // Current bag size
  3718. var openTags = []; // Stack for opened tags, so I can close them later
  3719. var tagName = null;
  3720. for (var i = 0, r = 0; r <= config.showChars; i++) {
  3721. if (content[i] === '<' && !inTag) {
  3722. inTag = true;
  3723. // This could be "tag" or "/tag"
  3724. tagName = content.substring(i + 1, content.indexOf('>', i));
  3725. // If its a closing tag
  3726. if (tagName[0] === '/') {
  3727. if (tagName !== ('/' + openTags[0])) {
  3728. config.errMsg = 'ERROR en HTML: the top of the stack should be the tag that closes';
  3729. } else {
  3730. openTags.shift(); // Pops the last tag from the open tag stack (the tag is closed in the retult HTML!)
  3731. }
  3732. } else {
  3733. // There are some nasty tags that don't have a close tag like <br/>
  3734. if (tagName.toLowerCase() !== 'br') {
  3735. openTags.unshift(tagName); // Add to start the name of the tag that opens
  3736. }
  3737. }
  3738. }
  3739. if (inTag && content[i] === '>') {
  3740. inTag = false;
  3741. }
  3742. if (inTag) {
  3743. bag += content.charAt(i);
  3744. } else {
  3745. // Add tag name chars to the result
  3746. r++;
  3747. if (countChars <= config.showChars) {
  3748. bag += content.charAt(i); // Fix to ie 7 not allowing you to reference string characters using the []
  3749. countChars++;
  3750. } else {
  3751. // Now I have the characters needed
  3752. if (openTags.length > 0) {
  3753. // I have unclosed tags
  3754. //console.log('They were open tags');
  3755. //console.log(openTags);
  3756. for (var j = 0; j < openTags.length; j++) {
  3757. //console.log('Cierro tag ' + openTags[j]);
  3758. bag += '</' + openTags[j] + '>'; // Close all tags that were opened
  3759. // You could shift the tag from the stack to check if you end with an empty stack, that means you have closed all open tags
  3760. }
  3761. break;
  3762. }
  3763. }
  3764. }
  3765. }
  3766. c = $('<div/>').html(bag + '<span class="ellip">' + config.ellipsesText + '</span>').html();
  3767. } else {
  3768. c += config.ellipsesText;
  3769. }
  3770. var html = '<div class="shortcontent">' + c +
  3771. '</div><div class="allcontent">' + content +
  3772. '</div><span><a href="javascript://nop/" class="morelink">' + config.moreText + '</a></span>';
  3773. $this.html(html);
  3774. $this.find(".allcontent").hide(); // Hide all text
  3775. $('.shortcontent p:last', $this).css('margin-bottom', 0); //Remove bottom margin on last paragraph as it's likely shortened
  3776. }
  3777. });
  3778. };
  3779. }
  3780. function finalizePage() {
  3781. // resize all charts - without starting the background thread
  3782. // this has to be done while NETDATA is paused
  3783. // if we omit this, the affix menu will be wrong, since all
  3784. // the Dom elements are initially zero-sized
  3785. NETDATA.parseDom();
  3786. // ------------------------------------------------------------------------
  3787. NETDATA.globalPanAndZoom.callback = null;
  3788. NETDATA.globalChartUnderlay.callback = null;
  3789. if (urlOptions.pan_and_zoom === true && NETDATA.options.targets.length > 0) {
  3790. NETDATA.globalPanAndZoom.setMaster(NETDATA.options.targets[0], urlOptions.after, urlOptions.before);
  3791. }
  3792. // callback for us to track PanAndZoom operations
  3793. NETDATA.globalPanAndZoom.callback = urlOptions.netdataPanAndZoomCallback;
  3794. NETDATA.globalChartUnderlay.callback = urlOptions.netdataHighlightCallback;
  3795. // ------------------------------------------------------------------------
  3796. // let it run (update the charts)
  3797. NETDATA.unpause();
  3798. runOnceOnDashboardWithjQuery();
  3799. $(".shorten").shorten();
  3800. enableTooltipsAndPopovers();
  3801. if (isdemo()) {
  3802. // do not to give errors on netdata demo servers for 60 seconds
  3803. NETDATA.options.current.retries_on_data_failures = 60;
  3804. // google analytics when this is used for the home page of the demo sites
  3805. // this does not run on user's installations
  3806. setTimeout(function () {
  3807. (function (i, s, o, g, r, a, m) {
  3808. i['GoogleAnalyticsObject'] = r;
  3809. i[r] = i[r] || function () {
  3810. (i[r].q = i[r].q || []).push(arguments)
  3811. }, i[r].l = 1 * new Date();
  3812. a = s.createElement(o),
  3813. m = s.getElementsByTagName(o)[0];
  3814. a.async = 1;
  3815. a.src = g;
  3816. m.parentNode.insertBefore(a, m)
  3817. })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
  3818. ga('create', 'UA-64295674-3', 'auto');
  3819. ga('send', 'pageview', '/demosite/' + window.location.host);
  3820. }, 2000);
  3821. } else {
  3822. notifyForUpdate();
  3823. }
  3824. if (urlOptions.show_alarms === true) {
  3825. setTimeout(function () {
  3826. $('#alarmsModal').modal('show');
  3827. }, 1000);
  3828. }
  3829. NETDATA.onresizeCallback = function () {
  3830. Ps.update(document.getElementById('sidebar'));
  3831. Ps.update(document.getElementById('my-netdata-dropdown-content'));
  3832. };
  3833. NETDATA.onresizeCallback();
  3834. if (netdataSnapshotData !== null) {
  3835. NETDATA.globalPanAndZoom.setMaster(NETDATA.options.targets[0], netdataSnapshotData.after_ms, netdataSnapshotData.before_ms);
  3836. }
  3837. //if (urlOptions.nowelcome !== true) {
  3838. // setTimeout(function () {
  3839. // $('#welcomeModal').modal();
  3840. // }, 2000);
  3841. //}
  3842. // var netdataEnded = performance.now();
  3843. // console.log('start up time: ' + (netdataEnded - netdataStarted).toString() + ' ms');
  3844. }
  3845. function resetDashboardOptions() {
  3846. var help = NETDATA.options.current.show_help;
  3847. NETDATA.resetOptions();
  3848. if (setTheme('slate')) {
  3849. netdataReload();
  3850. }
  3851. if (help !== NETDATA.options.current.show_help) {
  3852. netdataReload();
  3853. }
  3854. }
  3855. // callback to add the dashboard info to the
  3856. // parallel javascript downloader in netdata
  3857. var netdataPrepCallback = function () {
  3858. NETDATA.requiredCSS.push({
  3859. url: NETDATA.serverStatic + 'css/bootstrap-toggle-2.2.2.min.css',
  3860. isAlreadyLoaded: function () {
  3861. return false;
  3862. }
  3863. });
  3864. NETDATA.requiredJs.push({
  3865. url: NETDATA.serverStatic + 'lib/bootstrap-toggle-2.2.2.min.js',
  3866. isAlreadyLoaded: function () {
  3867. return false;
  3868. }
  3869. });
  3870. NETDATA.requiredJs.push({
  3871. url: NETDATA.serverStatic + 'dashboard_info.js?v20181019-1',
  3872. async: false,
  3873. isAlreadyLoaded: function () {
  3874. return false;
  3875. }
  3876. });
  3877. if (isdemo()) {
  3878. document.getElementById('masthead').style.display = 'block';
  3879. } else {
  3880. if (urlOptions.update_always === true) {
  3881. NETDATA.setOption('stop_updates_when_focus_is_lost', !urlOptions.update_always);
  3882. }
  3883. }
  3884. };
  3885. var selected_server_timezone = function (timezone, status) {
  3886. //console.log('called with timezone: ' + timezone + ", status: " + ((typeof status === 'undefined')?'undefined':status).toString());
  3887. // clear the error
  3888. document.getElementById('timezone_error_message').innerHTML = '';
  3889. if (typeof status === 'undefined') {
  3890. // the user selected a timezone from the menu
  3891. NETDATA.setOption('user_set_server_timezone', timezone);
  3892. if (NETDATA.dateTime.init(timezone) === false) {
  3893. NETDATA.dateTime.init();
  3894. if (!$('#local_timezone').prop('checked')) {
  3895. $('#local_timezone').bootstrapToggle('on');
  3896. }
  3897. document.getElementById('timezone_error_message').innerHTML = 'Ooops! That timezone was not accepted by your browser. Please open a github issue to help us fix it.';
  3898. NETDATA.setOption('user_set_server_timezone', NETDATA.options.server_timezone);
  3899. } else {
  3900. if ($('#local_timezone').prop('checked')) {
  3901. $('#local_timezone').bootstrapToggle('off');
  3902. }
  3903. }
  3904. } else if (status === true) {
  3905. // the user wants the browser default timezone to be activated
  3906. NETDATA.dateTime.init();
  3907. } else {
  3908. // the user wants the server default timezone to be activated
  3909. //console.log('found ' + NETDATA.options.current.user_set_server_timezone);
  3910. if (NETDATA.options.current.user_set_server_timezone === 'default') {
  3911. NETDATA.options.current.user_set_server_timezone = NETDATA.options.server_timezone;
  3912. }
  3913. timezone = NETDATA.options.current.user_set_server_timezone;
  3914. if (NETDATA.dateTime.init(timezone) === false) {
  3915. NETDATA.dateTime.init();
  3916. if (!$('#local_timezone').prop('checked')) {
  3917. $('#local_timezone').bootstrapToggle('on');
  3918. }
  3919. document.getElementById('timezone_error_message').innerHTML = 'Sorry. The timezone "' + timezone.toString() + '" is not accepted by your browser. Please select one from the list.';
  3920. NETDATA.setOption('user_set_server_timezone', NETDATA.options.server_timezone);
  3921. }
  3922. }
  3923. document.getElementById('current_timezone').innerText = (NETDATA.options.current.timezone === 'default') ? 'unset, using browser default' : NETDATA.options.current.timezone;
  3924. return false;
  3925. };
  3926. // our entry point
  3927. // var netdataStarted = performance.now();
  3928. var netdataCallback = initializeDynamicDashboard;
  3929. // =================================================================================================
  3930. // netdata.cloud
  3931. let registryAgents = [];
  3932. let cloudAgents = [];
  3933. let myNetdataMenuFilterValue = "";
  3934. let cloudAccountID = null;
  3935. let cloudAccountName = null;
  3936. let cloudToken = null;
  3937. /// Enforces a maximum string length while retaining the prefix and the postfix of
  3938. /// the string.
  3939. function truncateString(str, maxLength) {
  3940. if (str.length <= maxLength) {
  3941. return str;
  3942. }
  3943. const spanLength = Math.floor((maxLength - 3) / 2);
  3944. return `${str.substring(0, spanLength)}...${str.substring(str.length - spanLength)}`;
  3945. }
  3946. // -------------------------------------------------------------------------------------------------
  3947. // netdata.cloud API Client
  3948. // -------------------------------------------------------------------------------------------------
  3949. function isValidAgent(a) {
  3950. return a.urls != null && a.urls.length > 0;
  3951. }
  3952. // https://github.com/netdata/hub/issues/146
  3953. function getCloudAccountAgents() {
  3954. if (!isSignedIn()) {
  3955. return [];
  3956. }
  3957. return fetch(
  3958. `${NETDATA.registry.cloudBaseURL}/api/v1/accounts/${cloudAccountID}/agents`,
  3959. {
  3960. method: "GET",
  3961. mode: "cors",
  3962. headers: {
  3963. "Authorization": `Bearer ${cloudToken}`
  3964. }
  3965. }
  3966. ).then((response) => {
  3967. if (!response.ok) {
  3968. throw Error("Cannot fetch known accounts");
  3969. }
  3970. return response.json();
  3971. }).then((payload) => {
  3972. const agents = payload.result ? payload.result.agents : null;
  3973. if (!agents) {
  3974. return [];
  3975. }
  3976. return agents.filter((a) => isValidAgent(a)).map((a) => {
  3977. return {
  3978. "guid": a.id,
  3979. "name": a.name,
  3980. "url": a.urls[0],
  3981. "alternate_urls": a.urls
  3982. }
  3983. })
  3984. }).catch(function (error) {
  3985. console.log(error);
  3986. return null;
  3987. });
  3988. }
  3989. /** Updates the lastAccessTime and accessCount properties of the agent for the account. */
  3990. function touchAgent() {
  3991. if (!isSignedIn()) {
  3992. return [];
  3993. }
  3994. const touchUrl = `${NETDATA.registry.cloudBaseURL}/api/v1/agents/${NETDATA.registry.machine_guid}/touch?account_id=${cloudAccountID}`;
  3995. return fetch(
  3996. touchUrl,
  3997. {
  3998. method: "post",
  3999. body: "",
  4000. mode: "cors",
  4001. headers: {
  4002. "Authorization": `Bearer ${cloudToken}`
  4003. }
  4004. }
  4005. ).then((response) => {
  4006. if (!response.ok) {
  4007. throw Error("Cannot touch agent" + JSON.stringify(response));
  4008. }
  4009. return response.json();
  4010. }).then((payload) => {
  4011. }).catch(function (error) {
  4012. console.log(error);
  4013. return null;
  4014. });
  4015. }
  4016. // https://github.com/netdata/hub/issues/128
  4017. function postCloudAccountAgents(agentsToSync) {
  4018. if (!isSignedIn()) {
  4019. return [];
  4020. }
  4021. const maskedURL = NETDATA.registry.MASKED_DATA;
  4022. const agents = agentsToSync.map((a) => {
  4023. const urls = a.alternate_urls.filter((url) => url != maskedURL);
  4024. return {
  4025. "id": a.guid,
  4026. "name": a.name,
  4027. "urls": urls
  4028. }
  4029. }).filter((a) => isValidAgent(a))
  4030. const payload = {
  4031. "accountID": cloudAccountID,
  4032. "agents": agents,
  4033. "merge": false,
  4034. };
  4035. return fetch(
  4036. `${NETDATA.registry.cloudBaseURL}/api/v1/accounts/${cloudAccountID}/agents`,
  4037. {
  4038. method: "POST",
  4039. mode: "cors",
  4040. headers: {
  4041. "Content-Type": "application/json; charset=utf-8",
  4042. "Authorization": `Bearer ${cloudToken}`
  4043. },
  4044. body: JSON.stringify(payload)
  4045. }
  4046. ).then((response) => {
  4047. return response.json();
  4048. }).then((payload) => {
  4049. const agents = payload.result ? payload.result.agents : null;
  4050. if (!agents) {
  4051. return [];
  4052. }
  4053. return agents.filter((a) => isValidAgent(a)).map((a) => {
  4054. return {
  4055. "guid": a.id,
  4056. "name": a.name,
  4057. "url": a.urls[0],
  4058. "alternate_urls": a.urls
  4059. }
  4060. })
  4061. });
  4062. }
  4063. function deleteCloudAgentURL(agentID, url) {
  4064. if (!isSignedIn()) {
  4065. return [];
  4066. }
  4067. return fetch(
  4068. `${NETDATA.registry.cloudBaseURL}/api/v1/accounts/${cloudAccountID}/agents/${agentID}/url?value=${encodeURIComponent(url)}`,
  4069. {
  4070. method: "DELETE",
  4071. mode: "cors",
  4072. headers: {
  4073. "Content-Type": "application/json; charset=utf-8",
  4074. "Authorization": `Bearer ${cloudToken}`
  4075. },
  4076. }
  4077. ).then((response) => {
  4078. return response.json();
  4079. }).then((payload) => {
  4080. const count = payload.result ? payload.result.count : 0;
  4081. return count;
  4082. });
  4083. }
  4084. // -------------------------------------------------------------------------------------------------
  4085. function signInDidClick(e) {
  4086. e.preventDefault();
  4087. e.stopPropagation();
  4088. if (!NETDATA.registry.isUsingGlobalRegistry()) {
  4089. // If user is using a private registry, request his consent for
  4090. // synchronizing with cloud.
  4091. showSignInModal();
  4092. return;
  4093. }
  4094. signIn();
  4095. }
  4096. function shouldShowSignInBanner() {
  4097. return false;
  4098. }
  4099. function closeSignInBanner() {
  4100. localStorage.setItem("signInBannerClosed", "true");
  4101. const el = document.getElementById("sign-in-banner");
  4102. if (el) {
  4103. el.style.display = "none";
  4104. }
  4105. }
  4106. function closeSignInBannerDidClick(e) {
  4107. closeSignInBanner();
  4108. }
  4109. function signOutDidClick(e) {
  4110. e.preventDefault();
  4111. e.stopPropagation();
  4112. signOut();
  4113. }
  4114. // -------------------------------------------------------------------------------------------------
  4115. function updateMyNetdataAfterFilterChange() {
  4116. const machinesEl = document.getElementById("my-netdata-menu-machines")
  4117. machinesEl.innerHTML = renderMachines(cloudAgents);
  4118. if (options.hosts.length > 1) {
  4119. const streamedEl = document.getElementById("my-netdata-menu-streamed")
  4120. streamedEl.innerHTML = renderStreamedHosts(options);
  4121. }
  4122. }
  4123. function myNetdataMenuDidShow() {
  4124. const filterEl = document.getElementById("my-netdata-menu-filter-input");
  4125. if (filterEl) {
  4126. filterEl.focus();
  4127. }
  4128. }
  4129. function myNetdataFilterDidChange(e) {
  4130. const inputEl = e.target;
  4131. setTimeout(() => {
  4132. myNetdataMenuFilterValue = inputEl.value;
  4133. updateMyNetdataAfterFilterChange();
  4134. }, 1);
  4135. }
  4136. function myNetdataFilterClearDidClick(e) {
  4137. e.preventDefault();
  4138. e.stopPropagation();
  4139. const inputEl = document.getElementById("my-netdata-menu-filter-input");
  4140. inputEl.value = "";
  4141. myNetdataMenuFilterValue = "";
  4142. updateMyNetdataAfterFilterChange();
  4143. inputEl.focus();
  4144. }
  4145. // -------------------------------------------------------------------------------------------------
  4146. function clearCloudVariables() {
  4147. cloudAccountID = null;
  4148. cloudAccountName = null;
  4149. cloudToken = null;
  4150. }
  4151. function clearCloudLocalStorageItems() {
  4152. localStorage.removeItem("cloud.baseURL");
  4153. localStorage.removeItem("cloud.agentID");
  4154. localStorage.removeItem("cloud.sync");
  4155. }
  4156. function signIn() {
  4157. const url = `${NETDATA.registry.cloudBaseURL}/account/sign-in-agent?id=${NETDATA.registry.machine_guid}&name=${encodeURIComponent(NETDATA.registry.hostname)}&origin=${encodeURIComponent(window.location.origin + "/")}`;
  4158. window.open(url);
  4159. }
  4160. function signOut() {
  4161. cloudSSOSignOut();
  4162. }
  4163. function handleMessage(e) {
  4164. switch (e.data.type) {
  4165. case "sign-in":
  4166. handleSignInMessage(e);
  4167. break;
  4168. case "sign-out":
  4169. handleSignOutMessage(e);
  4170. break;
  4171. default:
  4172. return;
  4173. }
  4174. }
  4175. function handleSignInMessage(e) {
  4176. closeSignInBanner();
  4177. localStorage.setItem("cloud.baseURL", NETDATA.registry.cloudBaseURL);
  4178. cloudAccountID = e.data.accountID;
  4179. cloudAccountName = e.data.accountName;
  4180. cloudToken = e.data.token;
  4181. netdataRegistryCallback(registryAgents);
  4182. if (e.data.redirectURI && !window.location.href.includes(e.data.redirectURI)) {
  4183. // lgtm false-positive - redirectURI does not come from user input, but from iframe callback
  4184. window.location.replace(e.data.redirectURI); // lgtm[js/client-side-unvalidated-url-redirection]
  4185. }
  4186. }
  4187. function handleSignOutMessage(e) {
  4188. clearCloudVariables();
  4189. renderMyNetdataMenu(registryAgents);
  4190. }
  4191. function isSignedIn() {
  4192. return cloudToken != null && cloudAccountID != null;
  4193. }
  4194. function sortedArraysEqual(a, b) {
  4195. if (a.length != b.length) return false;
  4196. for (var i = 0; i < a.length; ++i) {
  4197. if (a[i] !== b[i]) return false;
  4198. }
  4199. return true;
  4200. }
  4201. // If merging is needed returns the merged agents set, otherwise returns null.
  4202. function mergeAgents(cloud, local) {
  4203. let dirty = false;
  4204. const union = new Map();
  4205. for (const cagent of cloud) {
  4206. union.set(cagent.guid, cagent);
  4207. }
  4208. for (const lagent of local) {
  4209. const cagent = union.get(lagent.guid);
  4210. if (cagent) {
  4211. for (const u of lagent.alternate_urls) {
  4212. if (u === NETDATA.registry.MASKED_DATA) { // TODO: temp until registry is updated.
  4213. continue;
  4214. }
  4215. if (!cagent.alternate_urls.includes(u)) {
  4216. dirty = true;
  4217. cagent.alternate_urls.push(u);
  4218. }
  4219. }
  4220. } else {
  4221. dirty = true;
  4222. union.set(lagent.guid, lagent);
  4223. }
  4224. }
  4225. if (dirty) {
  4226. return Array.from(union.values());
  4227. }
  4228. return null;
  4229. }
  4230. function showSignInModal() {
  4231. document.getElementById("sim-registry").innerHTML = NETDATA.registry.server;
  4232. $("#signInModal").modal("show");
  4233. }
  4234. function explicitlySignIn() {
  4235. $("#signInModal").modal("hide");
  4236. signIn();
  4237. }
  4238. function showSyncModal() {
  4239. document.getElementById("sync-registry-modal-registry").innerHTML = NETDATA.registry.server;
  4240. $("#syncRegistryModal").modal("show");
  4241. }
  4242. function explicitlySyncAgents() {
  4243. $("#syncRegistryModal").modal("hide");
  4244. const json = localStorage.getItem("cloud.sync");
  4245. const sync = json ? JSON.parse(json) : {};
  4246. delete sync[cloudAccountID];
  4247. localStorage.setItem("cloud.sync", JSON.stringify(sync));
  4248. NETDATA.registry.init();
  4249. }
  4250. function syncAgents(callback) {
  4251. const json = localStorage.getItem("cloud.sync");
  4252. const sync = json ? JSON.parse(json) : {};
  4253. const currentAgent = {
  4254. guid: NETDATA.registry.machine_guid,
  4255. name: NETDATA.registry.hostname,
  4256. url: NETDATA.serverDefault,
  4257. alternate_urls: [NETDATA.serverDefault],
  4258. }
  4259. const localAgents = sync[cloudAccountID]
  4260. ? [currentAgent]
  4261. : registryAgents.concat([currentAgent]);
  4262. console.log("Checking if sync is needed.", localAgents);
  4263. const agentsToSync = mergeAgents(cloudAgents, localAgents);
  4264. if ((!sync[cloudAccountID]) || agentsToSync) {
  4265. sync[cloudAccountID] = new Date().getTime();
  4266. localStorage.setItem("cloud.sync", JSON.stringify(sync));
  4267. }
  4268. if (agentsToSync) {
  4269. console.log("Synchronizing with netdata.cloud.");
  4270. postCloudAccountAgents(agentsToSync).then((agents) => {
  4271. // TODO: clear syncTime on error!
  4272. cloudAgents = agents;
  4273. callback(cloudAgents);
  4274. });
  4275. return
  4276. }
  4277. callback(cloudAgents);
  4278. }
  4279. let isCloudSSOInitialized = false;
  4280. function cloudSSOInit() {
  4281. const iframeEl = document.getElementById("ssoifrm");
  4282. const url = `${NETDATA.registry.cloudBaseURL}/account/sso-agent?id=${NETDATA.registry.machine_guid}`;
  4283. iframeEl.src = url;
  4284. isCloudSSOInitialized = true;
  4285. }
  4286. function cloudSSOSignOut() {
  4287. const iframe = document.getElementById("ssoifrm");
  4288. const url = `${NETDATA.registry.cloudBaseURL}/account/sign-out-agent`;
  4289. iframe.src = url;
  4290. }
  4291. function initCloud() {
  4292. if (!NETDATA.registry.isCloudEnabled) {
  4293. clearCloudVariables();
  4294. clearCloudLocalStorageItems();
  4295. return;
  4296. }
  4297. if (NETDATA.registry.cloudBaseURL != localStorage.getItem("cloud.baseURL")) {
  4298. clearCloudVariables();
  4299. clearCloudLocalStorageItems();
  4300. if (NETDATA.registry.cloudBaseURL) {
  4301. localStorage.setItem("cloud.baseURL", NETDATA.registry.cloudBaseURL);
  4302. }
  4303. }
  4304. if (!isCloudSSOInitialized) {
  4305. cloudSSOInit();
  4306. }
  4307. touchAgent();
  4308. }
  4309. // This callback is called after NETDATA.registry is initialized.
  4310. function netdataRegistryCallback(machinesArray) {
  4311. localStorage.setItem("cloud.agentID", NETDATA.registry.machine_guid);
  4312. initCloud();
  4313. registryAgents = machinesArray;
  4314. if (isSignedIn()) {
  4315. // We call getCloudAccountAgents() here because it requires that
  4316. // NETDATA.registry is initialized.
  4317. clearMyNetdataMenu();
  4318. getCloudAccountAgents().then((agents) => {
  4319. if (!agents) {
  4320. errorMyNetdataMenu();
  4321. return;
  4322. }
  4323. cloudAgents = agents;
  4324. syncAgents((agents) => {
  4325. const agentsMap = {}
  4326. for (const agent of agents) {
  4327. agentsMap[agent.guid] = agent;
  4328. }
  4329. NETDATA.registry.machines = agentsMap;
  4330. NETDATA.registry.machines_array = agents;
  4331. renderMyNetdataMenu(agents);
  4332. });
  4333. });
  4334. } else {
  4335. renderMyNetdataMenu(machinesArray)
  4336. }
  4337. };
  4338. // If we know the cloudBaseURL and agentID from local storage render (eagerly)
  4339. // the account ui before receiving the definitive response from the web server.
  4340. // This improves the perceived performance.
  4341. function tryFastInitCloud() {
  4342. const baseURL = localStorage.getItem("cloud.baseURL");
  4343. const agentID = localStorage.getItem("cloud.agentID");
  4344. if (baseURL && agentID) {
  4345. NETDATA.registry.cloudBaseURL = baseURL;
  4346. NETDATA.registry.machine_guid = agentID;
  4347. NETDATA.registry.isCloudEnabled = true;
  4348. initCloud();
  4349. }
  4350. }
  4351. function initializeApp() {
  4352. window.addEventListener("message", handleMessage, false);
  4353. // tryFastInitCloud();
  4354. }
  4355. if (document.readyState === "complete") {
  4356. initializeApp();
  4357. } else {
  4358. document.addEventListener("readystatechange", () => {
  4359. if (document.readyState === "complete") {
  4360. initializeApp();
  4361. }
  4362. });
  4363. }