PageSourceSearch

https://sauna.re/js/functions.js?v=2.1.5000

js sauna.re collected 2026-10-04 08:11:37 UTC 541,762 bytes, 17,400 lines download raw bytes

1// Create language switcher instance
2var lang = new Lang();
3var langStrings = { token: {} };
4loadLanguageList();
5var falbackLanguage =
6  languageList.filter(
7    (p) => p.code == language(moment.locale(navigator.languages[0]))
8  ).length > 0
9    ? language(moment.locale(navigator.languages[0]))
10    : "en";
11lang.init({
12  //defaultLang: 'en',
13  currentLang: getCookie("organizrLanguage")
14    ? getCookie("organizrLanguage")
15    : falbackLanguage,
16  cookie: {
17    name: "organizrLanguage",
18    expiry: 365,
19    path: "/",
20  },
21  allowCookieOverride: true,
22});
23var OAuthLoginNeeded = false;
24var directToHash = false;
25var pingOrg = false;
26var checkCommitLoadStatus = false;
27var timeouts = {};
28var increment = 0;
29var tabInformation = {};
30var tabActionsList = [];
31tabActionsList["refresh"] = [];
32tabActionsList["close"] = [];
33var customHTMLEditorObject = [];
34$.xhrPool = [];
35// Add new jquery serializeObject function
36$.fn.serializeObject = function () {
37  var o = {};
38  var a = this.serializeArray();
39  $.each(a, function () {
40    if (o[this.name] !== undefined) {
41      if (!o[this.name].push) {
42        o[this.name] = [o[this.name]];
43      }
44      o[this.name].push(this.value || "");
45    } else {
46      o[this.name] = this.value || "";
47    }
48  });
49  return o;
50};
51// Start Organizr
52$(document).ready(function () {
53  if (getCookie("organizrOAuth")) {
54    OAuthLoginNeeded = true;
55  }
56  launch();
57  local("r", "loggingIn");
58});
59/* NORMAL FUNCTIONS */
60function setLangCookie(lang) {
61  Cookies.set("organizrLanguage", lang, {
62    expires: 365,
63    path: "/",
64  });
65}
66
67function highlightObject(json) {
68  if (typeof json != "string") {
69    json = JSON.stringify(json, undefined, "\t");
70  }
71  json = json
72    .replace(/&/g, "&")
73    .replace(/</g, "&lt;")
74    .replace(/>/g, "&gt;");
75  return json.replace(
76    /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
77    function (match) {
78      var cls = "number";
79      if (/^"/.test(match)) {
80        if (/:$/.test(match)) {
81          cls = "key";
82        } else {
83          cls = "string";
84        }
85      } else if (/true|false/.test(match)) {
86        cls = "boolean";
87      } else if (/null/.test(match)) {
88        cls = "null";
89      }
90      return '<span class="' + cls + '">' + match + "</span>";
91    }
92  );
93}
94function orgDebug() {
95  let cmd = $("#debug-input").val();
96  let result = "";
97  if (cmd !== "") {
98    result = eval(cmd);
99  }
100  if (result !== "") {
101    $("#debugResultsBox").removeClass("hidden");
102    $("#debugResults").html(formatDebug(result));
103    $(".cmdName").text(cmd);
104    if (bowser.mobile !== true) {
105      $("#debugResults > .whitebox").slimScroll();
106    }
107  } else {
108  }
109}
110function getDepth(object) {
111  var level = 1;
112  for (var key in object) {
113    if (!object.hasOwnProperty(key)) continue;
114
115    if (typeof object[key] == "object") {
116      var depth = getDepth(object[key]) + 1;
117      level = Math.max(depth, level);
118    }
119  }
120  return level;
121}
122function jsonToHTML(json) {
123  var html = "";
124  $.each(json, function (i, v) {
125    if (typeof v === "object") {
126      html += '<p class="tab0">' + i + ":</p>";
127      $.each(v, function (index, value) {
128        if (typeof value === "object") {
129          html += '<p class="tab1">' + index + ":</p>";
130          html += jsonToHTML2(value);
131        } else {
132          html += '<p class="tab1">' + index + ": " + value + "</p>";
133        }
134      });
135    } else {
136      html += '<p class="tab0">' + i + ": " + v + "</p>";
137    }
138  });
139  return html;
140}
141function jsonToHTML2(json) {
142  var html = "";
143  $.each(json, function (i, v) {
144    if (typeof v === "object") {
145      html += '<p class="tab2">' + i + ":</p>";
146      $.each(v, function (index, value) {
147        if (typeof value === "object") {
148          html += '<p class="tab3">' + index + ":</p>";
149          html += jsonToHTML3(value);
150        } else {
151          html += '<p class="tab3">' + index + ": " + value + "</p>";
152        }
153      });
154    } else {
155      html += '<p class="tab2">' + i + ": " + v + "</p>";
156    }
157  });
158  return html;
159}
160function jsonToHTML3(json) {
161  var html = "";
162  $.each(json, function (i, v) {
163    if (typeof v === "object") {
164      html += '<p class="tab4">' + i + ":</p>";
165      $.each(v, function (index, value) {
166        if (typeof value === "object") {
167          html += '<p class="tab5">' + index + ":</p>";
168          html += jsonToHTML2(value);
169        } else {
170          html += '<p class="tab5">' + index + ": " + value + "</p>";
171        }
172      });
173    } else {
174      html += '<p class="tab4">' + i + ": " + v + "</p>";
175    }
176  });
177  return html;
178}
179
180function copyDebug() {
181  var pre = $("#debugPreInfo").find(".whitebox").text();
182  var debug = $("#debugResults").find(".whitebox").text();
183  clipboard(true, pre + debug);
184  console.log(pre + debug);
185}
186function formatDebug(result) {
187  var formatted = "";
188  switch (typeof result) {
189    case "object":
190      formatted = jsonToHTML(result);
191      break;
192    default:
193      formatted = result;
194  }
195  return (
196    '<pre class="whitebox bg-org text-success default-scroller">' +
197    formatted +
198    "</pre>"
199  );
200}
201function getDebugPreInfo() {
202  var formatted =
203    "Version: " +
204    activeInfo.version +
205    "<br/>Branch: " +
206    activeInfo.branch +
207    "<br/>Server OS: " +
208    activeInfo.serverOS +
209    "<br/>PHP: " +
210    activeInfo.phpVersion +
211    "<br/>Install Type: " +
212    (activeInfo.settings.misc.docker ? "Official Docker" : "Native") +
213    "<br/>Auth Type: " +
214    activeInfo.settings.misc.authType +
215    "<br/>Auth Backend: " +
216    activeInfo.settings.misc.authBackend +
217    "<br/>Installed Plugins: " +
218    formatDebug(activeInfo.settings.misc.installedPlugins) +
219    "<br/>Installed Themes: " +
220    formatDebug(activeInfo.settings.misc.installedThemes) +
221    "<br/>Theme: " +
222    activeInfo.theme +
223    "<br/>Local: " +
224    activeInfo.settings.user.local +
225    "<br/>oAuth: " +
226    activeInfo.settings.user.oAuthLogin +
227    "<br/>Agent: " +
228    activeInfo.settings.user.agent;
229  formatted =
230    '<pre class="whitebox bg-org text-success">' + formatted + "</pre>";
231  $("#debugPreInfo").html(formatted);
232  if (bowser.mobile !== true) {
233    $("#debugPreInfo > .whitebox").slimScroll();
234  }
235}
236function orgDebugList(cmd) {
237  if (cmd !== "") {
238    $("#debug-input").val(cmd);
239    orgDebug();
240  }
241}
242function updateIssueLink(line) {
243  let preNumber = line.match(/\S*\#(.*)/g);
244  if (preNumber !== null) {
245    preNumber = preNumber.toString();
246    let numberSplit = preNumber.split("#");
247    let issueType = numberSplit[0].replace("(", "").replace(")", "");
248    let issueNumber = numberSplit[1].replace("(", "").replace(")", "");
249    let issueWord =
250      issueType.toLowerCase() == "fr"
251        ? '<i class="icon-arrow-up-circle"></i> feature'
252        : '<i class="fa fa-github"></i> issue';
253    let colorType =
254      issueType.toLowerCase() == "fr" ? "label-info" : "label-primary";
255    let issueLink =
256      issueType.toLowerCase() == "fr"
257        ? "https://feature.organizr.app/posts/" + issueNumber
258        : "https://github.com/causefx/Organizr/issues/" + issueNumber;
259    issueLink =
260      '<span class="label upgrade-label text-uppercase ' +
261      colorType +
262      ' label-rounded font-12 pull-right"><a class="text-white text-uppercase" href="' +
263      issueLink +
264      '" target="_blank">' +
265      issueWord +
266      "</a></span>";
267    return line.replace(preNumber, issueLink);
268  } else {
269    return line;
270  }
271}
272function clipboard(trigger = true, string = null) {
273  let clipboard = $("#internal-clipboard");
274  if (string) {
275    clipboard.attr("data-clipboard-text", string);
276  }
277  if (trigger) {
278    clipboard.click();
279  }
280}
281function getLangStrings() {
282  let strings = JSON.stringify(window.langStrings, null, "\t");
283  clipboard(true, strings);
284  organizrConsole("JSON Function", "Copied JSON Strings to clipboard");
285}
286function getHiddenProp() {
287  var prefixes = ["webkit", "moz", "ms", "o"];
288  // if 'hidden' is natively supported just return it
289  if ("hidden" in document) return "hidden";
290  // otherwise loop over all the known prefixes until we find one
291  for (var i = 0; i < prefixes.length; i++) {
292    if (prefixes[i] + "Hidden" in document) return prefixes[i] + "Hidden";
293  }
294  // otherwise it's not supported
295  return null;
296}
297function isHidden() {
298  var prop = getHiddenProp();
299  if (!prop) return false;
300  return document[prop];
301}
302function loadLanguageList() {
303  $.each(languageList, function (i, v) {
304    lang.dynamic(v.code, "js/langpack/" + v.filename);
305  });
306}
307function sleep(ms) {
308  return new Promise((resolve) => setTimeout(resolve, ms));
309}
310function contains(target, pattern) {
311  var value = 0;
312  pattern.forEach(function (word) {
313    value = value + target.includes(word);
314  });
315  return value === 1;
316}
317function isNumberKey(evt) {
318  var charCode = evt.which ? evt.which : event.keyCode;
319  if (charCode < 48 || charCode > 57) return false;
320  return true;
321}
322function setTabInfo(id, action, value) {
323  let tabInfo = findTab(id);
324  if (!tabInfo) {
325    organizrConsole("Set Tab Info", "No Tab Info Found... Id: " + id, "error");
326    return false;
327  }
328  let tab = cleanClass(tabInfo.name);
329  if (
330    tab == "Organizr-Support" ||
331    tab == "Organizr-Docs" ||
332    tab == "Feature-Request"
333  ) {
334    return false;
335  }
336  if (tab !== null && action !== null && value !== null) {
337    switch (action) {
338      case "active":
339        $.each(tabInformation, function (i, v) {
340          tabInformation[i]["active"] = false;
341        });
342        break;
343      default:
344      //nada
345    }
346    tabInformation[id][action] = value;
347  } else {
348    return false;
349  }
350}
351function tabTimerAction() {
352  if (tabActionsList.close.length > 0) {
353    $.each(tabActionsList.close, function (i, v) {
354      var tab = v.tab;
355      var minutes = tabInformation[v.id]["tabInfo"]["timeout_ms"] / 1000 / 60;
356      var process = false;
357      if (tabInformation[v.id]["loaded"]) {
358        if (tabInformation[v.id]["active"] && idleTime >= 1) {
359          process = true;
360        }
361        if (tabInformation[v.id]["active"] === false) {
362          process = true;
363        }
364        if (process) {
365          tabInformation[v.id]["increments"] =
366            tabInformation[v.id]["increments"] + 1;
367          if (tabInformation[v.id]["increments"] >= minutes) {
368            tabInformation[v.id]["increments"] = 0;
369            organizrConsole("Tab Function", "Auto Closing tab: " + tab);
370            closeTab(v.id);
371          }
372        }
373      }
374    });
375  }
376  if (tabActionsList.refresh.length > 0) {
377    $.each(tabActionsList.refresh, function (i, v) {
378      var tab = v.tab;
379      var minutes = tabInformation[v.id]["tabInfo"]["timeout_ms"] / 1000 / 60;
380      var process = false;
381      if (tabInformation[v.id]["loaded"]) {
382        tabInformation[v.id]["increments"] =
383          tabInformation[v.id]["increments"] + 1;
384        if (tabInformation[v.id]["increments"] >= minutes) {
385          tabInformation[v.id]["increments"] = 0;
386          organizrConsole("Tab Function", "Auto Reloading tab: " + tab);
387          reloadTab(v.id);
388        }
389      }
390    });
391  }
392}
393function timerIncrement() {
394  increment = increment + 1;
395  tabTimerAction();
396  //check for cookieExpiry
397  if (hasCookie) {
398    if (getCookie("organizrToken")) {
399      //do nothing
400    } else {
401      location.reload();
402    }
403  }
404  idleTime = idleTime + 1;
405  if (typeof activeInfo !== "undefined") {
406    if (
407      activeInfo.settings.lockout.enabled &&
408      activeInfo.settings.user.oAuthLogin !== true
409    ) {
410      if (
411        idleTime > activeInfo.settings.lockout.timer &&
412        $("#lockScreen").length !== 1
413      ) {
414        if (
415          activeInfo.user.groupID <= activeInfo.settings.lockout.minGroup &&
416          activeInfo.user.groupID >= activeInfo.settings.lockout.maxGroup
417        ) {
418          lock();
419        }
420      }
421    }
422  }
423}
424function ajaxblocker(
425  element = null,
426  action = "out",
427  message = "Loading...",
428  background = "#707cd2",
429  border = "#5761a9",
430  colorText = "#fff"
431) {
432  switch (action) {
433    case "in":
434    case "fadein":
435      $(element).block({
436        message:
437          '<p style="margin:0;padding:8px;font-size:24px;" lang="en">' +
438          message +
439          "</p>",
440        css: {
441          color: colorText,
442          border: "1px solid " + border,
443          backgroundColor: background,
444        },
445      });
446      break;
447    case "out":
448    case "fadeout":
449      $(element).unblock();
450      break;
451    default:
452      $(element).unblock();
453  }
454}
455function ajaxloader(element = null, action = "out") {
456  var loader = `
457	<div class="ajaxloader">
458		<svg class="circular" viewBox="25 25 50 50">
459			<circle class="path" cx="50" cy="50" fill="none" r="20" stroke-miterlimit="10" stroke-width="5"></circle>
460		</svg>
461	</div>`;
462  switch (action) {
463    case "in":
464    case "fadein":
465      $(loader).appendTo(element);
466      break;
467    case "out":
468    case "fadeout":
469      $(".ajaxloader").remove();
470      break;
471    default:
472      $(".ajaxloader").remove();
473  }
474}
475function getDefault(id) {
476  let tabInfo = findTab(id);
477  if (!tabInfo) {
478    if (getHash() === false) {
479      organizrConsole("Get Default", "No Tab Info Found... Id: " + id, "error");
480      organizrConsole(
481        "Get Default",
482        "Trying to load next tab in cycle",
483        "error"
484      );
485      loadNextTab(true);
486      return false;
487    }
488  }
489  if (
490    getHash() === false ||
491    (getHash() === "OrganizrLogin" && activeInfo.user.loggedin)
492  ) {
493    if (tabInfo) {
494      switchTab(id);
495    } else {
496      $(".allTabsList").first().children().click();
497    }
498  } else if (getHash() == "OrganizrLogin") {
499    loadNextTab(true);
500  } else {
501    let hashTab = getHash();
502    let hashType = isNaN(hashTab) ? "name" : "id";
503    let tabInfo = findTab(hashTab, hashType);
504    if (!tabInfo) {
505      organizrConsole(
506        "Get Hash",
507        "No Tab Info Found... Hash: " + hashTab,
508        "error"
509      );
510      switchTab(id);
511      return false;
512    }
513    let type = tabInfo.type;
514    if (typeof hashTab !== "undefined" && typeof type !== "undefined") {
515      directToHash = true;
516      switchTab(tabInfo.id);
517    } else {
518      console.warn("Tab Function: " + hashTab + " is not a defined tab");
519      switchTab(id);
520    }
521  }
522}
523function getTabType(id) {
524  let tabInfo = findTab(id);
525  if (!tabInfo) {
526    organizrConsole(
527      "Tab Type Function",
528      "No Tab Info Found... Id: " + id,
529      "error"
530    );
531    return false;
532  }
533  return tabInfo.type;
534}
535function getHash() {
536  if ($(location).attr("hash")) {
537    return dirtyHash($(location).attr("hash").substr(1));
538  }
539  return false;
540}
541function setHash(hash) {
542  window.location.hash = "#" + cleanHash(hash);
543}
544function getQueryVariable(variable) {
545  var query = window.location.search.substring(1);
546  var vars = query.split("&");
547  for (var i = 0; i < vars.length; i++) {
548    var pair = vars[i].split("=");
549    if (pair[0] == variable) {
550      return pair[1];
551    }
552  }
553  return false;
554}
555function iconPrefix(source) {
556  var tabIcon = source.split("::");
557  var icons = {
558    materialize: "mdi mdi-",
559    fontawesome: "fa fa-",
560    themify: "ti-",
561    simpleline: "icon-",
562    weathericon: "wi wi-",
563    alphanumeric: "fa-fw",
564  };
565  if (Array.isArray(tabIcon) && tabIcon.length === 2) {
566    if (tabIcon[0] !== "url" && tabIcon[0] !== "alphanumeric") {
567      return '<i class="' + icons[tabIcon[0]] + tabIcon[1] + ' fa-fw"></i>';
568    } else if (tabIcon[0] == "alphanumeric") {
569      return '<i class="fa-fw">' + tabIcon[1] + "</i>";
570    } else {
571      return '<img class="fa-fw" src="' + tabIcon[1] + '" alt="tabIcon" />';
572    }
573  } else {
574    return '<img class="fa-fw" src="' + source + '" alt="tabIcon" />';
575  }
576}
577function iconPrefixSplash(source) {
578  var tabIcon = source.split("::");
579  var icons = {
580    materialize: "mdi mdi-",
581    fontawesome: "fa fa-",
582    themify: "ti-",
583    simpleline: "icon-",
584    weathericon: "wi wi-",
585    alphanumeric: "fa-fw",
586  };
587  if (Array.isArray(tabIcon) && tabIcon.length === 2) {
588    if (tabIcon[0] !== "url" && tabIcon[0] !== "alphanumeric") {
589      return '<i class="' + icons[tabIcon[0]] + tabIcon[1] + ' fa-fw"></i>';
590    } else if (tabIcon[0] == "alphanumeric") {
591      return '<i class="fa-fw">' + tabIcon[1] + "</i>";
592    } else {
593      return tabIcon[1];
594    }
595  } else {
596    return source;
597  }
598}
599function cleanClass(string) {
600  return string.replace(/ +/g, "-").replace(/\W+/g, "-");
601}
602function cleanHash(hash) {
603  hash = encodeURI(hash);
604  return hash.replaceAll("%20", "-");
605}
606function dirtyHash(hash) {
607  hash = hash.replaceAll("-", "%20");
608  return decodeURI(hash);
609}
610// What the hell is this?  I don't remember this lol
611function noTabs(arrayItems) {
612  if (arrayItems.data.user.loggedin === true) {
613    organizrAPI2("GET", "api/v2/page/tabs")
614      .success(function (data) {
615        try {
616          var json = data.response;
617          organizrConsole("Organizr Function", "No tabs available");
618          $(json.data).appendTo($(".organizr-area"));
619          $(".organizr-area").removeClass("hidden");
620          $("#preloader").fadeOut();
621        } catch (e) {
622          organizrCatchError(e, data);
623        }
624      })
625      .fail(function (xhr) {
626        OrganizrApiError(xhr, "Error");
627      });
628  } else {
629    $(".show-login").trigger("click");
630  }
631}
632function formatImage(icon) {
633  if (!icon.id || icon.text == "Select or type Image") {
634    return icon.text;
635  }
636  var baseUrl = "/user/pages/images/flags";
637  var $icon = $(
638    '<span><img src="' +
639      icon.id +
640      '" class="img-chooser" /> ' +
641      icon.text +
642      "</span>"
643  );
644  return $icon;
645}
646function formatIcon(icon) {
647  if (!icon.id || icon.text == "Select or type Icon") {
648    return icon.text;
649  }
650  var $icon = $("<span>" + iconPrefix(icon.id) + icon.text + "</span>");
651  return $icon;
652}
653function logout() {
654  message(
655    "",
656    " Goodbye!",
657    activeInfo.settings.notifications.position,
658    "#FFF",
659    "success",
660    "10000"
661  );
662  organizrAPI2("GET", "api/v2/logout")
663    .success(function (data) {
664      local("set", "message", "Goodbye|Logout Successful|success");
665      history.replaceState(null, null, " ");
666      if (
667        activeInfo.settings.misc.authProxyOverrideLogout &&
668        activeInfo.settings.misc.authProxyLogoutURL !== ""
669      ) {
670        location.href = activeInfo.settings.misc.authProxyLogoutURL;
671      } else {
672        location.reload();
673      }
674    })
675    .fail(function (xhr) {
676      OrganizrApiError(xhr, "Logout Failed");
677    });
678}
679function reloadOrganizr() {
680  location.reload();
681}
682function hideFrames(split = null) {
683  let extra = split ? "-right" : "";
684  $(".iFrame-listing" + extra + " div[class^='frame-container']")
685    .addClass("hidden")
686    .removeClass("show");
687  $(".internal-listing" + extra + " div[class^='internal-container']")
688    .addClass("hidden")
689    .removeClass("show");
690  $(".plugin-listing" + extra + " div[class^='plugin-container']")
691    .addClass("hidden")
692    .removeClass("show");
693}
694function closeSideMenu() {
695  $(".content-wrapper").removeClass("show-sidebar");
696}
697function removeMenuActive() {
698  $("#side-menu a").removeClass("active");
699}
700function swapDisplay(type, split) {
701  let extra = split ? "-right" : "";
702  switch (type) {
703    case "internal":
704      $("body").removeClass("fix-header");
705      $(".iFrame-listing" + extra)
706        .addClass("hidden")
707        .removeClass("show");
708      $(".internal-listing" + extra)
709        .addClass("show")
710        .removeClass("hidden");
711      $(".login-area").addClass("hidden").removeClass("show");
712      $(".plugin-listing" + extra)
713        .addClass("hidden")
714        .removeClass("show");
715      //$('body').removeClass('fix-header');
716      if (split) {
717        $("#page-wrapper").addClass("split");
718        $("#page-wrapper-right").removeClass("hidden");
719      }
720      break;
721    case "iframe":
722      $("body").addClass("fix-header");
723      $(".iFrame-listing" + extra)
724        .addClass("show")
725        .removeClass("hidden");
726      $(".internal-listing" + extra)
727        .addClass("hidden")
728        .removeClass("show");
729      $(".login-area").addClass("hidden").removeClass("show");
730      $(".plugin-listing" + extra)
731        .addClass("hidden")
732        .removeClass("show");
733      //$('body').addClass('fix-header');
734      if (split) {
735        $("#page-wrapper").addClass("split");
736        $("#page-wrapper-right").removeClass("hidden");
737      }
738      break;
739    case "login":
740      $("body").removeClass("fix-header");
741      $(".iFrame-listing" + extra)
742        .addClass("hidden")
743        .removeClass("show");
744      $(".internal-listing" + extra)
745        .addClass("hidden")
746        .removeClass("show");
747      $(".login-area").addClass("show").removeClass("hidden");
748      $(".plugin-listing" + extra)
749        .addClass("hidden")
750        .removeClass("show");
751      if (activeInfo.settings.misc.minimalLoginScreen == true) {
752        $(".sidebar").addClass("hidden");
753        $(".navbar").addClass("hidden");
754        $("#page-wrapper").addClass("hidden");
755      }
756      if (split) {
757        $("#page-wrapper").addClass("split");
758        $("#page-wrapper-right").removeClass("hidden");
759      }
760      break;
761    case "plugin":
762      $(".iFrame-listing" + extra)
763        .addClass("hidden")
764        .removeClass("show");
765      $(".internal-listing" + extra)
766        .addClass("hidden")
767        .removeClass("show");
768      $(".login-area").addClass("hidden").removeClass("show");
769      $(".plugin-listing" + extra)
770        .addClass("show")
771        .removeClass("hidden");
772      if (split) {
773        $("#page-wrapper").addClass("split");
774        $("#page-wrapper-right").removeClass("hidden");
775      }
776      break;
777    default:
778  }
779}
780function toggleParentActive(id) {
781  var childTab = $("#menu-" + id);
782  if (childTab.parent().hasClass("nav-second-level")) {
783    if (!childTab.parent().hasClass("in")) {
784      childTab.parent().addClass("collapse in");
785      childTab.parent().parent().addClass("active");
786    }
787  }
788}
789function swapBodyClass(id) {
790  let tabInfo = findTab(id);
791  if (!tabInfo) {
792    organizrConsole("Swap Body", "No Tab Info Found... Id: " + id, "error");
793    return false;
794  }
795  let prior = $("body").attr("data-active-tab");
796  let priorId = $("body").attr("data-active-tab-id");
797  if (prior !== "") {
798    $("body").removeClass("active-tab-" + prior);
799    $("body").removeClass("active-tab-" + priorId);
800  }
801  $("body").attr("data-active-tab", tabInfo.name);
802  $("body").attr("data-active-tab-id", tabInfo.id);
803  $("body").addClass("active-tab-" + tabInfo.name);
804  $("body").addClass("active-tab-" + tabInfo.id);
805}
806function editPageTitle(title) {
807  document.title = title + " - " + activeInfo.appearance.title;
808}
809function switchToPlugin(plugin) {
810  closeSideMenu();
811  removeMenuActive();
812  swapDisplay("plugin");
813  $(".plugin-container").each(function () {
814    $(this).addClass("hidden").removeClass("show");
815  });
816  $("#container-plugin-" + plugin)
817    .addClass("show")
818    .removeClass("hidden");
819}
820function switchTab(id, split = null) {
821  let tabInfo = findTab(id);
822  if (!tabInfo) {
823    organizrConsole("Switch Tab", "No Tab Info Found... Id: " + id, "error");
824    return false;
825  }
826  if (activeInfo.settings.misc.collapseSideMenuOnClick) {
827    if (!$(".navbar ").hasClass("sidebar-hidden")) {
828      toggleSideMenu();
829    }
830  }
831  let extra = split ? "right-" : "";
832  let type = tabInfo.type;
833  // need to rework for split
834  if (type !== 2) {
835    hideFrames(split);
836    closeSideMenu();
837    removeMenuActive();
838    toggleParentActive(id);
839    swapBodyClass(id);
840  }
841  if (type !== 2 && type !== "plugin") {
842    setHash(tabInfo.name);
843  }
844  switch (type) {
845    case 0:
846    case "0":
847    case "internal":
848      swapDisplay("internal", split);
849      var newTab = $("#internal-" + extra + id);
850      $("#menu-" + id)
851        .find("a")
852        .addClass("active");
853      editPageTitle(tabInfo.name);
854      if (newTab.hasClass("loaded")) {
855        organizrConsole("Tab Function", "Switching to tab: " + tabInfo.name);
856        newTab.addClass("show").removeClass("hidden");
857        setTabInfo(id, "active", true);
858      } else {
859        //$("#preloader").fadeIn();
860        organizrConsole("Tab Function", "Loading new tab for: " + tabInfo.name);
861        $("#menu-" + id + " a")
862          .children()
863          .addClass("tabLoaded");
864        newTab.addClass("show loaded").removeClass("hidden");
865        loadInternal(id, split);
866        setTabInfo(id, "active", true);
867        setTabInfo(id, "loaded", true);
868        //$("#preloader").fadeOut();
869      }
870      break;
871    case 1:
872    case "1":
873    case "iframe":
874      swapDisplay("iframe", split);
875      var newTab = $("#container-" + extra + id);
876      var tabURL = newTab.attr("data-url");
877      $("#menu-" + id)
878        .find("a")
879        .addClass("active");
880      editPageTitle(tabInfo.name);
881      if (newTab.hasClass("loaded")) {
882        organizrConsole("Tab Function", "Switching to tab: " + tabInfo.name);
883        newTab.addClass("show").removeClass("hidden");
884        setTabInfo(id, "active", true);
885      } else {
886        $("#preloader").fadeIn();
887        organizrConsole("Tab Function", "Loading new tab for: " + tabInfo.name);
888        $("#menu-" + id + " a")
889          .children()
890          .addClass("tabLoaded");
891        newTab.addClass("show loaded").removeClass("hidden");
892        $(buildFrame(id, extra)).appendTo(newTab);
893        setTabInfo(id, "active", true);
894        setTabInfo(id, "loaded", true);
895        $("#preloader").fadeOut();
896      }
897      $("#frame-" + id).focus();
898      break;
899    case 2:
900    case 3:
901    case "2":
902    case "3":
903    case "_blank":
904    case "popout":
905      popTab(id);
906      break;
907    case "plugin":
908      swapDisplay("plugin");
909      $("#container-plugin-" + id)
910        .addClass("show")
911        .removeClass("hidden");
912      break;
913    default:
914      organizrConsole("Tab Function", "Action not set", "error");
915  }
916}
917function popTab(id) {
918  let tabInfo = findTab(id);
919  if (!tabInfo) {
920    organizrConsole(
921      "Pop Tab Function",
922      "No Tab Info Found... Id: " + id,
923      "error"
924    );
925    return false;
926  }
927  let name = tabInfo.name;
928  switch (tabInfo.type) {
929    case 0:
930    case "0":
931    case "internal":
932      console.warn(
933        "Tab Function: New window not supported for tab id: " +
934          id +
935          " | " +
936          name
937      );
938      break;
939    case 1:
940    case "1":
941    case "iframe":
942    case 2:
943    case 3:
944    case "2":
945    case "3":
946    case "_blank":
947    case "popout":
948      organizrConsole(
949        "Tab Function",
950        "Creating New Window for tab id: " + id + " | " + name
951      );
952      window.open(tabInfo.access_url, "_blank");
953      break;
954    default:
955      organizrConsole("Tab Function", "Action not set", "error");
956  }
957}
958function closeTab(id) {
959  let tabInfo = findTab(id);
960  if (!tabInfo) {
961    organizrConsole(
962      "Close Tab Function",
963      "No Tab Info Found... Id: " + id,
964      "error"
965    );
966    return false;
967  }
968  // check if current tab?
969  if ($(".active-tab-" + id).length > 0) {
970    closeCurrentTab(event);
971  } else {
972    if ($(".frame-" + id).hasClass("loaded")) {
973      switch (tabInfo.type) {
974        case 0:
975        case "0":
976        case "internal":
977          // quick check if homepage
978          if (tabInfo.access_url == "api/v2/page/homepage") {
979            organizrConsole(
980              "Organizr Function",
981              "Clearing All Homepage AJAX calls"
982            );
983            clearAJAX("homepage");
984            $.xhrPool.abortAll();
985          }
986          organizrConsole("Tab Function", "Closing tab: " + tabInfo.name);
987          $("#internal-" + id).html("");
988          $("#menu-" + id + " a").removeClass("active");
989          $("#menu-" + id + " a")
990            .children()
991            .removeClass("tabLoaded");
992          $("#internal-" + id).removeClass("loaded show");
993          $("#menu-" + id).removeClass("active");
994          setTabInfo(id, "loaded", false);
995          break;
996        case 1:
997        case "1":
998        case "iframe":
999          organizrConsole("Tab Function", "Closing tab: " + tab);
1000          $("#menu-" + id + " a").removeClass("active");
1001          $("#menu-" + id + " a")
1002            .children()
1003            .removeClass("tabLoaded");
1004          $("#container-" + id).removeClass("loaded show");
1005          $("#frame-" + id).remove();
1006          setTabInfo(id, "loaded", false);
1007          break;
1008        case 2:
1009        case 3:
1010        case "2":
1011        case "3":
1012        case "_blank":
1013        case "popout":
1014          break;
1015        default:
1016          organizrConsole("Tab Function", "Action not set", "error");
1017      }
1018    }
1019  }
1020}
1021function reloadTab(id) {
1022  let tabInfo = findTab(id);
1023  if (!tabInfo) {
1024    organizrConsole(
1025      "Reload Tab Function",
1026      "No Tab Info Found... Id: " + id,
1027      "error"
1028    );
1029    return false;
1030  }
1031  $("#preloader").fadeIn();
1032  organizrConsole("Tab Function", "Reloading tab: " + tabInfo.name);
1033  switch (tabInfo.type) {
1034    case 0:
1035    case "0":
1036    case "internal":
1037      if (tabInfo.access_url == "api/v2/page/homepage") {
1038        organizrConsole(
1039          "Organizr Function",
1040          "Clearing All Homepage AJAX calls"
1041        );
1042        clearAJAX("homepage");
1043        $.xhrPool.abortAll();
1044      }
1045      $("#frame-" + id).html("");
1046      loadInternal(id);
1047      break;
1048    case 1:
1049    case "1":
1050    case "iframe":
1051      $("#frame-" + id).attr("src", $("#frame-" + id).attr("src"));
1052      break;
1053    case 2:
1054    case 3:
1055    case "2":
1056    case "3":
1057    case "_blank":
1058    case "popout":
1059      break;
1060    default:
1061      organizrConsole("Tab Function", "Action not set", "error");
1062  }
1063  $("#preloader").fadeOut();
1064}
1065function reloadCurrentTab() {
1066  //$("#preloader").fadeIn();
1067  organizrConsole("Tab Function", "Reloading Current tab");
1068  let id = null;
1069  let iframe = $(".iFrame-listing").find(".show");
1070  let internal = $(".internal-listing").find(".show");
1071  if (iframe.length > 0) {
1072    var type = "iframe";
1073  } else if (internal.length > 0) {
1074    var type = "internal";
1075  } else {
1076    var type = "not set";
1077  }
1078  switch (type) {
1079    case 0:
1080    case "0":
1081    case "internal":
1082      let activeInternal = $(".internal-listing").find(".show");
1083      if (activeInternal) {
1084        id = activeInternal.attr("id");
1085        id = id.split("-")[1];
1086      }
1087      if (id) {
1088        var tabInfo = findTab(id);
1089        if (!tabInfo) {
1090          organizrConsole(
1091            "Reload Current Tab Function",
1092            "No Tab Info Found... Id: " + id,
1093            "error"
1094          );
1095          return false;
1096        }
1097      } else {
1098        return false;
1099      }
1100      if (tabInfo.access_url == "api/v2/page/homepage") {
1101        organizrConsole(
1102          "Organizr Function",
1103          "Clearing All Homepage AJAX calls"
1104        );
1105        clearAJAX("homepage");
1106        $.xhrPool.abortAll();
1107      }
1108      $(activeInternal).html("");
1109      loadInternal(id);
1110      break;
1111    case 1:
1112    case "1":
1113    case "iframe":
1114      let activeFrame = $(".iFrame-listing").find(".show").children("iframe");
1115      if (RegExp("^/.*").test(activeFrame.attr("src"))) {
1116        activeFrame.attr("src", activeFrame[0].contentWindow.location.pathname);
1117      } else {
1118        activeFrame.attr("src", activeFrame.attr("src"));
1119      }
1120      break;
1121    case 2:
1122    case 3:
1123    case "2":
1124    case "3":
1125    case "_blank":
1126    case "popout":
1127      break;
1128    default:
1129      console.error("Tab Function: Action not set");
1130  }
1131  //$("#preloader").fadeOut();
1132}
1133function loadNextTab(loadNextTabIfNotLoaded = false) {
1134  let next = $("#page-wrapper").find(".loaded").attr("id");
1135  if (next) {
1136    next = next.split("-")[1];
1137  }
1138  if (typeof next !== "undefined") {
1139    let parent = $("#menu-" + next).parent();
1140    if (
1141      parent.hasClass("in") === false &&
1142      parent.hasClass("nav-second-level")
1143    ) {
1144      parent.parent().find("a").first().trigger("click");
1145    }
1146    switchTab(next);
1147  } else {
1148    if (loadNextTabIfNotLoaded) {
1149      if (findTab(0, "type")) {
1150        var id = findTab(0, "type")["id"];
1151      } else {
1152        var id = findTab(1, "type")["id"];
1153      }
1154      tabActions(1, id);
1155    } else {
1156      organizrConsole("Tab Function", "No Available Tab to open", "error");
1157    }
1158  }
1159}
1160function closeCurrentTab(event) {
1161  let extra = "";
1162  let split = "";
1163  if (typeof event !== "undefined") {
1164    if (event.ctrlKey && event.altKey && !event.shiftKey) {
1165      extra = "-right";
1166      split = true;
1167    }
1168  }
1169  if ($(".plugin-listing").hasClass("show")) {
1170    hideFrames(split);
1171    loadNextTab();
1172    return false;
1173  }
1174  let id = $("body").attr("data-active-tab-id");
1175  let tabInfo = findTab(id);
1176  if (!tabInfo) {
1177    organizrConsole(
1178      "Close Current Tab Function",
1179      "No Tab Info Found... Id: " + id,
1180      "error"
1181    );
1182    return false;
1183  }
1184  var iframe = $(".iFrame-listing" + extra).find(".show");
1185  var internal = $(".internal-listing" + extra).find(".show");
1186  if (iframe.length > 0) {
1187    var type = "iframe";
1188  } else if (internal.length > 0) {
1189    var type = "internal";
1190  } else {
1191    var type = "not set";
1192  }
1193  switch (type) {
1194    case 0:
1195    case "0":
1196    case "internal":
1197      // quick check if homepage
1198      if (tabInfo.access_url == "api/v2/page/homepage") {
1199        organizrConsole(
1200          "Organizr Function",
1201          "Clearing All Homepage AJAX calls"
1202        );
1203        clearAJAX("homepage");
1204        $.xhrPool.abortAll();
1205      }
1206      organizrConsole("Organizr Function", "Closing tab: " + tabInfo.name);
1207      $("#internal" + extra + "-" + id).html("");
1208      $("#menu-" + id + " a").removeClass("active");
1209      $("#menu-" + id + " a")
1210        .children()
1211        .removeClass("tabLoaded");
1212      $("#internal" + extra + "-" + id).removeClass("loaded show");
1213      $("#menu-" + id).removeClass("active");
1214      setTabInfo(id, "loaded", false);
1215      setTabInfo(id, "active", false);
1216      loadNextTab();
1217      break;
1218    case 1:
1219    case "1":
1220    case "iframe":
1221      organizrConsole("Organizr Function", "Closing tab: " + tabInfo.name);
1222      $("#menu-" + id + " a").removeClass("active");
1223      $("#menu-" + id + " a")
1224        .children()
1225        .removeClass("tabLoaded");
1226      $("#container" + extra + "-" + id)
1227        .removeClass("loaded show")
1228        .addClass("hidden");
1229      $("#frame" + extra + "-" + id).remove();
1230      setTabInfo(id, "loaded", false);
1231      setTabInfo(id, "active", false);
1232      loadNextTab();
1233      break;
1234    case 2:
1235    case 3:
1236    case "2":
1237    case "3":
1238    case "_blank":
1239    case "popout":
1240      break;
1241    default:
1242      organizrConsole("Tab Function", "No Available Tab to open", "error");
1243  }
1244}
1245function openInNewBrowserTab() {
1246  let id = $("body").attr("data-active-tab-id");
1247  let tabInfo = findTab(id);
1248  if (!tabInfo) {
1249    organizrConsole(
1250      "Open In New Browser Tab Function",
1251      "No Tab Info Found... Id: " + id,
1252      "error"
1253    );
1254    return false;
1255  }
1256  let url = tabInfo.access_url;
1257  window.open(url, "_blank");
1258}
1259function findTab(query, term = "id") {
1260  let tabInfo = activeInfo.tabs.filter((tab) => tab[term] == query);
1261  return tabInfo.length >= 1 ? tabInfo[0] : false;
1262}
1263function tabActions(event, id, redirectURL = "") {
1264  if (event.which == 3) {
1265    return false;
1266  }
1267  let tabInfo = findTab(id);
1268  if (!tabInfo) {
1269    organizrConsole(
1270      "Tab Action Function",
1271      "No Tab Info Found... Id: " + id,
1272      "error"
1273    );
1274    return false;
1275  }
1276  let type = tabInfo.type;
1277  let name = tabInfo.name;
1278  if ((event.ctrlKey && !event.shiftKey && !event.altKey) || event.which == 2) {
1279    popTab(id);
1280  } else if (event.altKey && !event.shiftKey && !event.ctrlKey) {
1281    closeTab(id);
1282  } else if (event.shiftKey && !event.ctrlKey && !event.altKey) {
1283    reloadTab(id);
1284  } else if (event.ctrlKey && event.shiftKey && !event.altKey) {
1285    organizrConsole("Tab Function", "Action not defined yet", "info");
1286  } else if (event.ctrlKey && event.altKey && !event.shiftKey) {
1287    organizrConsole("Tab Function", "Action not defined yet", "info");
1288    switchTab(id, true);
1289  } else if (event.shiftKey && event.altKey && !event.ctrlKey) {
1290    organizrConsole("Tab Function", "Action not defined yet", "info");
1291  } else {
1292    switchTab(id);
1293    if (type !== 2) {
1294      $(".splash-screen").removeClass("in").addClass("hidden");
1295    }
1296    if (redirectURL) {
1297      $(".close-popup").trigger("click");
1298      $("#frame-" + id).attr("src", redirectURL);
1299    }
1300  }
1301}
1302function reverseObject(object) {
1303  var newObject = {};
1304  var keys = [];
1305  for (var key in object) {
1306    keys.push(key);
1307  }
1308  for (var i = keys.length - 1; i >= 0; i--) {
1309    var value = object[keys[i]];
1310    newObject[keys[i]] = value;
1311  }
1312  return newObject;
1313}
1314function hasValue(test) {
1315  if (Array.isArray(test) && test[0] !== "") {
1316    return true;
1317  } else {
1318    return false;
1319  }
1320}
1321function arrayContains(needle, arrhaystack) {
1322  return arrhaystack.indexOf(needle) > -1;
1323}
1324/* END NORMAL FUNCTIONS */
1325/* BUILD FUNCTIONS */
1326/* END BUILD FUNCTIONS */
1327/* ORGANIZR API FUNCTIONS */
1328function selectOptions(options, active) {
1329  var selectOptions = "";
1330  $.each(options, function (i, v) {
1331    activeTest = active.split(",");
1332    if (activeTest.length > 1) {
1333      var selected = arrayContains(v.value, activeTest) ? "selected" : "";
1334    } else {
1335      var selected = active.toString() == v.value ? "selected" : "";
1336    }
1337    var disabled = v.disabled ? " disabled" : "";
1338    selectOptions +=
1339      "<option " +
1340      selected +
1341      disabled +
1342      ' value="' +
1343      v.value +
1344      '">' +
1345      v.name +
1346      "</option>";
1347  });
1348  return selectOptions;
1349}
1350function accordionOptions(options, parentID) {
1351  var accordionOptions = "";
1352  $.each(options, function (i, v) {
1353    var id = v.id;
1354    var extraClass = v.class ? " " + v.class : "";
1355    var header = v.header ? " " + v.header : "";
1356    if (typeof v.body == "object") {
1357      if (typeof v.body.length == "undefined") {
1358        var body = buildFormItem(v.body);
1359      } else {
1360        var body = "";
1361        $.each(v.body, function (int, val) {
1362          body += buildFormItem(val);
1363        });
1364      }
1365    } else {
1366      var body = v.body;
1367    }
1368    accordionOptions +=
1369      `
1370		<div class="panel">
1371			<div class="panel-heading" id="` +
1372      id +
1373      `-heading" role="tab">
1374				<a class="panel-title collapsed" data-toggle="collapse" href="#` +
1375      id +
1376      `-collapse" data-parent="#` +
1377      parentID +
1378      `" aria-expanded="false" aria-controls="` +
1379      id +
1380      `-collapse"><span lang="en">` +
1381      header +
1382      `</span></a>
1383			</div>
1384			<div class="panel-collapse collapse" id="` +
1385      id +
1386      `-collapse" aria-labelledby="` +
1387      id +
1388      `-heading" role="tabpanel" aria-expanded="false" style="height: 0px;">
1389				<div class="panel-body">` +
1390      body +
1391      `</div>
1392			</div>
1393		</div>
1394		`;
1395  });
1396  return accordionOptions;
1397}
1398function buildAccordion(array, open = false) {
1399  var items = "";
1400  var mainId = createRandomString(10);
1401  $.each(array, function (i, v) {
1402    var collapse = open && i == 0 ? "collapse in" : "collapse";
1403    var collapsed = open && i == 0 ? "" : "collapsed";
1404    var id = mainId + "-" + i;
1405    items +=
1406      `
1407        <div class="panel">
1408            <div class="panel-heading bg-org" id="` +
1409      id +
1410      `-heading" role="tab"> <a class="panel-title ` +
1411      collapsed +
1412      `" data-toggle="collapse" href="#` +
1413      id +
1414      `-collapse" data-parent="#` +
1415      mainId +
1416      `" aria-expanded="false" aria-controls="` +
1417      id +
1418      `-collapse"> <span lang="en">` +
1419      v.title +
1420      `</span> </a> </div>
1421            <div class="panel-collapse ` +
1422      collapse +
1423      `" id="` +
1424      id +
1425      `-collapse" aria-labelledby="` +
1426      id +
1427      `-heading" role="tabpanel">
1428                <div class="panel-body" lang="en"> ` +
1429      v.body +
1430      ` </div>
1431            </div>
1432        </div>
1433        `;
1434  });
1435  return (
1436    '<div class="panel-group" id="' +
1437    mainId +
1438    '" aria-multiselectable="true" role="tablist">' +
1439    items +
1440    "</div>"
1441  );
1442}
1443function buildFormItem(item) {
1444  var placeholder = item.placeholder
1445    ? ' placeholder="' + item.placeholder + '"'
1446    : "";
1447  var id = item.id ? ' id="' + item.id + '"' : "";
1448  var type = item.type ? ' data-type="' + item.type + '"' : "";
1449  var label = item.label ? ' data-label="' + item.label + '"' : "";
1450  var value = item.value ? ' value="' + item.value + '"' : "";
1451  var textarea = item.value ? item.value : "";
1452  var name = item.name ? ' name="' + item.name + '"' : "";
1453  var extraClass = item.class ? " " + item.class : "";
1454  var icon = item.icon ? " " + item.icon : "";
1455  var text = item.text ? " " + item.text : "";
1456  var attr = item.attr ? " " + item.attr : "";
1457  var disabled = item.disabled ? " disabled" : "";
1458  var href = item.href ? ' href="' + item.href + '"' : "";
1459  var pwd1 = createRandomString(6);
1460  var pwd2 = createRandomString(6);
1461  var pwd3 = createRandomString(6);
1462  var helpInfo = item.help
1463    ? '<div class="collapse" id="help-info-' +
1464      item.name +
1465      '"><blockquote lang="en">' +
1466      item.help +
1467      "</blockquote></div>"
1468    : "";
1469  var smallLabel = item.smallLabel
1470    ? '<label><span lang="en">' + item.smallLabel + "</span></label>" + helpInfo
1471    : "" + helpInfo;
1472  var pwgMgr =
1473    `
1474	<input name="disable-pwd-mgr-` +
1475    pwd1 +
1476    `" type="password" id="disable-pwd-mgr-` +
1477    pwd1 +
1478    `" style="display: none;" value="disable-pwd-mgr-` +
1479    pwd1 +
1480    `" />
1481	<input name="disable-pwd-mgr-` +
1482    pwd2 +
1483    `" type="password" id="disable-pwd-mgr-` +
1484    pwd2 +
1485    `" style="display: none;" value="disable-pwd-mgr-` +
1486    pwd2 +
1487    `" />
1488	<input name="disable-pwd-mgr-` +
1489    pwd3 +
1490    `" type="password" id="disable-pwd-mgr-` +
1491    pwd3 +
1492    `" style="display: none;" value="disable-pwd-mgr-` +
1493    pwd3 +
1494    `" />
1495	`;
1496  //+tof(item.value,'c')+`
1497  switch (item.type) {
1498    case "select-input":
1499      return (
1500        smallLabel +
1501        '<input list="' +
1502        item.name +
1503        'Options" data-changed="false" lang="en" type="text" class="form-control' +
1504        extraClass +
1505        '"' +
1506        placeholder +
1507        value +
1508        id +
1509        name +
1510        disabled +
1511        type +
1512        label +
1513        attr +
1514        ' autocomplete="new-password" /><datalist id="' +
1515        item.name +
1516        'Options">' +
1517        selectOptions(item.options, item.value) +
1518        "</datalist>"
1519      );
1520    case "input":
1521    case "text":
1522      return (
1523        smallLabel +
1524        '<input data-changed="false" lang="en" type="text" class="form-control' +
1525        extraClass +
1526        '"' +
1527        placeholder +
1528        value +
1529        id +
1530        name +
1531        disabled +
1532        type +
1533        label +
1534        attr +
1535        ' autocomplete="new-password" />'
1536      );
1537    case "number":
1538      return (
1539        smallLabel +
1540        '<input data-changed="false" lang="en" type="number" class="form-control' +
1541        extraClass +
1542        '"' +
1543        placeholder +
1544        value +
1545        id +
1546        name +
1547        disabled +
1548        type +
1549        label +
1550        attr +
1551        ' autocomplete="new-password" />'
1552      );
1553    case "textbox":
1554      return (
1555        smallLabel +
1556        '<textarea data-changed="false" class="form-control' +
1557        extraClass +
1558        '"' +
1559        placeholder +
1560        id +
1561        name +
1562        disabled +
1563        type +
1564        label +
1565        attr +
1566        ' autocomplete="new-password">' +
1567        textarea +
1568        "</textarea>"
1569      );
1570    case "password":
1571      return (
1572        smallLabel +
1573        pwgMgr +
1574        '<input data-changed="false" lang="en" type="password" class="form-control' +
1575        extraClass +
1576        '"' +
1577        placeholder +
1578        value +
1579        id +
1580        name +
1581        disabled +
1582        type +
1583        label +
1584        attr +
1585        ' autocomplete="new-password" />'
1586      );
1587    case "password-alt":
1588      return (
1589        smallLabel +
1590        '<div class="input-group">' +
1591        pwgMgr +
1592        '<input data-changed="false" lang="en" type="password" class="password-alt form-control' +
1593        extraClass +
1594        '"' +
1595        placeholder +
1596        value +
1597        id +
1598        name +
1599        disabled +
1600        type +
1601        label +
1602        attr +
1603        ' autocomplete="new-password" /><span class="input-group-btn"> <button class="btn btn-default showPassword" type="button"><i class="fa fa-eye passwordToggle"></i></button></span></div>'
1604      );
1605    case "password-alt-copy":
1606      return (
1607        smallLabel +
1608        '<div class="input-group">' +
1609        pwgMgr +
1610        '<input data-changed="false" lang="en" type="password" class="password-alt form-control' +
1611        extraClass +
1612        '"' +
1613        placeholder +
1614        value +
1615        id +
1616        name +
1617        disabled +
1618        type +
1619        label +
1620        attr +
1621        ' autocomplete="new-password" /><span class="input-group-btn"> <button class="btn btn-primary clipboard" type="button" data-clipboard-text="' +
1622        item.value +
1623        '"><i class="fa icon-docs"></i></button></span><span class="input-group-btn"> <button class="btn btn-inverse showPassword" type="button"><i class="fa fa-eye passwordToggle"></i></button></span></div>'
1624      );
1625    case "hidden":
1626      return (
1627        '<input data-changed="false" lang="en" type="hidden" class="form-control' +
1628        extraClass +
1629        '"' +
1630        placeholder +
1631        value +
1632        id +
1633        name +
1634        disabled +
1635        type +
1636        label +
1637        attr +
1638        " />"
1639      );
1640    case "select":
1641      return (
1642        smallLabel +
1643        '<select data-changed="false" class="form-control' +
1644        extraClass +
1645        '"' +
1646        placeholder +
1647        value +
1648        id +
1649        name +
1650        disabled +
1651        type +
1652        label +
1653        attr +
1654        ">" +
1655        selectOptions(item.options, item.value) +
1656        "</select>"
1657      );
1658    case "select2":
1659      var select2ID = item.id ? "#" + item.id : "." + item.name;
1660      let settings = item.settings ? item.settings : "{}";
1661      return (
1662        smallLabel +
1663        '<select class="m-b-10 ' +
1664        extraClass +
1665        '"' +
1666        placeholder +
1667        value +
1668        id +
1669        name +
1670        disabled +
1671        type +
1672        label +
1673        attr +
1674        ' multiple="multiple" data-placeholder="">' +
1675        selectOptions(item.options, item.value) +
1676        '</select><script>$("' +
1677        select2ID +
1678        '").select2(' +
1679        settings +
1680        ').on("select2:unselecting", function() { $(this).data("unselecting", true); }).on("select2:opening", function(e) { if ($(this).data("unselecting")) { $(this).removeData("unselecting");  e.preventDefault(); } });</script>'
1681      );
1682    case "switch":
1683    case "checkbox":
1684      return (
1685        smallLabel +
1686        '<input data-changed="false" type="checkbox" class="js-switch' +
1687        extraClass +
1688        '" data-size="medium" data-color="#99d683" data-secondary-color="#f96262"' +
1689        name +
1690        value +
1691        tof(item.value, "c") +
1692        id +
1693        disabled +
1694        type +
1695        label +
1696        attr +
1697        ' /><input data-changed="false" type="hidden"' +
1698        name +
1699        'value="false">'
1700      );
1701    case "button":
1702      return (
1703        smallLabel +
1704        '<button class="btn btn-sm btn-success btn-rounded waves-effect waves-light b-none' +
1705        extraClass +
1706        '" ' +
1707        href +
1708        attr +
1709        ' type="button"><span class="btn-label"><i class="' +
1710        icon +
1711        '"></i></span><span lang="en">' +
1712        text +
1713        "</span></button>"
1714      );
1715    case "blank":
1716      return "";
1717    case "accordion":
1718      return (
1719        '<div class="panel-group' +
1720        extraClass +
1721        '"' +
1722        placeholder +
1723        value +
1724        id +
1725        name +
1726        disabled +
1727        type +
1728        label +
1729        attr +
1730        '  aria-multiselectable="true" role="tablist">' +
1731        accordionOptions(item.options, item.id) +
1732        "</div>"
1733      );
1734    case "html":
1735      return item.html;
1736    case "arrayMultiple":
1737      return '<span class="text-danger">BuildFormItem Class not setup...';
1738    case "cron":
1739      return `${smallLabel}<div class="input-group"><input data-changed="false" class="form-control ${extraClass}" ${placeholder} ${value} ${id} ${name} ${disabled} ${type} ${label} ${attr} autocomplete="new-password"><span class="input-group-btn"><button class="btn btn-info test-cron" type="button"><i class="fa fa-flask"></i></button></span></div>`;
1740    case "folder":
1741      return `${smallLabel}<div class="input-group"><input data-changed="false" class="form-control ${extraClass}" ${placeholder} ${value} ${id} ${name} ${disabled} ${type} ${label} ${attr} autocomplete="new-password"><span class="input-group-btn"><button class="btn btn-info test-folder" type="button"><i class="fa fa-flask"></i></button></span></div>`;
1742    default:
1743      return '<span class="text-danger">BuildFormItem Class not setup...';
1744  }
1745}
1746function checkCronFile() {
1747  $(".cron-results-container").removeClass("hidden");
1748  organizrAPI2("GET", "api/v2/test/cron")
1749    .success(function (data) {
1750      try {
1751        $(".cron-results").text("Cron file is setup correctly");
1752      } catch (e) {
1753        $(".cron-results").text("Unknown error");
1754        organizrCatchError(e, data);
1755      }
1756    })
1757    .fail(function (xhr) {
1758      $(".cron-results").text("Cron file is not setup or is setup incorrectly");
1759      OrganizrApiError(xhr);
1760    });
1761}
1762function buildPluginsItem(array, type = "enabled") {
1763  var activePlugins = "";
1764  var inactivePlugins = "";
1765  $.each(array, function (i, v) {
1766    var settingsPage =
1767      v.settings == true && type == "enabled"
1768        ? `
1769		<!-- Plugin Settings Page -->
1770		<form id="` +
1771          v.idPrefix +
1772          `-settings-page" class="mfp-hide white-popup mfp-with-anim addFormTick col-md-10 col-md-offset-1" autocomplete="off">
1773            <div class="panel bg-org panel-info">
1774                <div class="panel-heading">
1775                    <span lang="en">` +
1776          v.name +
1777          ` Settings</span>
1778                    <button type="button" class="btn bg-org btn-circle close-popup pull-right"><i class="fa fa-times"></i> </button>
1779                    <button id="` +
1780          v.idPrefix +
1781          `-settings-page-save" onclick="submitSettingsForm('` +
1782          v.idPrefix +
1783          `-settings-page')" class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right hidden animated loop-animation rubberBand m-r-20" type="button"><span class="btn-label"><i class="fa fa-save"></i></span><span lang="en">Save</span></button>
1784                </div>
1785                <div class="panel-wrapper collapse in" aria-expanded="true">
1786                    <div class="bg-org">
1787                        <fieldset id="` +
1788          v.idPrefix +
1789          `-settings-items" style="border:0;" class=""><h2>Loading...</h2></fieldset>
1790                    </div>
1791                    <div class="clearfix"></div>
1792                </div>
1793            </div>
1794		</form>
1795		`
1796        : "";
1797    var href =
1798      v.settings == true
1799        ? "#" + v.idPrefix + "-settings-page"
1800        : "javascript:void(0);";
1801    if (v.enabled == true) {
1802      var activeToggle =
1803        `<li><a class="btn default btn-outline disablePlugin" href="javascript:void(0);" data-plugin-name="` +
1804        v.name +
1805        `" data-config-prefix="` +
1806        v.configPrefix +
1807        `" data-config-name="` +
1808        v.configPrefix +
1809        `-enabled"><i class="ti-power-off fa-2x"></i></a></li>`;
1810      var settings =
1811        `<li><a class="btn default btn-outline popup-with-form" href="` +
1812        href +
1813        `" data-effect="mfp-3d-unfold"data-plugin-name="` +
1814        v.name +
1815        `" id="` +
1816        v.idPrefix +
1817        `-settings-button" data-config-prefix="` +
1818        v.configPrefix +
1819        `" data-api="${v.api}" data-settings="${v.settings}" data-bind="${v.bind}"><i class="ti-panel fa-2x"></i></a></li>`;
1820    } else {
1821      var activeToggle =
1822        `<li><a class="btn default btn-outline enablePlugin" href="javascript:void(0);" data-plugin-name="` +
1823        v.name +
1824        `" data-config-prefix="` +
1825        v.configPrefix +
1826        `" data-config-name="` +
1827        v.configPrefix +
1828        `-enabled"><i class="ti-plug fa-2x"></i></a></li>`;
1829      var settings = "";
1830    }
1831    var plugin =
1832      `
1833		<div class="col-lg-2 col-md-2 col-sm-6 col-xs-6 m-b-10">
1834			<div class="white-box m-0">
1835				<div class="el-card-item p-0">
1836					<div class="el-card-avatar el-overlay-1 m-0"> <img class="lazyload" data-src="` +
1837      v.image +
1838      `">
1839						<div class="el-overlay">
1840							<ul class="el-info">
1841								${settings} ${activeToggle}
1842							</ul>
1843						</div>
1844					</div>
1845					<div class="el-card-content">
1846						<h3 class="box-title elip">` +
1847      v.name +
1848      `</h3>
1849						<small class="elip text-uppercase p-b-10">` +
1850      v.category +
1851      `</small>
1852					</div>
1853				</div>
1854			</div>
1855		</div>
1856		`;
1857    if (v.enabled == true) {
1858      activePlugins += plugin + settingsPage;
1859    } else {
1860      inactivePlugins += plugin + settingsPage;
1861    }
1862  });
1863  activePlugins =
1864    activePlugins.length !== 0
1865      ? activePlugins
1866      : '<h2 class="text-center" lang="en">Nothing Active</h2>';
1867  inactivePlugins =
1868    inactivePlugins.length !== 0
1869      ? inactivePlugins
1870      : '<h2 class="text-center" lang="en">Everything Active</h2>';
1871  return type === "enabled"
1872    ? `
1873	<div class="panel bg-org panel-info">
1874		<div class="panel-heading">
1875			<span lang="en">Active Plugins</span>
1876		</div>
1877		<div class="panel-wrapper collapse in" aria-expanded="true">
1878			<div class="panel-body bg-org">
1879				<div class="row el-element-overlay m-b-40">` +
1880        activePlugins +
1881        `</div>
1882			</div>
1883		</div>
1884	</div>
1885	<div class="clearfix"></div>`
1886    : `	
1887	<div class="panel bg-org panel-info">
1888		<div class="panel-heading">
1889			<span lang="en">Inactive Plugins</span>
1890		</div>
1891		<div class="panel-wrapper collapse in" aria-expanded="true">
1892			<div class="panel-body bg-org">
1893				<div class="row el-element-overlay m-b-40">` +
1894        inactivePlugins +
1895        `</div>
1896			</div>
1897		</div>
1898	</div>`;
1899}
1900function buildPluginsItemOld(array) {
1901  var activePlugins = "";
1902  var inactivePlugins = "";
1903  $.each(array, function (i, v) {
1904    var settingsPage =
1905      v.settings == true
1906        ? `
1907		<!-- Plugin Settings Page -->
1908		<form id="` +
1909          v.idPrefix +
1910          `-settings-page" class="mfp-hide white-popup mfp-with-anim addFormTick col-md-10 col-md-offset-1" autocomplete="off">
1911            <div class="panel bg-org panel-info">
1912                <div class="panel-heading">
1913                    <span lang="en">` +
1914          v.name +
1915          ` Settings</span>
1916                    <button type="button" class="btn bg-org btn-circle close-popup pull-right"><i class="fa fa-times"></i> </button>
1917                    <button id="` +
1918          v.idPrefix +
1919          `-settings-page-save" onclick="submitSettingsForm('` +
1920          v.idPrefix +
1921          `-settings-page')" class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right hidden animated loop-animation rubberBand m-r-20" type="button"><span class="btn-label"><i class="fa fa-save"></i></span><span lang="en">Save</span></button>
1922                </div>
1923                <div class="panel-wrapper collapse in" aria-expanded="true">
1924                    <div class="bg-org">
1925                        <fieldset id="` +
1926          v.idPrefix +
1927          `-settings-items" style="border:0;" class=""><h2>Loading...</h2></fieldset>
1928                    </div>
1929                    <div class="clearfix"></div>
1930                </div>
1931            </div>
1932		</form>
1933		`
1934        : "";
1935    var href =
1936      v.settings == true
1937        ? "#" + v.idPrefix + "-settings-page"
1938        : "javascript:void(0);";
1939    if (v.enabled == true) {
1940      var activeToggle =
1941        `<li><a class="btn default btn-outline disablePlugin" href="javascript:void(0);" data-plugin-name="` +
1942        v.name +
1943        `" data-config-prefix="` +
1944        v.configPrefix +
1945        `" data-config-name="` +
1946        v.configPrefix +
1947        `-enabled"><i class="ti-power-off fa-2x"></i></a></li>`;
1948      var settings =
1949        `<li><a class="btn default btn-outline popup-with-form" href="` +
1950        href +
1951        `" data-effect="mfp-3d-unfold"data-plugin-name="` +
1952        v.name +
1953        `" id="` +
1954        v.idPrefix +
1955        `-settings-button" data-config-prefix="` +
1956        v.configPrefix +
1957        `" data-api="${v.api}" data-settings="${v.settings}" data-bind="${v.bind}"><i class="ti-panel fa-2x"></i></a></li>`;
1958    } else {
1959      var activeToggle =
1960        `<li><a class="btn default btn-outline enablePlugin" href="javascript:void(0);" data-plugin-name="` +
1961        v.name +
1962        `" data-config-prefix="` +
1963        v.configPrefix +
1964        `" data-config-name="` +
1965        v.configPrefix +
1966        `-enabled"><i class="ti-plug fa-2x"></i></a></li>`;
1967      var settings = "";
1968    }
1969    var plugin =
1970      `
1971		<div class="col-lg-2 col-md-2 col-sm-4 col-xs-4">
1972			<div class="white-box m-0">
1973				<div class="el-card-item p-0">
1974					<div class="el-card-avatar el-overlay-1 m-0"> <img class="lazyload" data-src="` +
1975      v.image +
1976      `">
1977						<div class="el-overlay">
1978							<ul class="el-info">
1979								${settings} ${activeToggle}
1980							</ul>
1981						</div>
1982					</div>
1983					<div class="el-card-content">
1984						<h3 class="box-title elip">` +
1985      v.name +
1986      `</h3>
1987						<small class="elip text-uppercase p-b-10">` +
1988      v.category +
1989      `</small>
1990					</div>
1991				</div>
1992			</div>
1993		</div>
1994		`;
1995    if (v.enabled == true) {
1996      activePlugins += plugin + settingsPage;
1997    } else {
1998      inactivePlugins += plugin + settingsPage;
1999    }
2000  });
2001  activePlugins =
2002    activePlugins.length !== 0
2003      ? activePlugins
2004      : '<h2 class="text-center" lang="en">Nothing Active</h2>';
2005  inactivePlugins =
2006    inactivePlugins.length !== 0
2007      ? inactivePlugins
2008      : '<h2 class="text-center" lang="en">Everything Active</h2>';
2009  var panes =
2010    `
2011	<select class="form-control settings-dropdown-box plugin-menu w-100 visible-xs">
2012		<option value="#settings-plugins-active-anchor" lang="en">Active</option>
2013		<option value="#settings-plugins-inactive-anchor" lang="en">Inactive</option>
2014		<option value="#settings-plugins-marketplace-anchor" lang="en">Marketplace</option>
2015	</select>
2016	<ul class="nav customtab2 nav-tabs nav-non-mobile hidden-xs" data-dropdown="plugin-menu" role="tablist">
2017		<li onclick="changeSettingsMenu('Settings::Plugins::Active')" role="presentation" class="active"><a id="settings-plugins-active-anchor" href="#settings-plugins-active" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="false"><span class="visible-xs"><i class="ti-file"></i></span><span class="hidden-xs" lang="en">Active</span></a>
2018		</li>
2019		<li onclick="changeSettingsMenu('Settings::Plugins::Inactive')" role="presentation" class=""><a id="settings-plugins-inactive-anchor" href="#settings-plugins-inactive" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="false"><span class="visible-xs"><i class="ti-zip"></i></span><span class="hidden-xs" lang="en">Inactive</span></a>
2020		</li>
2021		<li onclick="changeSettingsMenu('Settings::Plugins::Marketplace');loadMarketplace('plugins');" role="presentation" class=""><a id="settings-plugins-marketplace-anchor" href="#settings-plugins-marketplace" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="false"><span class="visible-xs"><i class="ti-shopping-cart-full"></i></span><span class="hidden-xs" lang="en">Marketplace</span></a>
2022		</li>
2023	</ul>
2024	<!-- Tab panes -->
2025	<div class="tab-content">
2026		<div role="tabpanel" class="tab-pane fade in active" id="settings-plugins-active">
2027			<div class="panel bg-org panel-info">
2028				<div class="panel-heading">
2029					<span lang="en">Active Plugins</span>
2030				</div>
2031				<div class="panel-wrapper collapse in" aria-expanded="true">
2032					<div class="panel-body bg-org">
2033						<div class="row el-element-overlay m-b-40">` +
2034    activePlugins +
2035    `</div>
2036					</div>
2037				</div>
2038			</div>
2039			<div class="clearfix"></div>
2040		</div>
2041		<div role="tabpanel" class="tab-pane fade" id="settings-plugins-inactive">
2042			<div class="panel bg-org panel-info">
2043				<div class="panel-heading">
2044					<span lang="en">Inactive Plugins</span>
2045				</div>
2046				<div class="panel-wrapper collapse in" aria-expanded="true">
2047					<div class="panel-body bg-org">
2048						<div class="row el-element-overlay m-b-40">` +
2049    inactivePlugins +
2050    `</div>
2051					</div>
2052				</div>
2053			</div>
2054		</div>
2055		<div role="tabpanel" class="tab-pane fade" id="settings-plugins-marketplace">
2056			<div class="panel bg-org panel-info">
2057				<div class="panel-heading">
2058					<span lang="en">Plugin Marketplace</span>
2059				</div>
2060				<div class="panel-wrapper collapse in" aria-expanded="true">
2061					<div class="table-responsive">
2062                        <table class="table table-hover manage-u-table">
2063                            <thead>
2064                                <tr>
2065                                    <th width="70" class="text-center" lang="en">PLUGIN</th>
2066                                    <th></th>
2067                                    <th lang="en">CATEGORY</th>
2068                                    <th lang="en">STATUS</th>
2069                                    <th lang="en" style="text-align:center">INFO</th>
2070                                    <th lang="en" style="text-align:center">INSTALL</th>
2071                                    <th lang="en" style="text-align:center">DELETE</th>
2072                                </tr>
2073                            </thead>
2074                            <tbody id="managePluginTable"></tbody>
2075                        </table>
2076                    </div>
2077				</div>
2078			</div>
2079		</div>
2080	</div>
2081
2082	`;
2083
2084  return panes;
2085}
2086function loadMarketplace(type) {
2087  marketplaceJSON(type)
2088    .success(function (data) {
2089      try {
2090        var response = JSON.parse(data);
2091      } catch (e) {
2092        organizrCatchError(e, data);
2093      }
2094      switch (type) {
2095        case "plugins":
2096          loadMarketplacePluginsItems(response);
2097          break;
2098        case "themes":
2099          loadMarketplaceThemesItems(response);
2100          break;
2101        default:
2102      }
2103    })
2104    .fail(function (xhr) {
2105      OrganizrApiError(xhr);
2106    });
2107}
2108function loadThemeMarketplace() {
2109  $("#manageThemeTable").html(
2110    '<td class="text-center" colspan="12"><i class="fa fa-spin fa-spinner"></i></td>'
2111  );
2112  organizrAPI2("GET", "api/v2/themes/marketplace")
2113    .success(function (data) {
2114      try {
2115        let response = data.response;
2116        loadMarketplaceThemesItems(response.data);
2117      } catch (e) {
2118        organizrCatchError(e, data);
2119      }
2120    })
2121    .fail(function (xhr) {
2122      OrganizrApiError(xhr, "loadThemeMarketplace Failed");
2123    });
2124}
2125function loadPluginMarketplace() {
2126  $("#managePluginTable").html(
2127    '<td class="text-center" colspan="12"><i class="fa fa-spin fa-spinner"></i></td>'
2128  );
2129  organizrAPI2("GET", "api/v2/plugins/marketplace")
2130    .success(function (data) {
2131      try {
2132        let response = data.response;
2133        loadMarketplacePluginsItems(response.data);
2134      } catch (e) {
2135        organizrCatchError(e, data);
2136      }
2137    })
2138    .fail(function (xhr) {
2139      OrganizrApiError(xhr, "loadPluginMarketplace Failed");
2140    });
2141}
2142function loadMarketplacePluginsItems(plugins) {
2143  var pluginList = "";
2144  $.each(plugins, function (i, v) {
2145    if (v.icon == null || v.icon == "") {
2146      v.icon = "test.png";
2147    }
2148    var installButton =
2149      v.status == "Update Available" ? "fa fa-download" : "fa fa-plus";
2150    var removeButton = v.status == "Not Installed" ? "disabled" : "";
2151    v.name = i;
2152    pluginList +=
2153      `
2154            <tr class="pluginManagement" data-name="${i}" data-version="${v.version}" data-repo="${v.repo}">
2155                <td class="text-center el-element-overlay">
2156                    <div class="el-card-item p-0">
2157                        <div class="el-card-avatar el-overlay-1 m-0">
2158                            <img alt="user-img" src="` +
2159      v.icon +
2160      `" width="45">
2161                        </div>
2162                    </div>
2163                </td>
2164                <td>` +
2165      i +
2166      `
2167                    <br><span class="text-muted">` +
2168      v.version +
2169      `</span>
2170                    <br><span class="text-muted">` +
2171      v.author +
2172      `</span>
2173                </td>
2174                <td>` +
2175      v.category +
2176      `</td>
2177                <td lang="en">` +
2178      v.status +
2179      `</td>
2180                <td style="text-align:center"><button type="button" onclick='aboutPlugin(` +
2181      JSON.stringify(v) +
2182      `);' class="btn btn-success btn-outline btn-circle btn-lg popup-with-form" href="#about-plugin-form" data-effect="mfp-3d-unfold"><i class="fa fa-info"></i></button></td>
2183                <td style="text-align:center"><button type="button" onclick='installPlugin("` +
2184      cleanClass(i) +
2185      `");' class="btn btn-info btn-outline btn-circle btn-lg"><i class="` +
2186      installButton +
2187      `"></i></button></td>
2188                <td style="text-align:center"><button type="button" onclick='removePlugin("` +
2189      cleanClass(i) +
2190      `");' class="btn btn-danger btn-outline btn-circle btn-lg" ` +
2191      removeButton +
2192      `><i class="fa fa-tras
2192h"></i></button></td>
2193            </tr>
2194        `;
2195  });
2196  $("#managePluginTable").html(pluginList);
2197}
2198
2199function getRepoUsernameAndRepoName(repo) {
2200  let parts = repo.split("/");
2201  if (parts.length) {
2202    return parts[parts.length - 2] + "/" + parts[parts.length - 1];
2203  } else {
2204    return repo;
2205  }
2206}
2207
2208function loadMarketplaceThemesItems(themes) {
2209  var themeList = "";
2210  $.each(themes, function (i, v) {
2211    if (v.icon == null || v.icon == "") {
2212      v.icon = "test.png";
2213    }
2214    //v.status = themeStatus(i,v.version);
2215    var installButton =
2216      v.status == "Update Available" ? "fa fa-download" : "fa fa-plus";
2217    var removeButton = v.status == "Not Installed" ? "disabled" : "";
2218    let category =
2219      v.repo == "https://github.com/Organizr/Organizr-Themes"
2220        ? "Official"
2221        : "3rd Party";
2222    let categoryTooltip = getRepoUsernameAndRepoName(v.repo);
2223    v.name = i;
2224    let cleanName = i.replace(/_/gi, " ");
2225    themeList += `
2226            <tr class="themeManagement" data-name="${i}" data-version="${
2227      v.version
2228    }">
2229                <td class="text-center el-element-overlay">
2230                    <div class="el-card-item p-0">
2231                        <div class="el-card-avatar el-overlay-1 m-0">
2232                            <img alt="user-img" src="${v.icon}" width="45">
2233                        </div>
2234                    </div>
2235                </td>
2236                <td>${cleanName}
2237                    <br><span class="text-muted">${v.version}</span>
2238                    <br><span class="text-muted">${v.author}</span>
2239                </td>
2240                <td><span data-toggle="tooltip" title="${categoryTooltip}" data-placement="bottom">${category}</span></td>
2241                <td lang="en">${v.status}</td>
2242                <td style="text-align:center"><button type="button" onclick='aboutTheme(${JSON.stringify(
2243                  v
2244                )});' class="btn btn-success btn-outline btn-circle btn-lg popup-with-form" href="#about-theme-form" data-effect="mfp-3d-unfold"><i class="fa fa-info"></i></button></td>
2245                <td style="text-align:center"><button type="button" onclick='installTheme("${cleanClass(
2246                  i
2247                )}");themeAnalytics("${
2248      v.name
2249    }");' class="btn btn-info btn-outline btn-circle btn-lg"><i class="${installButton}"></i></button></td>
2250                <td style="text-align:center"><button type="button" onclick='removeTheme("${cleanClass(
2251                  i
2252                )}");' class="btn btn-danger btn-outline btn-circle btn-lg" ${removeButton}><i class="fa fa-trash"></i></button></td>
2253            </tr>
2254        `;
2255  });
2256  $("#manageThemeTable").html(themeList);
2257}
2258function aboutPluginImages(images) {
2259  var imageList = "";
2260  if (Object.keys(images).length !== 0) {
2261    var imageCount = 0;
2262    $.each(images, function (i, v) {
2263      imageCount++;
2264      var active = imageCount == 1 ? "active" : "";
2265      imageList +=
2266        `
2267            <div class="` +
2268        active +
2269        ` item">
2270                <div class="overlaybg"><img src="` +
2271        v +
2272        `" /></div>
2273                <div class="news-content"><span class="label label-info label-rounded">` +
2274        i +
2275        `</span></div>
2276            </div>
2277            `;
2278    });
2279  } else {
2280    imageList += `
2281            <div class="active item">
2282                <div class="overlaybg"><img src="https://via.placeholder.com/350x150" /></div>
2283            </div>
2284        `;
2285  }
2286  return imageList;
2287}
2288function aboutPluginFiles(fileList) {
2289  var files = [];
2290  $.each(fileList, function (i, v) {
2291    var splitFiles = v.split("|");
2292    var formattedSplit = [];
2293    $.each(splitFiles, function (i, v) {
2294      var arrayFilePush = {
2295        text: v,
2296      };
2297      formattedSplit.push(arrayFilePush);
2298    });
2299    var arrayPush = {
2300      text: i,
2301      nodes: formattedSplit,
2302    };
2303    files.push(arrayPush);
2304  });
2305  return files;
2306}
2307function pluginFileList(fileList, folder, type) {
2308  var files = [];
2309  $.each(fileList, function (i, v) {
2310    var splitFiles = v.split("|");
2311    var formattedSplit = [];
2312    var prePath = i.length !== 1 ? i + "/" : i;
2313    $.each(splitFiles, function (i, v) {
2314      var arrayPush = {
2315        fileName: v,
2316        path: prePath,
2317        githubPath:
2318          "https://raw.githubusercontent.com/causefx/Organizr/v2-" +
2319          type +
2320          "/" +
2321          folder +
2322          prePath +
2323          v,
2324      };
2325      files.push(arrayPush);
2326    });
2327  });
2328  return files;
2329}
2330function aboutTheme(theme) {
2331  var files = aboutPluginFiles(theme.files);
2332  var imageList = aboutPluginImages(theme.images);
2333  var homepageLink =
2334    theme.website !== "" || theme.website !== null
2335      ? "onclick=\"window.open('" + theme.website + "','_blank');\""
2336      : " ";
2337
2338  var infoBox =
2339    `
2340    <div class="row">
2341        <div class="col-lg-6 col-sm-12 col-xs-12">
2342            <div class="row">
2343                <div class="col-lg-12 col-sm-12 col-xs-12">
2344                    <div class="white-box p-10" id="aboutThemeScroll">
2345                        ` +
2346    theme.description +
2347    `
2348                    </div>
2349                </div>
2350                <div class="clearfix">&nbsp;</div>
2351                <div class="col-lg-4 col-sm-4 col-xs-12">
2352                    <div class="white-box mouse">
2353                        <ul class="list-inline two-part text-center m-b-0">
2354                            <li><i class="icon-envelope-open text-info"></i></li>
2355                        </ul>
2356                    </div>
2357                </div>
2358                <div class="col-lg-4 col-sm-4 col-xs-12">
2359                    <div class="white-box mouse" ` +
2360    homepageLink +
2361    `>
2362                        <ul class="list-inline two-part text-center m-b-0">
2363                            <li><i class="icon-home text-danger"></i></li>
2364                        </ul>
2365                    </div>
2366                </div>
2367                <div class="col-lg-4 col-sm-4 col-xs-12">
2368                    <div class="white-box mouse" onclick="$('.themeFileList').toggleClass('hidden');">
2369                        <ul class="list-inline two-part text-center m-b-0">
2370                            <li><i class="icon-folder text-purple"></i></li>
2371                        </ul>
2372                    </div>
2373                </div>
2374                <div class="col-sm-12 col-xs-12 themeFileList hidden">
2375                    <div id="treeviewTheme" class=""></div>
2376                </div>
2377            </div>
2378        </div>
2379        <div class="col-lg-6 col-sm-12 col-xs-12">
2380            <div class="news-slide m-b-15">
2381                <div class="vcarousel slide">
2382                    <!-- Carousel items -->
2383                    <div class="carousel-inner">
2384                        ` +
2385    imageList +
2386    `
2387                    </div>
2388                </div>
2389            </div>
2390        </div>
2391    </div>`;
2392  $("#about-theme-title").html(
2393    theme.name + "&nbsp;<small>" + theme.version + "</small>"
2394  );
2395  $("#about-theme-body").html(infoBox);
2396  $(".vcarousel").carousel({
2397    interval: 3000,
2398  });
2399  $("#treeviewTheme").treeview({
2400    levels: 1,
2401    expandIcon: "ti-angle-right",
2402    onhoverColor: "rgba(0, 0, 0, 0.05)",
2403    selectedBackColor: "#03a9f3",
2404    collapseIcon: "ti-angle-down",
2405    data: JSON.stringify(files),
2406  });
2407  $("#aboutThemeScroll").slimScroll({
2408    height: "225px",
2409  });
2410}
2411function aboutPlugin(plugin) {
2412  var files = aboutPluginFiles(plugin.files);
2413  var imageList = aboutPluginImages(plugin.images);
2414  var homepageLink =
2415    plugin.website !== "" || plugin.website !== null
2416      ? "onclick=\"window.open('" + plugin.website + "','_blank');\""
2417      : " ";
2418
2419  var infoBox =
2420    `
2421    <div class="row">
2422        <div class="col-lg-6 col-sm-12 col-xs-12">
2423            <div class="row">
2424                <div class="col-lg-12 col-sm-12 col-xs-12">
2425                    <div class="white-box p-10" id="aboutPluginScroll">
2426                        ` +
2427    plugin.description +
2428    `
2429                    </div>
2430                </div>
2431                <div class="clearfix">&nbsp;</div>
2432                <div class="col-lg-4 col-sm-4 col-xs-12">
2433                    <div class="white-box mouse">
2434                        <ul class="list-inline two-part text-center m-b-0">
2435                            <li><i class="icon-envelope-open text-info"></i></li>
2436                        </ul>
2437                    </div>
2438                </div>
2439                <div class="col-lg-4 col-sm-4 col-xs-12">
2440                    <div class="white-box mouse" ` +
2441    homepageLink +
2442    `>
2443                        <ul class="list-inline two-part text-center m-b-0">
2444                            <li><i class="icon-home text-danger"></i></li>
2445                        </ul>
2446                    </div>
2447                </div>
2448                <div class="col-lg-4 col-sm-4 col-xs-12">
2449                    <div class="white-box mouse" onclick="$('.pluginFileList').toggleClass('hidden');">
2450                        <ul class="list-inline two-part text-center m-b-0">
2451                            <li><i class="icon-folder text-purple"></i></li>
2452                        </ul>
2453                    </div>
2454                </div>
2455                <div class="col-sm-12 col-xs-12 pluginFileList hidden">
2456                    <div id="treeview5" class=""></div>
2457                </div>
2458            </div>
2459        </div>
2460        <div class="col-lg-6 col-sm-12 col-xs-12">
2461            <div class="news-slide m-b-15">
2462                <div class="vcarousel slide">
2463                    <!-- Carousel items -->
2464                    <div class="carousel-inner">
2465                        ` +
2466    imageList +
2467    `
2468                    </div>
2469                </div>
2470            </div>
2471        </div>
2472    </div>`;
2473  $("#about-plugin-title").html(
2474    plugin.name + "&nbsp;<small>" + plugin.version + "</small>"
2475  );
2476  $("#about-plugin-body").html(infoBox);
2477  $(".vcarousel").carousel({
2478    interval: 3000,
2479  });
2480  $("#treeview5").treeview({
2481    levels: 1,
2482    expandIcon: "ti-angle-right",
2483    onhoverColor: "rgba(0, 0, 0, 0.05)",
2484    selectedBackColor: "#03a9f3",
2485    collapseIcon: "ti-angle-down",
2486    data: JSON.stringify(files),
2487  });
2488  $("#aboutPluginScroll").slimScroll({
2489    height: "225px",
2490  });
2491}
2492function removePlugin(plugin = null) {
2493  if (plugin == null) {
2494    return false;
2495  }
2496  message(
2497    "Removing Plugin",
2498    plugin,
2499    activeInfo.settings.notifications.position,
2500    "#FFF",
2501    "success",
2502    "5000"
2503  );
2504  organizrAPI2("DELETE", "api/v2/plugins/manage/" + plugin, {})
2505    .success(function (data) {
2506      try {
2507        let html = data.response;
2508        loadPluginMarketplace();
2509        message(
2510          plugin + " Removed",
2511          "",
2512          activeInfo.settings.notifications.position,
2513          "#FFF",
2514          "success",
2515          "5000"
2516        );
2517      } catch (e) {
2518        organizrCatchError(e, data);
2519      }
2520    })
2521    .fail(function (xhr) {
2522      OrganizrApiError(xhr, "Removal Failed");
2523    });
2524}
2525function removeTheme(theme = null) {
2526  if (theme == null) {
2527    return false;
2528  }
2529  message(
2530    "Removing Theme",
2531    theme,
2532    activeInfo.settings.notifications.position,
2533    "#FFF",
2534    "success",
2535    "5000"
2536  );
2537  organizrAPI2("DELETE", "api/v2/themes/manage/" + theme, {})
2538    .success(function (data) {
2539      try {
2540        let html = data.response;
2541        loadThemeMarketplace();
2542        message(
2543          theme + " Removed",
2544          "",
2545          activeInfo.settings.notifications.position,
2546          "#FFF",
2547          "success",
2548          "5000"
2549        );
2550      } catch (e) {
2551        organizrCatchError(e, data);
2552      }
2553    })
2554    .fail(function (xhr) {
2555      OrganizrApiError(xhr, "Removal Failed");
2556    });
2557}
2558function installPlugin(plugin = null) {
2559  if (plugin == null) {
2560    return false;
2561  }
2562  message(
2563    "Installing Plugin",
2564    plugin,
2565    activeInfo.settings.notifications.position,
2566    "#FFF",
2567    "success",
2568    "5000"
2569  );
2570  organizrAPI2("POST", "api/v2/plugins/manage/" + plugin, {})
2571    .success(function (data) {
2572      try {
2573        var html = data.response;
2574        loadPluginMarketplace();
2575        message(
2576          plugin + " Installed",
2577          "",
2578          activeInfo.settings.notifications.position,
2579          "#FFF",
2580          "success",
2581          "5000"
2582        );
2583      } catch (e) {
2584        organizrCatchError(e, data);
2585      }
2586    })
2587    .fail(function (xhr) {
2588      OrganizrApiError(xhr, "Install Failed");
2589    });
2590}
2591function installTheme(theme = null) {
2592  if (theme == null) {
2593    return false;
2594  }
2595  message(
2596    "Installing Theme",
2597    theme,
2598    activeInfo.settings.notifications.position,
2599    "#FFF",
2600    "success",
2601    "5000"
2602  );
2603  organizrAPI2("POST", "api/v2/themes/manage/" + theme, {})
2604    .success(function (data) {
2605      try {
2606        var html = data.response;
2607        loadThemeMarketplace();
2608        message(
2609          theme + " Installed",
2610          "",
2611          activeInfo.settings.notifications.position,
2612          "#FFF",
2613          "success",
2614          "5000"
2615        );
2616      } catch (e) {
2617        organizrCatchError(e, data);
2618      }
2619    })
2620    .fail(function (xhr) {
2621      OrganizrApiError(xhr, "Install Failed");
2622    });
2623}
2624function pluginStatus(name = null, version = null) {
2625  var installedPlugins = [];
2626  var installedPluginsList = [];
2627  if (activeInfo.settings.misc.installedPlugins !== "") {
2628    installedPlugins = activeInfo.settings.misc.installedPlugins.split("|");
2629    $.each(installedPlugins, function (i, v) {
2630      var plugin = v.split(":");
2631      installedPluginsList[plugin[0]] = plugin[1];
2632    });
2633    if (typeof installedPluginsList[name] !== "undefined") {
2634      if (version !== installedPluginsList[name]) {
2635        return "Update Available";
2636      } else {
2637        return "Up to date";
2638      }
2639    } else {
2640      return "Not Installed";
2641    }
2642  } else {
2643    return "Not Installed";
2644  }
2645}
2646function themeStatus(name = null, version = null) {
2647  var installedThemes = [];
2648  var installedThemesList = [];
2649  if (activeInfo.settings.misc.installedThemes !== "") {
2650    installedThemes = activeInfo.settings.misc.installedThemes.split("|");
2651    $.each(installedThemes, function (i, v) {
2652      var theme = v.split(":");
2653      installedThemesList[theme[0]] = theme[1];
2654    });
2655    if (typeof installedThemesList[name] !== "undefined") {
2656      if (version !== installedThemesList[name]) {
2657        return "Update Available";
2658      } else {
2659        return "Up to date";
2660      }
2661    } else {
2662      return "Not Installed";
2663    }
2664  } else {
2665    return "Not Installed";
2666  }
2667}
2668function copyHomepageJSON(item) {
2669  organizrAPI2("GET", "api/v2/settings/homepage/" + item + "/debug")
2670    .success(function (data) {
2671      try {
2672        let response = data.response;
2673        let debug = response.data;
2674        clipboard(true, JSON.stringify(debug, null, "\t"));
2675        message(
2676          "",
2677          window.lang.translate("Copied JSON to clipboard"),
2678          activeInfo.settings.notifications.position,
2679          "#FFF",
2680          "success",
2681          "5000"
2682        );
2683      } catch (e) {
2684        organizrCatchError(e, data);
2685      }
2686    })
2687    .fail(function (xhr) {
2688      OrganizrApiError(xhr, "Copy JSON Failed");
2689    });
2690}
2691function homepageItemFormHTML(v) {
2692  let docs =
2693    typeof v.docs == "undefined"
2694      ? ""
2695      : `<small class="pull-right m-r-5"><a data-toggle="tooltip" title="Go to Support Doc" data-placement="bottom" class="btn btn-circle btn-primary waves-effect waves-light" href="${v.docs}" target="_blank"> <i class="fa-fw fa fa-question-circle"></i></a></small>`;
2696  let debug = typeof v.debug == "undefined" ? false : true;
2697  debug = debug === true ? v.debug : false;
2698  debug =
2699    debug === true
2700      ? `<small class="pull-right m-r-5"><a data-toggle="tooltip" title="Copy JSON Settings" data-placement="bottom" href="javascript:copyHomepageJSON('${v.name}')" class="btn btn-circle btn-info waves-effect waves-light copyHomepageJSON"> <i class="fa-fw ti-clipboard"></i></a></small>`
2701      : "";
2702  return (
2703    `
2704	<a id="editHomepageItemCall" href="#editHomepageItemDiv" class="hidden">homepage item</a>
2705	<form id="homepage-` +
2706    v.name +
2707    `-form" class="white-popup mfp-with-anim homepageForm addFormTick">
2708		<fieldset style="border:0;" class="col-md-10 col-md-offset-1">
2709            <div class="panel bg-org panel-info">
2710                <div class="panel-heading">
2711                    <span class="" lang="en">` +
2712    v.name +
2713    `</span>
2714                    <button data-toggle="tooltip" title="Close" data-placement="bottom"  type="button" class="btn btn-default btn-circle close-popup pull-right close-editHomepageItemDiv"><i class="fa fa-times"></i> </button>
2715                    ${docs}${debug}
2716                    <button data-toggle="tooltip" title="Reset" data-placement="bottom" id="homepage-` +
2717    v.name +
2718    `-form-reset" onclick="editHomepageItem('` +
2719    v.name +
2720    `', true)" class="btn btn-inverse btn-circle waves-effect waves-light pull-right hidden m-r-5" type="button"><span class=""><i class="fa fa-undo"></i></span></button>
2721                    <button data-toggle="tooltip" title="Save" data-placement="bottom" id="homepage-` +
2722    v.name +
2723    `-form-save" onclick="submitSettingsForm('homepage-` +
2724    v.name +
2725    `-form', true)" class="btn btn-success btn-circle waves-effect waves-light pull-right hidden animated loop-animation rubberBand m-r-5" type="button"><span class=""><i class="fa fa-save"></i></span></button>
2726                </div>
2727                <div class="panel-wrapper collapse in" aria-expanded="true">
2728                    <div class="bg-org">
2729                        ` +
2730    buildFormGroup(v.settings) +
2731    `
2732                    </div>
2733                </div>
2734            </div>
2735		</fieldset>
2736		<div class="clearfix"></div>
2737	</form>
2738	`
2739  );
2740}
2741function clearHomepageOriginal() {
2742  $("#editHomepageItem").html("");
2743}
2744function completeHomepageLoad(item, data) {
2745  /*
2746	if(item == 'CustomHTML'){
2747		let iteration = 0;
2748		$.each(data.settings, function(i,customItem) {
2749			let iterationString = (parseInt(iteration, 10) + 101).toString().substr(1);
2750			let customEditor = 'customHTML'+iterationString+'Editor';
2751			let customTextarea = 'customHTML'+iterationString+'Textarea';
2752			let HTMLMode = ace.require("ace/mode/html").Mode;
2753			customHTMLEditorObject[iterationString] = ace.edit(customEditor);
2754			customHTMLEditorObject[iterationString].session.setMode(new HTMLMode());
2755			customHTMLEditorObject[iterationString].setTheme("ace/theme/idle_fingers");
2756			customHTMLEditorObject[iterationString].setShowPrintMargin(false);
2757			customHTMLEditorObject[iterationString].session.on('change', function(delta) {
2758				$('.' + customTextarea).val(customHTMLEditorObject[iterationString].getValue());
2759				//$('#homepage-CustomHTML-form-save').removeClass('hidden');
2760			});
2761			iteration++;
2762		});
2763	}
2764	*/
2765  pageLoad();
2766}
2767function editHomepageItem(item, reload = false) {
2768  ajaxloader(".editHomepageItemBox-" + item, "in");
2769  organizrAPI2("GET", "api/v2/settings/homepage/" + item)
2770    .success(function (data) {
2771      try {
2772        let response = data.response;
2773        let html = homepageItemFormHTML(response.data);
2774        $("#editHomepageItem").html(html);
2775        if (reload) {
2776          ajaxloader(".editHomepageItemBox-" + item);
2777          return false;
2778        }
2779        /*$("#editHomepageItemCall").animatedModal({
2780				top: '40px',
2781				left: '0px',
2782				color: '#000000eb',
2783				animatedIn: 'bounceInUp',
2784				animatedOut: 'bounceOutDown',
2785				position: 'fixed',
2786				afterClose: function() {
2787					$('body, html').css({'overflow':'hidden'});
2788				}
2789			});*/
2790        new Custombox.modal({
2791          content: {
2792            effect: "slidetogether",
2793            animateFrom: "bottom",
2794            animateTo: "bottom",
2795            target: "#editHomepageItemDiv",
2796            width: "100%",
2797            delay: 0,
2798            fullscreen: true,
2799            clone: false,
2800            onComplete: completeHomepageLoad(item, response.data),
2801            onClose: clearHomepageOriginal,
2802          },
2803          loader: { active: true },
2804        }).open();
2805        //$('#editHomepageItemCall').click();
2806      } catch (e) {
2807        organizrCatchError(e, data);
2808      }
2809      ajaxloader(".editHomepageItemBox-" + item);
2810    })
2811    .fail(function (xhr) {
2812      OrganizrApiError(xhr, "Edit Homepage Failed");
2813      ajaxloader(".editHomepageItemBox-" + item);
2814    });
2815}
2816function buildHomepageItem(array) {
2817  var listing = "";
2818  if (Array.isArray(array)) {
2819    $.each(array, function (i, v) {
2820      if (v.enabled) {
2821        listing +=
2822          `
2823				<div class="col-lg-2 col-md-2 col-sm-6 col-xs-6">
2824					<div class="white-box bg-org m-0">
2825						<div class="el-card-item p-0 editHomepageItemBox-` +
2826          v.name +
2827          `">
2828							<div class="el-card-avatar el-overlay-1">
2829								<a onclick="editHomepageItem('` +
2830          v.name +
2831          `')"><img class="lazyload tabImages mouse" data-src="` +
2832          v.image +
2833          `"></a>
2834							</div>
2835							<div class="el-card-content">
2836								<h3 class="box-title elip">` +
2837          v.name +
2838          `</h3>
2839								<small class="elip text-uppercase elip">` +
2840          v.category +
2841          `</small><br>
2842							</div>
2843						</div>
2844					</div>
2845				</div>
2846				`;
2847      }
2848    });
2849  }
2850  return listing;
2851}
2852function buildPluginsOLD() {
2853  organizrAPI2("GET", "api/v2/plugins")
2854    .success(function (data) {
2855      try {
2856        var response = data.response;
2857      } catch (e) {
2858        organizrCatchError(e, data);
2859      }
2860      $("#main-plugin-area").html(buildPluginsItemOLD(response.data));
2861    })
2862    .fail(function (xhr) {
2863      OrganizrApiError(xhr);
2864    });
2865}
2866function buildPlugins(status = "enabled") {
2867  organizrAPI2("GET", "api/v2/plugins/" + status)
2868    .success(function (data) {
2869      try {
2870        var response = data.response;
2871      } catch (e) {
2872        organizrCatchError(e, data);
2873      }
2874      $("#" + status + "-plugin-area").html(
2875        buildPluginsItem(response.data, status)
2876      );
2877    })
2878    .fail(function (xhr) {
2879      OrganizrApiError(xhr);
2880    });
2881}
2882function buildHomepage() {
2883  organizrAPI2("GET", "api/v2/settings/homepage")
2884    .success(function (data) {
2885      try {
2886        var response = data.response;
2887      } catch (e) {
2888        organizrCatchError(e, data);
2889      }
2890      $("#settings-homepage-list").html(buildHomepageItem(response.data));
2891    })
2892    .fail(function (xhr) {
2893      OrganizrApiError(xhr);
2894    });
2895}
2896function buildFormGroup(array) {
2897  var mainCount = 0;
2898  var group = '<div class="tab-content w-100">';
2899  var uList =
2900    '<div class="vtabs customvtab"><ul class="nav tabs-vertical" role="tablist">';
2901  $.each(array, function (i, v) {
2902    mainCount++;
2903    var count = 0;
2904    var total = v.length;
2905    var active = mainCount == 1 ? "active" : "";
2906    var customID = createRandomString(10);
2907    if (i == "custom") {
2908      group += v;
2909    } else {
2910      uList +=
2911        `<li role="presentation" class="` +
2912        active +
2913        `"><a href="#` +
2914        customID +
2915        cleanClass(i) +
2916        `" aria-controls="` +
2917        i +
2918        `" role="tab" data-toggle="tab" aria-expanded="false"><span lang="en">` +
2919        i +
2920        `</span></a></li>`;
2921      group +=
2922        `
2923				<!-- FORM GROUP -->
2924				<div role="tabpanel" class="tab-pane fade in ` +
2925        active +
2926        `" id="` +
2927        customID +
2928        cleanClass(i) +
2929        `">
2930			`;
2931      $.each(v, function (i, v) {
2932        var override = "6";
2933        if (typeof v.override !== "undefined") {
2934          override = v.override;
2935        }
2936        var arrayMultiple = false;
2937        if (typeof v.type !== "undefined") {
2938          if (v.type == "arrayMultiple") {
2939            arrayMultiple = true;
2940          }
2941        }
2942        count++;
2943        if (count % 2 !== 0) {
2944          group += '<div class="row start">';
2945        }
2946        var helpID = "#help-info-" + v.name;
2947        var helpTip = v.help
2948          ? '<sup><a class="help-tip" data-toggle="collapse" href="' +
2949            helpID +
2950            '" aria-expanded="true"><i class="m-l-5 fa fa-question-circle text-info" title="Help" data-toggle="tooltip"></i></a></sup>'
2951          : "";
2952        var builtItems = "";
2953        if (arrayMultiple == true) {
2954          $.each(v.value, function (index, value) {
2955            if (typeof value === "object") {
2956              builtItems += '<div class="row m-b-40">';
2957              $.each(value, function (number, formItem) {
2958                let clearfix =
2959                  formItem.type == "blank"
2960                    ? '<div class="clearfix"></div>'
2961                    : "";
2962                builtItems += `
2963                                    <!-- INPUT BOX  Yes Multiple -->
2964                                    <div class="col-md-6 p-b-10">
2965                                        <div class="form-group">
2966                                            <label class="control-label col-md-12"><span lang="en">${
2967                                              formItem.label
2968                                            }</span>${helpTip}</label>
2969                                            <div class="col-md-12">${buildFormItem(
2970                                              formItem
2971                                            )}</div> <!-- end div -->
2972                                        </div>
2973                                    </div>
2974                                    ${clearfix}
2975                                    <!--/ INPUT BOX -->
2976                                `;
2977              });
2978              builtItems += "</div>";
2979            } else {
2980              builtItems += buildFormItem(value);
2981            }
2982          });
2983        } else {
2984          builtItems =
2985            `
2986					<!-- INPUT BOX  no Multiple-->
2987					<div class="col-md-` +
2988            override +
2989            ` p-b-10">
2990						<div class="form-group">
2991							<label class="control-label col-md-12"><span lang="en">${
2992                v.label
2993              }</span>${helpTip}</label>
2994							<div class="col-md-12">
2995								${buildFormItem(v)}
2996							</div>
2997						</div>
2998					</div>
2999					<!--/ INPUT BOX -->
3000				`;
3001        }
3002        group += builtItems;
3003        if (count % 2 == 0 || count == total) {
3004          group += "</div><!--end-->";
3005        }
3006      });
3007      group += "</div>";
3008    }
3009  });
3010  return uList + "</ul>" + group + "</div>";
3011}
3012function createImageSwal(attr) {
3013  let title = attr.attr("data-title");
3014  let fullPath = attr.attr("data-image-path");
3015  let clipboardText = attr.attr("data-clipboard-text");
3016  let name = attr.attr("data-image-name");
3017  let extension = attr.attr("data-image-name-ext");
3018  let div =
3019    `
3020		<div class="panel panel-default">
3021            <div class="panel-heading"><h1><img class="center" src="` +
3022    fullPath +
3023    `" style="height: 50px; width: 50px">` +
3024    title +
3025    `</h1></div>
3026            <div class="panel-wrapper collapse in">
3027                <div class="panel-body">
3028                	<h5 lang="en">Choose action:</h5>
3029					<div class="button-box">
3030                        <button class="btn btn-info waves-effect waves-light clipboard" type="button" data-clipboard-text="` +
3031    clipboardText +
3032    `"><span class="btn-label"><i class="ti-clipboard"></i></span><span lang="en">Copy to Clipboard</span></button>
3033                        <button class="btn btn-danger waves-effect waves-light deleteImage" type="button" data-image-path="` +
3034    fullPath +
3035    `" data-image-name="` +
3036    name +
3037    `" data-image-name-ext="` +
3038    extension +
3039    `"><span class="btn-label"><i class="fa fa-trash"></i></span><span lang="en">Delete</span></button>                        
3040                    </div>
3041                </div>
3042            </div>
3043        </div>
3044        `;
3045  swal({
3046    content: createElementFromHTML(div),
3047    buttons: false,
3048    className: "bg-org",
3049  });
3050}
3051function buildImageManagerViewItem(array) {
3052  var imageListing = "";
3053  if (Array.isArray(array)) {
3054    $.each(array, function (i, v) {
3055      var filepath = v.split("/");
3056      var name = filepath[filepath.length - 1].split(".");
3057      var clipboardText = v.replace(/ /g, "%20");
3058      var fileAndExt = filepath[filepath.length - 1];
3059      imageListing +=
3060        `
3061			<a class="imageManagerItem" href="javascript:void(0);" data-toggle="lightbox" data-gallery="multiimages" data-title="` +
3062        name[0] +
3063        `" data-clipboard-text="` +
3064        clipboardText +
3065        `" data-image-path="` +
3066        v +
3067        `" data-image-name="` +
3068        name[0] +
3069        `" data-image-name-ext="` +
3070        fileAndExt +
3071        `"><img data-toggle="tooltip" title="${name[0]}" data-placement="bottom"  data-src="` +
3072        v +
3073        `" alt="tabImage" class="all studio lazyload" /> </a>
3074			`;
3075    });
3076  }
3077  return imageListing;
3078}
3079function buildImageManagerView() {
3080  organizrAPI2("GET", "api/v2/image")
3081    .success(function (data) {
3082      try {
3083        let response = data.response;
3084        $(".settings-image-manager-list").html(
3085          buildImageManagerViewItem(response.data)
3086        );
3087        $container = $("#gallery-content-center");
3088        try {
3089          if (typeof $container.isotope == "undefined") {
3090            $container.isotope({ itemSelector: "img" });
3091          } else {
3092            $container.isotope({ itemSelector: "img" });
3093          }
3094        } catch (e) {
3095          $container.isotope("destroy");
3096          $container.isotope({ itemSelector: "img" });
3097        }
3098      } catch (e) {
3099        organizrCatchError(e, data);
3100      }
3101    })
3102    .fail(function (xhr) {
3103      OrganizrApiError(xhr);
3104    });
3105}
3106function buildPluginsSettings() {
3107  organizrAPI2("GET", "api/v2/settings/plugin")
3108    .success(function (data) {
3109      try {
3110        let response = data.response;
3111        $("#plugin-settings-form").html(buildFormGroup(response.data));
3112      } catch (e) {
3113        organizrCatchError(e, data);
3114      }
3115    })
3116    .fail(function (xhr) {
3117      OrganizrApiError(xhr);
3118    });
3119}
3120function buildThemeSettings() {
3121  organizrAPI2("GET", "api/v2/settings/theme")
3122    .success(function (data) {
3123      try {
3124        let response = data.response;
3125        $("#theme-settings-form").html(buildFormGroup(response.data));
3126      } catch (e) {
3127        organizrCatchError(e, data);
3128      }
3129    })
3130    .fail(function (xhr) {
3131      OrganizrApiError(xhr);
3132    });
3133}
3134function buildCustomizeAppearance() {
3135  organizrAPI2("GET", "api/v2/settings/appearance")
3136    .success(function (data) {
3137      try {
3138        var response = data.response;
3139      } catch (e) {
3140        organizrCatchError(e, data);
3141      }
3142      $("#customize-appearance-form").html(buildFormGroup(response.data));
3143      $("input.pick-a-color-custom-options").ColorPickerSliders({
3144        placement: "bottom",
3145        color: "#987654",
3146        hsvpanel: true,
3147        previewformat: "hex",
3148      });
3149    })
3150    .fail(function (xhr) {
3151      OrganizrApiError(xhr);
3152    });
3153}
3154function buildSSO() {
3155  organizrAPI2("GET", "api/v2/settings/sso")
3156    .success(function (data) {
3157      try {
3158        var response = data.response;
3159      } catch (e) {
3160        organizrCatchError(e, data);
3161      }
3162      $("#sso-form").html(buildFormGroup(response.data));
3163    })
3164    .fail(function (xhr) {
3165      console.error("Organizr Function: API Connection Failed");
3166    });
3167}
3168function buildSettingsMain() {
3169  organizrAPI2("GET", "api/v2/settings/main")
3170    .success(function (data) {
3171      try {
3172        var response = data.response;
3173      } catch (e) {
3174        organizrCatchError(e, data);
3175      }
3176      $("#settings-main-form").html(buildFormGroup(response.data));
3177      changeAuth();
3178    })
3179    .fail(function (xhr) {
3180      OrganizrApiError(xhr);
3181    });
3182}
3183function buildUserManagement() {
3184  organizrAPI2("GET", "api/v2/users?includeGroups")
3185    .success(function (data) {
3186      try {
3187        var response = data.response;
3188      } catch (e) {
3189        organizrCatchError(e, data);
3190      }
3191      $("#manageUserTable").html(buildUserManagementItem(response.data));
3192    })
3193    .fail(function (xhr) {
3194      OrganizrApiError(xhr);
3195    });
3196}
3197function buildGroupManagement() {
3198  organizrAPI2("GET", "api/v2/groups?includeUsers")
3199    .success(function (data) {
3200      try {
3201        var response = data.response;
3202      } catch (e) {
3203        organizrCatchError(e, data);
3204      }
3205      $("#manageGroupTable").html(buildGroupManagementItem(response.data));
3206    })
3207    .fail(function (xhr) {
3208      OrganizrApiError(xhr);
3209    });
3210}
3211function buildTabEditor() {
3212  organizrAPI2("GET", "api/v2/tabs")
3213    .success(function (data) {
3214      try {
3215        var response = data.response;
3216      } catch (e) {
3217        organizrCatchError(e, data);
3218      }
3219      $("#tabEditorTable").html(buildTabEditorItem(response.data));
3220      checkTabHomepageItems();
3221      addTabSortable();
3222    })
3223    .fail(function (xhr) {
3224      OrganizrApiError(xhr);
3225    });
3226}
3227function addTabSortable() {
3228  let el = document.getElementById("tabEditorTable");
3229  let tabSorter = new Sortable(el, {
3230    handle: ".sort-tabs-handle",
3231    ghostClass: "sortable-ghost",
3232    multiDrag: true,
3233    selectedClass: "multi-selected",
3234    onUpdate: function (evt) {
3235      $("input.order").each(function (idx) {
3236        $(this).val(idx + 1);
3237      });
3238      newTabsGlobal = $("#submit-tabs-form").serializeToJSON();
3239      $(".saveTabOrderButton").removeClass("hidden");
3240    },
3241  });
3242}
3243function checkTabHomepageItems() {
3244  var tabList = $(".checkTabHomepageItem");
3245  $.each(tabList, function (i, v) {
3246    var el = $(v);
3247    var id = el.attr("id");
3248    var name = el.attr("data-name");
3249    var url = el.attr("data-url");
3250    var urlLocal = el.attr("data-url-local");
3251    checkTabHomepageItem(id, name, url, urlLocal);
3252  });
3253}
3254function sortHomepageItemHrefs() {
3255  var hrefList = $(".popup-with-form");
3256  window.hrefList = new Array();
3257  $.each(hrefList, function (i, v) {
3258    var el = $(v);
3259    var href = el.attr("href");
3260    if (href.includes("#homepage-")) {
3261      var splitHref = href.split("-");
3262      window.hrefList[splitHref[1]] = i;
3263    }
3264  });
3265}
3266function checkTabHomepageItemList(name, url, urlLocal, id, check, tab) {
3267  // might use this later
3268  if (name.includes(check) || url.includes(check) || urlLocal.includes(check)) {
3269    addEditHomepageItem(id, tab);
3270  }
3271}
3272function checkTabHomepageItem(id, name, url, urlLocal) {
3273  name = name.toLowerCase();
3274  url = url.toLowerCase();
3275  urlLocal = urlLocal.toLowerCase();
3276  try {
3277    let urlObject = new URL(url);
3278    if (urlObject.pathname !== "/" && urlObject !== "#") {
3279      url = urlObject.pathname;
3280    }
3281  } catch {
3282    url = url;
3283  }
3284  if (
3285    name.includes("sonarr") ||
3286    url.includes("sonarr") ||
3287    urlLocal.includes("sonarr")
3288  ) {
3289    addEditHomepageItem(id, "Sonarr");
3290  } else if (
3291    name.includes("radarr") ||
3292    url.includes("radarr") ||
3293    urlLocal.includes("radarr")
3294  ) {
3295    addEditHomepageItem(id, "Radarr");
3296  } else if (
3297    name.includes("lidarr") ||
3298    url.includes("lidarr") ||
3299    urlLocal.includes("lidarr")
3300  ) {
3301    addEditHomepageItem(id, "Lidarr");
3302  } else if (
3303    name.includes("couchpotato") ||
3304    url.includes("couchpotato") ||
3305    urlLocal.includes("couchpotato")
3306  ) {
3307    addEditHomepageItem(id, "CouchPotato");
3308  } else if (
3309    name.includes("sick") ||
3310    url.includes("sick") ||
3311    urlLocal.includes("sick")
3312  ) {
3313    addEditHomepageItem(id, "SickRage");
3314  } else if (
3315    (name.includes("plex") ||
3316      url.includes("plex") ||
3317      urlLocal.includes("plex")) &&
3318    !name.includes("plexpy")
3319  ) {
3320    addEditHomepageItem(id, "Plex");
3321  } else if (
3322    name.includes("emby") ||
3323    url.includes("emby") ||
3324    urlLocal.includes("emby")
3325  ) {
3326    addEditHomepageItem(id, "Emby");
3327  } else if (
3328    name.includes("jdownloader") ||
3329    url.includes("jdownloader") ||
3330    urlLocal.includes("jdownloader") ||
3331    name.includes("rsscrawler") ||
3332    url.includes("rsscrawler") ||
3333    urlLocal.includes("rsscrawler")
3334  ) {
3335    addEditHomepageItem(id, "jDownloader");
3336  } else if (
3337    name.includes("sab") ||
3338    url.includes("sab") ||
3339    urlLocal.includes("sab")
3340  ) {
3341    addEditHomepageItem(id, "SabNZBD");
3342  } else if (
3343    name.includes("nzbget") ||
3344    url.includes("nzbget") ||
3345    urlLocal.includes("nzbget")
3346  ) {
3347    addEditHomepageItem(id, "NZBGet");
3348  } else if (
3349    name.includes("transmission") ||
3350    url.includes("transmission") ||
3351    urlLocal.includes("transmission")
3352  ) {
3353    addEditHomepageItem(id, "Transmission");
3354  } else if (
3355    name.includes("qbit") ||
3356    url.includes("qbit") ||
3357    urlLocal.includes("qbit")
3358  ) {
3359    addEditHomepageItem(id, "qBittorrent");
3360  } else if (
3361    name.includes("rtorrent") ||
3362    url.includes("rtorrent") ||
3363    urlLocal.includes("rtorrent")
3364  ) {
3365    addEditHomepageItem(id, "rTorrent");
3366  } else if (
3367    name.includes("utorrent") ||
3368    url.includes("utorrent") ||
3369    urlLocal.includes("utorrent")
3370  ) {
3371    addEditHomepageItem(id, "utorrent");
3372  } else if (
3373    name.includes("deluge") ||
3374    url.includes("deluge") ||
3375    urlLocal.includes("deluge")
3376  ) {
3377    addEditHomepageItem(id, "Deluge");
3378  } else if (
3379    name.includes("ombi") ||
3380    url.includes("ombi") ||
3381    urlLocal.includes("ombi")
3382  ) {
3383    addEditHomepageItem(id, "Ombi");
3384  } else if (
3385    name.includes("healthcheck") ||
3386    url.includes("healthcheck") ||
3387    urlLocal.includes("healthcheck")
3388  ) {
3389    addEditHomepageItem(id, "HealthChecks");
3390  } else if (
3391    name.includes("jackett") ||
3392    url.includes("jackett") ||
3393    urlLocal.includes("jackett")
3394  ) {
3395    addEditHomepageItem(id, "Jackett");
3396  } else if (
3397    name.includes("prowlarr") ||
3398    url.includes("prowlarr") ||
3399    urlLocal.includes("prowlarr")
3400  ) {
3401    addEditHomepageItem(id, "Prowlarr");
3402  } else if (
3403    name.includes("unifi") ||
3404    url.includes("unifi") ||
3405    urlLocal.includes("unifi")
3406  ) {
3407    addEditHomepageItem(id, "Unifi");
3408  } else if (
3409    name.includes("tautulli") ||
3410    url.includes("tautulli") ||
3411    urlLocal.includes("tautulli")
3412  ) {
3413    addEditHomepageItem(id, "Tautulli");
3414  }
3415}
3416function addEditHomepageItem(id, type) {
3417  let html = '<i class="ti-home"></i>';
3418  $("#" + id).html(html);
3419  $("#" + id).attr("onclick", 'editHomepageItem("' + type + '")');
3420  return false;
3421}
3422function buildCategoryEditor() {
3423  organizrAPI2("GET", "api/v2/tabs")
3424    .success(function (data) {
3425      try {
3426        var response = data.response;
3427      } catch (e) {
3428        organizrCatchError(e, data);
3429      }
3430      $("#categoryEditorTable").html(buildCategoryEditorItem(response.data));
3431    })
3432    .fail(function (xhr) {
3433      OrganizrApiError(xhr);
3434    });
3435}
3436/* END ORGANIZR API FUNCTIONS */
3437function buildLanguage(replace = false, newLang = null) {
3438  var languageItems = "";
3439  var currentLanguage = getCookie("organizrLanguage")
3440    ? getCookie("organizrLanguage")
3441    : window.lang.currentLang;
3442  var newLangCode = "";
3443  $.each(languageList, function (i, v) {
3444    if (newLang === v.language) {
3445      newLangCode = v.code;
3446    }
3447    var active = v.code == currentLanguage ? "" : "";
3448    languageItems +=
3449      `
3450			<a onclick="window.lang.change('` +
3451      v.code +
3452      `');buildLanguage(true,'` +
3453      v.language +
3454      `')" href="javascript:void(0);" class="` +
3455      active +
3456      `">
3457				<div class="mail-content"><h5 class="m-0">` +
3458      v.language +
3459      `</h5><span class="mail-desc" lang="en">` +
3460      active +
3461      `</span></div>
3462			</a>
3463		`;
3464  });
3465  var lang = `
3466		<li class="dropdown" id="languageDropdown">
3467			<a class="dropdown-toggle waves-effect waves-light" data-toggle="dropdown" href="#" aria-expanded="false"> <i class="fa fa-language"></i><span></span></a>
3468			<ul class="dropdown-menu mailbox animated bounceInDown language-box">
3469				<li>
3470					<div class="drop-title" lang="en">Choose Language</div>
3471				</li>
3472				<li>
3473					<div class="message-center default-scrollbar">${languageItems}</div>
3474				</li>
3475			</ul>
3476			<!-- /.dropdown-messages -->
3477		</li>
3478	`;
3479  if (replace == true) {
3480    setLangCookie(newLangCode);
3481    $("#languageDropdown").replaceWith(lang);
3482    message(
3483      "",
3484      window.lang.translate("Changed Language To") + ": " + newLang,
3485      activeInfo.settings.notifications.position,
3486      "#FFF",
3487      "success",
3488      "3500"
3489    );
3490  } else if (replace == "wizard") {
3491    $(lang).appendTo(".navbar-right");
3492  } else {
3493    return lang;
3494  }
3495}
3496
3497function updateUserInformation() {
3498  var passwordMatch = true;
3499  var username = $("#accountUsername").val();
3500  var email = $("#accountEmail").val();
3501  var password1 = $("#accountPassword1").val();
3502  var password2 = $("#accountPassword2").val();
3503  if (password1 != password2) {
3504    passwordMatch = false;
3505    messageSingle(
3506      "",
3507      "Passwords do not match",
3508      activeInfo.settings.notifications.position,
3509      "#FFF",
3510      "error",
3511      "5000"
3512    );
3513    return false;
3514  }
3515  if (username !== "" && email !== "" && passwordMatch == true) {
3516    var post = {
3517      username: username,
3518      email: email,
3519    };
3520    if (password1 !== "") {
3521      post["password"] = password1;
3522    }
3523    ajaxloader(".content-wrap", "in");
3524    organizrAPI2("PUT", "api/v2/users/" + activeInfo.user.userID, post)
3525      .success(function (data) {
3526        try {
3527          var response = data.response;
3528          $.magnificPopup.close();
3529          messageSingle(
3530            "",
3531            window.lang.translate("User Info Updated"),
3532            activeInfo.settings.notifications.position,
3533            "#FFF",
3534            "success",
3535            "5000"
3536          );
3537        } catch (e) {
3538          organizrCatchError(e, data);
3539        }
3540        ajaxloader();
3541      })
3542      .fail(function (xhr) {
3543        OrganizrApiError(xhr, "Update User");
3544        ajaxloader();
3545      });
3546  }
3547}
3548function twoFA(action, type, secret = null) {
3549  switch (action) {
3550    case "activate":
3551      organizrAPI2("POST", "api/v2/2fa/" + type, {})
3552        .success(function (data) {
3553          try {
3554            var html = data.response;
3555          } catch (e) {
3556            organizrCatchError(e, data);
3557          }
3558          let div =
3559            `
3560				<div class="panel panel-default">
3561                    <div class="panel-heading">Enable 2FA: ` +
3562            html.data.type +
3563            `</div>
3564                    <div class="panel-wrapper collapse in">
3565                        <div class="panel-body">
3566                            <p class="twofa-modal-image"><img class="center" src="` +
3567            html.data.url +
3568            `"></p>
3569                            <h5 class="twofa-modal-secret text-center">` +
3570            html.data.secret +
3571            `</h5>
3572	                        <div class="form-group m-t-10">
3573	                            <div class="input-group" style="width: 100%;">
3574	                                <div class="input-group-addon hidden-xs"><i class="ti-lock"></i></div>
3575	                                <input type="text" class="form-control tfa-input" id="twofa-verify" placeholder="Code" autocomplete="off" autocorrect="off" autocapitalize="off" maxlength="6" spellcheck="false" autofocus="" required="">
3576	                            </div>
3577	                            <br>
3578	                            <button class="btn btn-block btn-info" onclick="twoFA('verify','google');">Verify</button>
3579	
3580	                        </div>
3581                        </div>
3582                    </div>
3583                </div>
3584                `;
3585          swal({
3586            content: createElementFromHTML(div),
3587            buttons: false,
3588            className: "bg-org",
3589          });
3590        })
3591        .fail(function (xhr) {
3592          OrganizrApiError(xhr, "2FA");
3593        });
3594      break;
3595    case "deactivate":
3596      organizrAPI2("DELETE", "api/v2/2fa")
3597        .success(function (data) {
3598          try {
3599            message(
3600              "2FA Removed",
3601              "",
3602              activeInfo.settings.notifications.position,
3603              "#FFF",
3604              "success",
3605              "5000"
3606            );
3607            $(".2fa-list").replaceWith(buildTwoFA("internal"));
3608          } catch (e) {
3609            organizrCatchError(e, data);
3610          }
3611        })
3612        .fail(function (xhr) {
3613          OrganizrApiError(xhr, "2FA");
3614        });
3615      break;
3616    case "verify":
3617      var secret = $(".twofa-modal-secret").text();
3618      var code = $("#twofa-verify").val();
3619      if (type !== "" && secret !== "" && code !== "") {
3620        organizrAPI2("POST", "api/v2/2fa", {
3621          type: type,
3622          secret: secret,
3623          code: code,
3624        })
3625          .success(function (data) {
3626            try {
3627              var html = data.response;
3628              message(
3629                "2FA Success",
3630                "Input Code Validated! Saving...",
3631                activeInfo.settings.notifications.position,
3632                "#FFF",
3633                "success",
3634                "5000"
3635              );
3636              swal.close();
3637              twoFA("save", type, secret);
3638            } catch (e) {
3639              organizrCatchError(e, data);
3640            }
3641          })
3642          .fail(function (xhr) {
3643            OrganizrApiError(xhr, "2FA");
3644          });
3645      } else {
3646        message(
3647          "2FA Failed",
3648          "Input Code",
3649          activeInfo.settings.notifications.position,
3650          "#FFF",
3651          "warning",
3652          "5000"
3653        );
3654      }
3655      break;
3656    case "save":
3657      organizrAPI2("PUT", "api/v2/2fa", { type: type, secret: secret })
3658        .success(function (data) {
3659          try {
3660            var html = data.response;
3661            message(
3662              "2FA Success",
3663              "2FA Saved",
3664              activeInfo.settings.notifications.position,
3665              "#FFF",
3666              "success",
3667              "5000"
3668            );
3669            $(".2fa-list").replaceWith(buildTwoFA(type));
3670          } catch (e) {
3671            organizrCatchError(e, data);
3672          }
3673        })
3674        .fail(function (xhr) {
3675          OrganizrApiError(xhr, "2FA");
3676        });
3677      break;
3678  }
3679}
3680function buildTwoFA(current) {
3681  switch (current) {
3682    case "internal":
3683      var option = `
3684                <div class="col-lg-3 col-sm-6 row-in-br">
3685                    <ul class="col-in">
3686                        <li>
3687                            <span class="circle circle-md bg-info"><i class="mdi mdi-webpack mdi-24px"></i></span>
3688                        </li>
3689                        <li class="col-middle">
3690                            <h5>Organizr Authenticator</h5>
3691                            <h5><span lang="en">Current</span></h5>
3692                        </li>
3693                    </ul>
3694                </div>
3695                <div class="col-lg-3 col-sm-6 row-in-br">
3696                    <ul class="col-in">
3697                        <li>
3698                            <span class="circle circle-md bg-info"><i class="fa fa-google"></i></span>
3699                        </li>
3700                        <li class="col-middle">
3701                            <h5>Google Authenticator</h5>
3702                            <h5><a href="javascript:void(0)" onclick="twoFA('activate','google');"><span lang="en">Activate</span></a></h5>
3703                        </li>
3704                    </ul>
3705                </div>
3706            `;
3707      break;
3708    case "google":
3709      var option = `
3710                <div class="col-lg-3 col-sm-6 row-in-br">
3711                    <ul class="col-in">
3712                        <li>
3713                            <span class="circle circle-md bg-info"><i class="fa fa-google"></i></span>
3714                        </li>
3715                        <li class="col-middle">
3716                            <h5>Google Authenticator</h5>
3717                            <h5><a href="javascript:void(0)" onclick="twoFA('deactivate','google');"><span lang="en">Deactivate</span></a></h5>
3718                        </li>
3719                    </ul>
3720                </div>
3721            `;
3722      break;
3723    default:
3724      break;
3725  }
3726  var element =
3727    `
3728    <div class="white-box 2fa-list">
3729        <div class="row row-in">
3730            ` +
3731    option +
3732    `
3733        </div>
3734    </div>
3735    `;
3736  return element;
3737}
3738function scrapeCall() {
3739  // Define the URL to scrape [only supports GET at the moment
3740  var url = "https://api.github.com/users/causefx/repos";
3741  // Define callbacks variable first
3742  var callbacks = $.Callbacks();
3743  // Add functions that will deal with the data
3744  callbacks.add(scrapeFunction);
3745  // Call the API function to scrape the page you want [types = 'json' or 'html']
3746  scrapeAPI(url, callbacks, "json");
3747}
3748function scrapeFunction(data) {
3749  // Here you would do whatever you like
3750  if (data.data.result == "Success") {
3751    console.log("Success!!!");
3752  }
3753  console.log("data:");
3754  console.log(data);
3755}
3756function scrapeAPI(url, callbacks = null, type = null) {
3757  if (typeof url === "undefined") {
3758    console.log("error");
3759    return false;
3760  }
3761  organizrAPI2("POST", "api/v2/homepage/scrape", { url: url, type: type })
3762    .success(function (data) {
3763      try {
3764        let response = data.response;
3765        if (response) {
3766          if (callbacks) {
3767            callbacks.fire(response);
3768          }
3769        }
3770      } catch (e) {
3771        organizrCatchError(e, data);
3772      }
3773    })
3774    .fail(function (xhr) {
3775      OrganizrApiError(xhr, "Scrape");
3776    });
3777}
3778function revokeToken(id) {
3779  organizrAPI2("DELETE", "api/v2/token/" + id, {})
3780    .success(function (data) {
3781      try {
3782        $("#token-" + id).fadeOut();
3783        message(
3784          window.lang.translate("Removed Token"),
3785          "",
3786          activeInfo.settings.notifications.position,
3787          "#FFF",
3788          "success",
3789          "3500"
3790        );
3791      } catch (e) {
3792        organizrCatchError(e, data);
3793      }
3794    })
3795    .fail(function (xhr) {
3796      ajaxloader();
3797      OrganizrApiError(xhr, "Revoke Token");
3798    });
3799}
3800function buildActiveTokens(array) {
3801  var parser = new UAParser();
3802  var tokens = "";
3803  $.each(array, function (i, v) {
3804    parser.setUA(v.browser);
3805    var result = parser.getResult();
3806    var className =
3807      activeInfo.user.token === v.token ? "bg-success text-inverse" : "";
3808    var extraText =
3809      activeInfo.user.token === v.token
3810        ? '<span class="tooltip-info" data-toggle="tooltip" data-placement="right" title="" data-original-title="Current Token">...' +
3811          v.token.substr(-10, 10) +
3812          "</span>"
3813        : v.token.substr(-10, 10);
3814    if (typeof v.created == "object") {
3815      v.created = v.created.date;
3816    }
3817    if (typeof v.expires == "object") {
3818      v.expires = v.expires.date;
3819    }
3820    v.created = v.created.indexOf("Z") !== -1 ? v.created : v.created + "Z";
3821    v.expires = v.expires.indexOf("Z") !== -1 ? v.expires : v.expires + "Z";
3822
3823    tokens +=
3824      `
3825            <tr id="token-` +
3826      v.id +
3827      `" class="` +
3828      className +
3829      `">
3830                <td>` +
3831      v.id +
3832      `</td>
3833                <td>` +
3834      extraText +
3835      `</td>
3836                <td>` +
3837      moment(v.created).format("LLL") +
3838      `</td>
3839                <td>` +
3840      moment(v.expires).format("LLL") +
3841      `</td>
3842                <td>` +
3843      v.ip +
3844      `</td>
3845                <td>
3846                    <button class="btn btn-danger waves-effect waves-light" type="button" onclick="revokeToken('` +
3847      v.id +
3848      `');"><i class="fa fa-ban"></i></button>
3849                </td>
3850            </tr>
3851        `;
3852  });
3853  return (
3854    `
3855        <div class="col-lg-12">
3856            <div class="panel panel-info">
3857                <div class="panel-heading"> <span lang="en">Active Tokens</span>
3858                    <div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-plus"></i></a> </div>
3859                </div>
3860                <div class="panel-wrapper collapse" aria-expanded="true">
3861                    <div class="panel-body bg-org p-0">
3862                        <div class="table-responsive">
3863                            <table class="table color-table info-table">
3864                                <thead>
3865                                    <tr>
3866                                        <th>#</th>
3867                                        <th lang="en">Token</th>
3868                                        <th lang="en">Created</th>
3869                                        <th lang="en">Expires</th>
3870                                        <th lang="en">IP</th>
3871                                        <th lang="en">Action</th>
3872                                    </tr>
3873                                </thead>
3874                                <tbody>
3875                                    ` +
3876    tokens +
3877    `
3878                                </tbody>
3879                            </table>
3880                        </div>
3881                    </div>
3882                </div>
3883            </div>
3884        </div>
3885    `
3886  );
3887}
3888function accountManager(user) {
3889  var passwordMessage = "";
3890  switch (activeInfo.settings.misc.authBackend) {
3891    case "plex":
3892      passwordMessage = `
3893                <div class="col-lg-12">
3894                    <div class="panel panel-info">
3895                        <div class="panel-heading"> <span lang="en">Password Notice</span>
3896                            <div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-plus"></i></a> </div>
3897                        </div>
3898                        <div class="panel-wrapper collapse" aria-expanded="true">
3899                            <div class="panel-body bg-org">
3900                                <p lang="en">If you signed in with a Plex Acct... Please use the following link to change your password there:</p><br>
3901                                <p><a href="https://app.plex.tv/auth#?resetPassword" target="_blank" lang="en">Change Password on Plex Website</a></p>
3902                            </div>
3903                        </div>
3904                    </div>
3905                </div>
3906            `;
3907      break;
3908    case "emby":
3909      passwordMessage = `
3910                <div class="col-lg-12">
3911                    <div class="panel panel-info">
3912                        <div class="panel-heading"> <span lang="en">Password Notice</span>
3913                            <div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-minus"></i></a> <a href="#" data-perform="panel-dismiss"><i class="ti-close"></i></a> </div>
3914                        </div>
3915                        <div class="panel-wrapper collapse in" aria-expanded="true">
3916                            <div class="panel-body bg-org">
3917                                <p lang="en">If you signed in with a Emby Acct... Please use the following link to change your password there:</p><br>
3918                                <p><a href="https://emby.media/community/index.php?app=core&module=global&section=lostpass" target="_blank">
3918Change Password on Emby Website</a></p>
3919                            </div>
3920                        </div>
3921                    </div>
3922                </div>
3923            `;
3924      break;
3925    default:
3926      passwordMessage = "";
3927      break;
3928  }
3929  if (user.data.user.loggedin === true) {
3930    var twoFADisable =
3931      buildTwoFA(user.data.user.authService) == "internal" ? "" : "disabled";
3932    var activeTokens = buildActiveTokens(user.data.user.tokenList);
3933    var accountDiv =
3934      `
3935		<div id="account-area" class="white-popup mfp-with-anim mfp-hide">
3936			<div class="col-md-10 col-md-offset-1">
3937				<div class="row">
3938					<div class="col-md-12">
3939						<div class="panel panel-info m-0">
3940							<div class="panel-heading">
3941								<span lang="en">Account Information</span>
3942								<div class="btn-group pull-right">
3943									<button class="btn btn-info waves-effect waves-light" type="button" onclick="updateUserInformation();">
3944										<i class="fa fa-save"></i>
3945									</button>
3946								</div>
3947							</div>
3948							<div class="panel-wrapper collapse in main-email-panel" aria-expanded="true">
3949								<div class="panel-body">
3950									<div class="form-body">
3951									    ` +
3952      buildTwoFA(user.data.user.authService) +
3953      `
3954										<div class="row">
3955                                            <div class="col-lg-12">
3956                                                <div class="panel panel-info">
3957                                                    <div class="panel-heading"> <span lang="en">User Information</span>
3958                                                        <div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-plus"></i></a> </div>
3959                                                    </div>
3960                                                    <div class="panel-wrapper collapse" aria-expanded="true">
3961                                                        <div class="panel-body bg-org p-0 p-t-10">
3962                                                            <div class="col-md-6">
3963                                                                <div class="form-group">
3964                                                                    <label class="control-label" lang="en">Username</label>
3965                                                                    <input ` +
3966      twoFADisable +
3967      ` type="text" id="accountUsername" class="form-control" value="` +
3968      activeInfo.user.username +
3969      `"></div>
3970                                                            </div>
3971                                                            <div class="col-md-6">
3972                                                                <div class="form-group">
3973                                                                    <label class="control-label" lang="en">Email</label>
3974                                                                    <input ` +
3975      twoFADisable +
3976      ` type="text" id="accountEmail" class="form-control" value="` +
3977      activeInfo.user.email +
3978      `"></div>
3979                                                            </div>
3980                                                            <div class="col-md-6 userManagementPassword">
3981                                                                <div class="form-group">
3982                                                                    <label class="control-label" lang="en">Password</label>
3983                                                                    <input type="password" id="accountPassword1" class="form-control"></div>
3984                                                            </div>
3985                                                            <div class="col-md-6 userManagementPassword">
3986                                                                <div class="form-group">
3987                                                                    <label class="control-label" lang="en">Verify Password</label>
3988                                                                    <input type="password" id="accountPassword2" class="form-control"></div>
3989                                                            </div>
3990                                                        </div>
3991                                                    </div>
3992                                                </div>
3993                                            </div>
3994										</div>
3995										<!--/row-->
3996										<div class="row">
3997											` +
3998      activeTokens +
3999      passwordMessage +
4000      `
4001										</div>
4002										<!--/row-->
4003									</div>
4004								</div>
4005							</div>
4006						</div>
4007					</div>
4008				</div>
4009			</div>
4010		</div>
4011		`;
4012    $(".organizr-area").after(accountDiv);
4013    pageLoad();
4014  }
4015}
4016function userMenu(user) {
4017  $("body").attr("data-active-user-group-name", user.data.user.group);
4018  $("body").attr("data-active-user-group-id", user.data.user.groupID);
4019  var sideMenu = "";
4020  var menuList =
4021    '<li class="hidden-xs" onclick="toggleFullScreen();"><a class="waves-effect waves-light"> <i class="ti-fullscreen fullscreen-icon"></i></a></li>';
4022  var showDebug = activeInfo.settings.misc.debugArea
4023    ? '<li><a href="javascript:void(0)" onclick="toggleDebug();"><i class="mdi mdi-bug fa-fw"></i> <span lang="en">Debug Area</span></a></li>'
4024    : "";
4025  menuList += buildLanguage();
4026  if (user.data.user.loggedin === true) {
4027    menuList +=
4028      `
4029			<li class="dropdown">
4030				<a class="dropdown-toggle profile-pic" data-toggle="dropdown" href="javascript:void(0)"><img alt="" class="img-circle profile-image" src="` +
4031      user.data.user.image +
4032      `" width="36"><b class="hidden-xs">` +
4033      user.data.user.username +
4034      `</b><span class="caret"></span></a>
4035				<ul class="dropdown-menu dropdown-user animated flipInY">
4036					<li>
4037						<div class="dw-user-box">
4038							<div class="u-img"><img alt="user" src="` +
4039      user.data.user.image +
4040      `"></div>
4041							<div class="u-text"><h4>` +
4042      user.data.user.username +
4043      `</h4><p class="text-muted">` +
4044      user.data.user.email +
4045      `</p><p class="text-muted">` +
4046      user.data.user.group +
4047      `</p></div>
4048						</div>
4049					</li>
4050					<li class="divider" role="separator"></li>
4051					<li class="append-menu"><a class="inline-popups" href="#account-area" data-effect="mfp-zoom-out"><i class="ti-settings fa-fw"></i> <span lang="en">Account Settings</span></a></li>
4052					<li class="divider" role="separator"></li>
4053					<li><a href="javascript:void(0)" onclick="lock();"><i class="ti-lock fa-fw"></i> <span lang="en">Lock Screen</span></a></li>
4054					${showDebug}
4055					<li><a href="javascript:void(0)" onclick="logout();"><i class="fa fa-sign-out fa-fw"></i> <span lang="en">Logout</span></a></li>
4056				</ul><!-- /.dropdown-user -->
4057			</li><!-- /.dropdown -->
4058		`;
4059    sideMenu +=
4060      `
4061		<li class="user-pro">
4062			<a href="#" class="waves-effect">
4063				<img src="` +
4064      user.data.user.image +
4065      `" alt="user-img" class="img-circle">
4066				<span class="hide-menu">` +
4067      user.data.user.username +
4068      `<span class="fa arrow"></span></span>
4069			</a>
4070			<ul class="nav nav-second-level collapse" aria-expanded="false" style="height: 0px;">
4071				<li class="append-menu"><a class="inline-popups" href="#account-area" data-effect="mfp-zoom-out"><i class="ti-settings fa-fw"></i> <span lang="en">Account Settings</span></a></li>
4072				<li><a href="javascript:void(0)" onclick="lock();"><i class="ti-lock fa-fw"></i> <span lang="en">Lock Screen</span></a></li>
4073				${showDebug}
4074				<li><a href="javascript:void(0)" onclick="logout();"><i class="fa fa-sign-out fa-fw"></i> <span lang="en">Logout</span></a></li>
4075			</ul>
4076		</li>
4077		`;
4078  } else {
4079    menuList +=
4080      `
4081			<li class="dropdown">
4082					<a class="dropdown-toggle profile-pic" data-toggle="dropdown" href="javascript:void(0)"><img alt="" class="img-circle profile-image" src="` +
4083      user.data.user.image +
4084      `" width="36"><b class="hidden-xs">` +
4085      user.data.user.username +
4086      `</b><span class="caret"></span></a>
4087					<ul class="dropdown-menu dropdown-user animated flipInY">
4088						<li>
4089							<div class="dw-user-box">
4090								<div class="u-img"><img alt="user" src="` +
4091      user.data.user.image +
4092      `"></div>
4093								<div class="u-text"><h4>` +
4094      user.data.user.username +
4095      `</h4></div>
4096							</div>
4097						</li>
4098						<li class="divider" role="separator"></li>
4099						<li class="append-menu"><a href="javascript:void(0)" class="show-login"><i class="fa fa-sign-in fa-fw"></i> <span lang="en">Login/Register</span></a></li>
4100					</ul><!-- /.dropdown-user -->
4101				</li><!-- /.dropdown -->
4102		`;
4103    sideMenu +=
4104      `
4105		<li class="user-pro">
4106			<a href="#" class="waves-effect">
4107				<img src="` +
4108      user.data.user.image +
4109      `" alt="user-img" class="img-circle">
4110				<span class="hide-menu">` +
4111      user.data.user.username +
4112      `<span class="fa arrow"></span></span>
4113			</a>
4114			<ul class="nav nav-second-level collapse" aria-expanded="false" style="height: 0px;">
4115				<li class="append-menu"><a href="javascript:void(0)" class="show-login"><i class="fa fa-sign-in fa-fw"></i> <span lang="en">Login/Register</span></a></li>
4116			</ul>
4117		</li>
4118		`;
4119  }
4120  $(menuList).appendTo(".navbar-right").html;
4121  //$(sideMenu).appendTo('#side-menu').html;
4122  //message("",window.lang.translate('Welcome')+" "+user.data.user.username,activeInfo.settings.notifications.position,"#FFF","success","3500");
4123  console.info(
4124    "%c " +
4125      window.lang.translate("Welcome") +
4126      " %c ".concat(user.data.user.username, " "),
4127    "color: white; background: #AD80FD; font-weight: 700;",
4128    "color: #AD80FD; background: white; font-weight: 700;"
4129  );
4130}
4131function menuExtraTabs() {
4132  return [
4133    {
4134      id: 99999991,
4135      type: 2,
4136      group_id: 1,
4137      name: "Github Repo",
4138      access_url: "https://github.com/causefx/organizr",
4139      url: "https://github.com/causefx/organizr",
4140      image: "fontawesome::github",
4141      active: activeInfo.settings.menuLink.githubMenuLink,
4142      ping_url: null,
4143    },
4144    {
4145      id: 99999992,
4146      type: 1,
4147      group_id: 1,
4148      name: "Organizr Support",
4149      access_url: "https://organizr.app/support",
4150      url: "https://organizr.app/support",
4151      image: "fontawesome::life-ring",
4152      active: activeInfo.settings.menuLink.organizrSupportMenuLink,
4153      ping_url: null,
4154    },
4155    {
4156      id: 99999993,
4157      type: 2,
4158      group_id: 1,
4159      name: "Organizr Docs",
4160      access_url: "https://docs.organizr.app",
4161      url: "https://docs.organizr.app",
4162      image: "simpleline::docs",
4163      active: activeInfo.settings.menuLink.organizrDocsMenuLink,
4164      ping_url: null,
4165    },
4166    {
4167      id: 99999994,
4168      type: 1,
4169      group_id: 1,
4170      name: "Feature Request",
4171      access_url: "https://feature.organizr.app",
4172      url: "https://feature.organizr.app",
4173      image: "simpleline::arrow-up-circle",
4174      active: activeInfo.settings.menuLink.organizrFeatureRequestLink,
4175      ping_url: null,
4176    },
4177  ];
4178}
4179function menuExtras(active) {
4180  let adminMenu = '<li class="devider"></li>';
4181  let extraOrganizrLinks = menuExtraTabs();
4182  activeInfo.tabs = [].concat(activeInfo.tabs, menuExtraTabs());
4183
4184  $.each(extraOrganizrLinks, function (i, v) {
4185    tabInformation[v.id] = {
4186      id: v.id,
4187      name: cleanClass(v.name),
4188      active: false,
4189      loaded: false,
4190      increments: 0,
4191      tabInfo: v,
4192    };
4193    if (v.type == 1) {
4194      let frame = buildFrameContainer(v.id);
4195      $(frame).appendTo($(".iFrame-listing"));
4196    }
4197    adminMenu +=
4198      activeInfo.user.groupID <= v.group_id && v.active
4199        ? buildMenuList(v.id)
4200        : "";
4201  });
4202  if (active === true) {
4203    return activeInfo.settings.menuLink.organizrSignoutMenuLink
4204      ? `
4205			<li class="devider"></li>
4206			<li id="sign-out"><a class="waves-effect" onclick="logout();"><i class="fa fa-sign-out fa-fw"></i> <span class="hide-menu" lang="en">Logout</span></a></li>
4207		` + adminMenu
4208      : "" + adminMenu;
4209  } else {
4210    return activeInfo.settings.menuLink.organizrSignoutMenuLink
4211      ? `
4212			<li class="devider"></li>
4213			<li id="menu-login"><a class="waves-effect show-login" href="javascript:void(0)"><i class="mdi mdi-login fa-fw"></i> <span class="hide-menu" lang="en">Login/Register</span></a></li>
4214		`
4215      : "";
4216  }
4217}
4218function categoryProcess(arrayItems) {
4219  var menuList = "";
4220  let categoryIn = activeInfo.settings.misc.expandCategoriesByDefault
4221    ? "in"
4222    : "";
4223  let categoryActive = activeInfo.settings.misc.expandCategoriesByDefault
4224    ? "active"
4225    : "";
4226  let categoryExpanded = activeInfo.settings.misc.expandCategoriesByDefault
4227    ? "true"
4228    : "false";
4229  if (
4230    Array.isArray(arrayItems["data"]["categories"]) &&
4231    Array.isArray(arrayItems["data"]["tabs"])
4232  ) {
4233    $.each(arrayItems["data"]["categories"], function (i, v) {
4234      if (v.count !== 0 && v.category_id !== 0) {
4235        menuList +=
4236          `
4237					<li class="allGroupsList ` +
4238          categoryActive +
4239          `" data-group-name="` +
4240          cleanClass(v.category) +
4241          `">
4242						<a class="waves-effect" href="javascript:void(0)">` +
4243          iconPrefix(v.image) +
4244          `<span class="hide-menu">` +
4245          v.category +
4246          ` <span class="fa arrow"></span> <span class="label label-rouded label-inverse pull-right">` +
4247          v.count +
4248          `</span></span><div class="menu-category-ping" data-good="0" data-bad="0"></div></a>
4249						<ul class="nav nav-second-level category-` +
4250          v.category_id +
4251          ` collapse ` +
4252          categoryIn +
4253          `" aria-expanded="` +
4254          categoryExpanded +
4255          `"></ul>
4256					</li>
4257				`;
4258      }
4259    });
4260    $(menuList).appendTo($("#side-menu"));
4261  }
4262}
4263function buildFrame(id, split = null) {
4264  let extra = split ? "right-" : "";
4265  let tabInfo = findTab(id);
4266  if (!tabInfo) {
4267    organizrConsole("Build Frame", "No Tab Info Found... Id: " + id, "error");
4268    return false;
4269  }
4270  var sandbox = activeInfo.settings.misc.sandbox;
4271  sandbox = sandbox.replace(/,/gi, " ");
4272  sandbox = sandbox ? ' sandbox="' + sandbox + '"' : "";
4273  var allow = activeInfo.settings.misc.iframeAllow;
4274  allow = allow.replace(/,/gi, "; ");
4275  allow = allow ? ' allow="' + allow + '"' : "";
4276  return (
4277    `
4278		<iframe ` +
4279    allow +
4280    ` frameborder="0" id="frame-` +
4281    extra +
4282    id +
4283    `" ` +
4284    sandbox +
4285    ` scrolling="auto" src="` +
4286    tabInfo.access_url +
4287    `" class="iframe"></iframe>
4288	`
4289  );
4290}
4291function buildFrameContainer(id, split = null) {
4292  let extra = split ? "right-" : "";
4293  return (
4294    `<div id="container-` +
4295    extra +
4296    id +
4297    `" class="frame-container frame-${id} hidden" ></div>`
4298  );
4299}
4300function buildInternalContainer(id, split = null) {
4301  let extra = split ? "right-" : "";
4302  return (
4303    `<div id="internal-` +
4304    extra +
4305    id +
4306    `" class="internal-container frame-${id} hidden"></div>`
4307  );
4308}
4309function buildMenuList(id) {
4310  let tabInfo = findTab(id);
4311  if (!tabInfo) {
4312    organizrConsole(
4313      "Build Menu List",
4314      "No Tab Info Found... Id: " + id,
4315      "error"
4316    );
4317    return false;
4318  }
4319  let name = tabInfo.name;
4320  let icon = tabInfo.image;
4321  let ping = typeof tabInfo.ping_url !== "undefined" ? tabInfo.ping_url : null;
4322  ping =
4323    ping !== null && ping !== ""
4324      ? `<small class="menu-` +
4325        cleanClass(ping) +
4326        `-ping-ms hidden-xs label label-rouded label-inverse pull-right pingTime hidden">
4327</small><div class="menu-` +
4328        cleanClass(ping) +
4329        `-ping" data-tab-name="` +
4330        name +
4331        `" data-previous-state=""></div>`
4332      : "";
4333  return (
4334    `<li class="allTabsList" id="menu-${id}" data-tab-id="${id}"><a class="waves-effect"  href="javascript:void(0)" onclick="tabActions(event,'${id}');" onauxclick="tabActions(event,'${id}');">` +
4335    iconPrefix(icon) +
4336    `<span class="hide-menu elip sidebar-tabName">` +
4337    name +
4338    `</span>` +
4339    ping +
4340    `</a></li>`
4341  );
4342}
4343function tabProcess(arrayItems) {
4344  var iFrameList = "";
4345  var internalList = "";
4346  var defaultTabId = null;
4347  if (
4348    Array.isArray(arrayItems["data"]["tabs"]) &&
4349    arrayItems["data"]["tabs"].length > 0
4350  ) {
4351    $.each(arrayItems["data"]["tabs"], function (i, v) {
4352      if (v.enabled === 1 && v.access_url) {
4353        tabInformation[v.id] = {
4354          id: v.id,
4355          name: cleanClass(v.name),
4356          active: false,
4357          loaded: false,
4358          increments: 0,
4359          tabInfo: v,
4360        };
4361        switch (v.timeout) {
4362          case 1:
4363          case "1":
4364            tabActionsList["close"].push({
4365              id: v.id,
4366              tab: cleanClass(v.name),
4367              action_ms: v.timeout_ms,
4368            });
4369            break;
4370          case 2:
4371          case "2":
4372            tabActionsList["refresh"].push({
4373              id: v.id,
4374              tab: cleanClass(v.name),
4375              action_ms: v.timeout_ms,
4376            });
4377            break;
4378          default:
4379          //nada
4380        }
4381        if (v.default === 1) {
4382          defaultTabId = v.id;
4383        }
4384        var menuList = buildMenuList(v.id);
4385        if (v.category_id === 0) {
4386          if (activeInfo.settings.misc.unsortedTabs === "top") {
4387            $(menuList).prependTo($("#side-menu"));
4388          } else if (activeInfo.settings.misc.unsortedTabs === "bottom") {
4389            $(menuList).appendTo($("#side-menu"));
4390          }
4391        } else {
4392          if (activeInfo.settings.misc.unsortedTabs === "top") {
4393            $(menuList).prependTo($(".category-" + v.category_id));
4394          } else if (activeInfo.settings.misc.unsortedTabs === "bottom") {
4395            $(menuList).appendTo($(".category-" + v.category_id));
4396          }
4397        }
4398        switch (v.type) {
4399          case 0:
4400          case "0":
4401          case "internal":
4402            internalList = buildInternalContainer(v.id);
4403            $(internalList).appendTo($(".internal-listing"));
4404            internalList = buildInternalContainer(v.id, true);
4405            $(internalList).appendTo($(".internal-listing-right"));
4406            if (v.preload) {
4407              var newTab = $("#internal-" + v.id);
4408              organizrConsole(
4409                "Tab Function",
4410                "Preloading new tab for: " + cleanClass(v.name)
4411              );
4412              $("#menu-" + v.id + " a")
4413                .children()
4414                .addClass("tabLoaded");
4415              newTab.addClass("loaded");
4416              loadInternal(v.id);
4417            }
4418            break;
4419          case 1:
4420          case "1":
4421          case "iframe":
4422            iFrameList = buildFrameContainer(v.id);
4423            $(iFrameList).appendTo($(".iFrame-listing"));
4424            iFrameList = buildFrameContainer(v.id, true);
4425            $(iFrameList).appendTo($(".iFrame-listing-right"));
4426            if (v.preload) {
4427              var newTab = $("#container-" + v.id);
4428              organizrConsole(
4429                "Tab Function",
4430                "Preloading new tab for: " + cleanClass(v.name)
4431              );
4432              $("#menu-" + v.id + " a")
4433                .children()
4434                .addClass("tabLoaded");
4435              newTab.addClass("loaded");
4436              $(buildFrame(v.id)).appendTo(newTab);
4437            }
4438            break;
4439          case 2:
4440          case 3:
4441          case "2":
4442          case "3":
4443          case "_blank":
4444          case "popout":
4445            break;
4446          default:
4447            organizrConsole("Tab Function", "Action not set", "error");
4448        }
4449      }
4450    });
4451    $("#side-menu").metisMenu({
4452      toggle: activeInfo.settings.misc.autoCollapseCategories,
4453    });
4454    getDefault(defaultTabId);
4455  } else {
4456    noTabs(arrayItems);
4457  }
4458  $(menuExtras(arrayItems.data.user.loggedin)).appendTo($("#side-menu"));
4459}
4460function buildLogin() {
4461  swapDisplay("login");
4462  closeSideMenu();
4463  removeMenuActive();
4464  $("#menu-login a").addClass("active");
4465  organizrAPI2("GET", "api/v2/page/login")
4466    .success(function (data) {
4467      try {
4468        var response = data.response;
4469        organizrConsole("Organizr Function", "Opening Login Page");
4470        $(".login-area").html(response.data);
4471        setHash("OrganizrLogin");
4472      } catch (e) {
4473        organizrCatchError(e, data);
4474      }
4475    })
4476    .fail(function (xhr) {
4477      OrganizrApiError(xhr, "Login Error");
4478    });
4479  $("#preloader").fadeOut();
4480}
4481function buildLockscreen() {
4482  $("#preloader").fadeIn();
4483  closeSideMenu();
4484  organizrAPI2("GET", "api/v2/page/lockscreen")
4485    .success(function (data) {
4486      try {
4487        var response = data.response;
4488        organizrConsole("Organizr Function", "Adding Lockscreen");
4489        $(response.data).appendTo($("body"));
4490      } catch (e) {
4491        organizrCatchError(e, data);
4492      }
4493    })
4494    .fail(function (xhr) {
4495      OrganizrApiError(xhr);
4496    });
4497  $("#preloader").fadeOut();
4498}
4499function buildSplashScreenItem(arrayItems) {
4500  var splashList = "";
4501  if (
4502    Array.isArray(arrayItems["data"]["tabs"]) &&
4503    arrayItems["data"]["tabs"].length > 0
4504  ) {
4505    arrayItems["data"]["tabs"].sort(
4506      (a, b) => parseFloat(a.order) - parseFloat(b.order)
4507    );
4508    $.each(arrayItems["data"]["tabs"], function (i, v) {
4509      if (v.enabled === 1 && v.splash === 1 && v.access_url) {
4510        var image = iconPrefixSplash(v.image);
4511        if (image.indexOf(".") !== -1) {
4512          var dataSrc = 'data-src="' + iconPrefixSplash(v.image) + '"';
4513          var nonImage = "";
4514        } else {
4515          var dataSrc = "";
4516          var nonImage =
4517            '<span class="text-uppercase badge bg-org splash-badge">' +
4518            image +
4519            "</span>";
4520        }
4521        splashList += `
4522                <div class="col-xs-12 col-sm-3 col-md-3 col-lg-3 col-xl-2 mouse hvr-grow m-b-20" id="menu-${cleanClass(
4523                  v.name
4524                )}" type="${v.type}" data-url="${
4525          v.access_url
4526        }" onclick="tabActions(event,'${v.id}');">
4527                    <div class="homepage-drag fc-event bg-org lazyload" ${dataSrc}>
4528                        ${nonImage}
4529                        <span class="homepage-text">&nbsp; ${v.name}</span>
4530                    </div>
4531                </div>
4532                `;
4533      }
4534    });
4535  }
4536  return splashList !== "" ? splashList : false;
4537}
4538function buildSplashScreen(json) {
4539  let hiddenSplash = directToHash ? "hidden" : "in";
4540  var items = buildSplashScreenItem(json);
4541  var menu =
4542    '<li ><a href="javascript:void(0)" onclick="$(\'.splash-screen\').removeClass(\'hidden\').addClass(\'in\')"><i class="ti-layout-grid2 fa-fw"></i> <span lang="en">Splash Page</span></a></li>';
4543  if (items) {
4544    closeSideMenu();
4545    organizrConsole("Organizr Function", "Adding Splash Screen");
4546    var splash =
4547      `
4548        <section id="splashScreen" class="lock-screen splash-screen default-scroller fade ${hiddenSplash}">
4549            <div class="row p-20 flexbox">` +
4550      items +
4551      `</div>
4552            <div class="row p-20 p-t-0 flexbox">
4553                <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 col-xl-12 mouse hvr-wobble-bottom bottom-close-splash" onclick="$('.splash-screen').addClass('hidden').removeClass('in')">
4554                    <div class="homepage-drag fc-event bg-danger lazyload"  data-src="">
4555                        <span class="homepage-text">&nbsp; Close Splash</span>
4556                    </div>
4557                </div>
4558            </div>
4559        </section>
4560        `;
4561    $(splash).appendTo($("body"));
4562    $(".append-menu").after(menu);
4563  }
4564}
4565function buildUserGroupSelect(array, userID, groupID) {
4566  var groupSelect = "";
4567  var selected = "";
4568  var disabled = "";
4569  if (groupID == 0 && userID == 1) {
4570    disabled = "disabled";
4571  }
4572  $.each(array, function (i, v) {
4573    selected = "";
4574    if (v.group_id == groupID) {
4575      selected = "selected";
4576    }
4577    var selectDisable = v.group_id == 0 || v.group_id == 999 ? "disabled" : "";
4578    groupSelect +=
4579      "<option " +
4580      selected +
4581      " " +
4582      selectDisable +
4583      ' value="' +
4584      v.group_id +
4585      '">' +
4586      v.group +
4587      "</option>";
4588  });
4589  return (
4590    '<td><select name="userGroupSelect" class="form-control userGroupSelect" ' +
4591    disabled +
4592    ">" +
4593    groupSelect +
4594    "</select></td>"
4595  );
4596}
4597function buildTabGroupSelect(array, tabID, groupID, type) {
4598  var groupSelect = "";
4599  var selected = "";
4600  let name = type == "tabGroupSelectMax" ? "group_id_max" : "group_id";
4601  $.each(array, function (i, v) {
4602    selected = "";
4603    if (v.group_id == groupID) {
4604      selected = "selected";
4605    }
4606    groupSelect +=
4607      "<option " +
4608      selected +
4609      ' value="' +
4610      v.group_id +
4611      '">' +
4612      v.group +
4613      "</option>";
4614  });
4615  return (
4616    '<td><select name="tab[' +
4617    tabID +
4618    "]." +
4619    name +
4620    '" class="form-control ' +
4621    type +
4622    '">' +
4623    groupSelect +
4624    "</select></td>"
4625  );
4626}
4627function buildTabTypeSelect(tabID, typeID, disabled) {
4628  var array = [
4629    {
4630      type_id: 0,
4631      type: "Organizr",
4632    },
4633    {
4634      type_id: 1,
4635      type: "iFrame",
4636    },
4637    {
4638      type_id: 2,
4639      type: "New Window",
4640    },
4641  ];
4642  var typeSelect = "";
4643  var selected = "";
4644  disabled = disabled == "disabled" && typeID !== 0 ? null : disabled;
4645  $.each(array, function (i, v) {
4646    selected = "";
4647    if (v.type_id == typeID) {
4648      selected = "selected";
4649    }
4650    var disabledAttr =
4651      disabled === "disabled" && v.type !== "Internal" ? "disabled" : "";
4652    typeSelect +=
4653      "<option " +
4654      selected +
4655      ' value="' +
4656      v.type_id +
4657      '" ' +
4658      disabledAttr +
4659      ">" +
4660      v.type +
4661      "</option>";
4662  });
4663  return (
4664    '<td><select name="tab[' +
4665    tabID +
4666    '].type" class="form-control tabTypeSelect">' +
4667    typeSelect +
4668    "</select></td>"
4669  );
4670}
4671function buildTabCategorySelect(array, tabID, categoryID) {
4672  var categorySelect = "";
4673  var selected = "";
4674  $.each(array, function (i, v) {
4675    selected = "";
4676    if (v.category_id == categoryID) {
4677      selected = "selected";
4678    }
4679    categorySelect +=
4680      "<option " +
4681      selected +
4682      ' value="' +
4683      v.category_id +
4684      '">' +
4685      v.category +
4686      "</option>";
4687  });
4688  return (
4689    '<td><select name="tab[' +
4690    tabID +
4691    '].category_id" class="form-control tabCategorySelect">' +
4692    categorySelect +
4693    "</select></td>"
4694  );
4695}
4696function buildUserManagementItem(array) {
4697  var userList = "";
4698  $.each(array.users, function (i, v) {
4699    var disabledDelete =
4700      v.group_id == 999 || v.group_id == 0 ? "disabled" : "deleteUser";
4701    userList +=
4702      `
4703		<tr class="userManagement" data-id="` +
4704      v.id +
4705      `" data-username="` +
4706      v.username +
4707      `" data-group="` +
4708      v.group +
4709      `" data-email="` +
4710      v.email +
4711      `">
4712			<td class="text-center el-element-overlay">
4713				<div class="el-card-item p-0">
4714					<div class="el-card-avatar el-overlay-1 m-0">
4715						<img alt="user-img" class="img-circle" src="` +
4716      v.image +
4717      `" width="45">
4718						<div class="el-overlay">
4719							<ul class="el-info">
4720								` +
4721      v.id +
4722      `
4723							</ul>
4724						</div>
4725					</div>
4726				</div>
4727			</td>
4728			<td>` +
4729      v.username +
4730      `
4731				<br/><span class="text-muted">` +
4732      v.email +
4733      `</span></td>
4734			<td>` +
4735      moment(v.register_date).format("ll") +
4736      `
4737				<br/><span class="text-muted">` +
4738      moment(v.register_date).format("LT") +
4739      `</span></td>
4740			` +
4741      buildUserGroupSelect(array.groups, v.id, v.group_id) +
4742      `
4743			<td><button type="button" class="btn btn-info btn-outline btn-circle btn-lg m-r-5 editUserButton popup-with-form" href="#edit-user-form" data-effect="mfp-3d-unfold"><i class="ti-pencil-alt"></i></button></td>
4744			<td><button type="button" class="btn btn-info btn-outline btn-circle btn-lg m-r-20 emailUser"><i class="ti-email"></i></button></td>
4745			<td><button type="button" class="btn btn-danger btn-outline btn-circle btn-lg m-r-5 ` +
4746      disabledDelete +
4747      `"><i class="ti-trash"></i></button></td>
4748		</tr>
4749		`;
4750  });
4751  return userList;
4752}
4753function buildGroupManagementItem(array) {
4754  var userList = "";
4755  $.each(array.groups, function (i, v) {
4756    var userCount = array.users.reduce(function (n, group) {
4757      return n + (group.group_id == v.group_id);
4758    }, 0);
4759    var disabledDefault =
4760      v.group_id == 0 || v.group_id == 999 ? "disabled" : "";
4761    var disabledDelete =
4762      userCount > 0 || v.default == 1 || v.group_id == 999 || v.group_id <= 1
4763        ? "disabled"
4764        : "";
4765    var defaultIcon =
4766      v.default == 1 ? "icon-user-following" : "icon-user-follow";
4767    var defaultColor = v.default == 1 ? "btn-info disabled" : "btn-warning";
4768    userList +=
4769      `
4770		<tr class="userManagement" data-id="` +
4771      v.id +
4772      `" data-group-id="` +
4773      v.group_id +
4774      `" data-group="` +
4775      v.group +
4776      `" data-default="` +
4777      tof(v.default) +
4778      `" data-image="` +
4779      v.image +
4780      `" data-user-count="` +
4781      userCount +
4782      `">
4783			<td class="text-center el-element-overlay">
4784				<div class="el-card-item p-0">
4785					<div class="el-card-avatar el-overlay-1 m-0">
4786						<div class="tabEditorIcon">` +
4787      iconPrefix(v.image) +
4788      `</div>
4789						<div class="el-overlay">
4790							<ul class="el-info">
4791								` +
4792      v.group_id +
4793      `
4794							</ul>
4795						</div>
4796					</div>
4797				</div>
4798			</td>
4799			<td>` +
4800      v.group +
4801      `</td>
4802			<td>` +
4803      userCount +
4804      `</td>
4805			<td><button type="button" class="btn ` +
4806      defaultColor +
4807      ` btn-outline btn-circle btn-lg m-r-5 changeDefaultGroup" ` +
4808      disabledDefault +
4809      `><i class="` +
4810      defaultIcon +
4811      `"></i></button></td>
4812			<td><button type="button" class="btn btn-info btn-outline btn-circle btn-lg m-r-5 editGroupButton popup-with-form" href="#edit-group-form" data-effect="mfp-3d-unfold"><i class="ti-pencil-alt"></i></button></td>
4813			<td><button type="button" class="btn btn-danger btn-outline btn-circle btn-lg m-r-5 deleteUserGroup" ` +
4814      disabledDelete +
4815      `><i class="ti-trash"></i></button></td>
4816		</tr>
4817		`;
4818  });
4819  return userList;
4820}
4821function buildCategoryEditorItem(array) {
4822  var categoryList = "";
4823  $.each(array.categories, function (i, v) {
4824    var tabCount = array.tabs.reduce(function (n, category) {
4825      return n + (category.category_id == v.category_id);
4826    }, 0);
4827    var disabledDefault = v.default == 1 ? "disabled" : "";
4828    var disabledDelete =
4829      tabCount > 0 || v.default == 1 || v.category_id == 0 ? "disabled" : "";
4830    var defaultIcon =
4831      v.default == 1 ? "icon-user-following" : "icon-user-follow";
4832    var defaultColor = v.default == 1 ? "btn-info disabled" : "btn-warning";
4833    categoryList +=
4834      `
4835		<tr class="categoryEditor" data-id="` +
4836      v.id +
4837      `" data-order="` +
4838      v.order +
4839      `" data-category-id="` +
4840      v.category_id +
4841      `" data-name="` +
4842      v.category +
4843      `" data-default="` +
4844      tof(v.default) +
4845      `" data-image="` +
4846      v.image +
4847      `" data-tab-count="` +
4848      tabCount +
4849      `">
4850			<input type="hidden" class="form-control order" name="category[` +
4851      v.id +
4852      `].order" value="` +
4853      v.order +
4854      `">
4855			<input type="hidden" class="form-control" name="category[` +
4856      v.id +
4857      `].originalOrder" value="` +
4858      v.order +
4859      `">
4860			<input type="hidden" class="form-control" name="category[` +
4861      v.id +
4862      `].name" value="` +
4863      v.category +
4864      `">
4865			<input type="hidden" class="form-control" name="category[` +
4866      v.id +
4867      `].id" value="` +
4868      v.id +
4869      `">
4870			<td class="text-center el-element-overlay">
4871				<div class="el-card-item p-0">
4872					<div class="el-card-avatar el-overlay-1 m-0">
4873						<div class="tabEditorIcon">` +
4874      iconPrefix(v.image) +
4875      `</div>
4876						<div class="el-overlay bg-org">
4877							<ul class="el-info">
4878								<i class="fa fa-bars"></i>
4879							</ul>
4880						</div>
4881					</div>
4882				</div>
4883			</td>
4884			<td>` +
4885      v.category +
4886      `</td>
4887			<td style="text-align:center">` +
4888      tabCount +
4889      `</td>
4890			<td style="text-align:center"><button type="button" class="btn ` +
4891      defaultColor +
4892      ` btn-outline btn-circle btn-lg m-r-5 changeDefaultCategory" ` +
4893      disabledDefault +
4894      `><i class="` +
4895      defaultIcon +
4896      `"></i></button></td>
4897			<td style="text-align:center"><button type="button" class="btn btn-info btn-outline btn-circle btn-lg m-r-5 editCategoryButton popup-with-form" href="#edit-category-form" data-effect="mfp-3d-unfold"><i class="ti-pencil-alt"></i></button></td>
4898			<td style="text-align:center"><button type="button" class="btn btn-danger btn-outline btn-circle btn-lg m-r-5 deleteCategory" ` +
4899      disabledDelete +
4900      `><i class="ti-trash"></i></button></td>
4901		</tr>
4902		`;
4903  });
4904  return categoryList;
4905}
4906function buildTabEditorItem(array) {
4907  var tabList = "";
4908  $.each(array.tabs, function (i, v) {
4909    let deleteDisabled = "deleteTab";
4910    let buttonDisabled = "";
4911    let typeDisabled = "";
4912    if (v.url !== null) {
4913      deleteDisabled =
4914        v.url.indexOf("/page/settings") > 0 ? "disabled" : "deleteTab";
4915      buttonDisabled = v.url.indexOf("/page/settings") > 0 ? "disabled" : "";
4916      typeDisabled = v.url.indexOf("/v2/page/") > 0 ? "disabled" : "";
4917    }
4918    tabList +=
4919      `
4920		<tr class="tabEditor" data-order="` +
4921      v.order +
4922      `" data-original-order="` +
4923      v.order +
4924      `" data-id="` +
4925      v.id +
4926      `" data-group-id="` +
4927      v.group_id +
4928      `" data-category-id="` +
4929      v.category_id +
4930      `" data-name="` +
4931      v.name +
4932      `" data-url="` +
4933      v.url +
4934      `" data-local-url="` +
4935      v.url_local +
4936      `" data-ping-url="` +
4937      v.ping_url +
4938      `" data-image="` +
4939      v.image +
4940      `" data-tab-action-type="` +
4941      v.timeout +
4942      `" data-tab-action-time="` +
4943      v.timeout_ms +
4944      `">
4945			<input type="hidden" class="form-control" name="tab[` +
4946      v.id +
4947      `].id" value="` +
4948      v.id +
4949      `">
4950			<input type="hidden" class="form-control order" name="tab[` +
4951      v.id +
4952      `].order" value="` +
4953      v.order +
4954      `">
4955			<input type="hidden" class="form-control" name="tab[` +
4956      v.id +
4957      `].originalOrder" value="` +
4958      v.order +
4959      `">
4960			<td class="mouse-grab sort-tabs-handle">
4961				<i class="icon-options-vertical m-r-5"></i> 
4962				<!-- May use later on
4963				<div class="btn-group dropside visible-xs">
4964					<button aria-expanded="false" data-toggle="dropdown" class="btn btn-default btn-outline dropdown-toggle waves-effect waves-light" type="button"> <i class="icon-options-vertical m-r-5"></i> <span class="caret"></span></button>
4965					<ul role="menu" class="dropdown-menu">
4966						<li><a href="#"><i class="fa fa-angle-double-up"></i></a></li>
4967						<li><a href="#"><i class="fa fa-angle-up"></i></a></li>
4968						<li><a href="#"><i class="fa fa-angle-double-down"></i></a></li>
4969						<li><a href="#"><i class="fa fa-angle-down"></i></a></li>
4970					</ul>
4971				</div>
4972				-->
4973			</td>
4974			<td style="text-align:center" class="text-center el-element-overlay">
4975				<div class="el-card-item p-0">
4976					<div class="el-card-avatar el-overlay-1 m-0 tooltip-info" data-toggle="tooltip" data-placement="top" title="" data-original-title="${v.id}">
4977						<div class="tabEditorIcon">` +
4978      iconPrefix(v.image) +
4979      `</div>
4980					</div>
4981				</div>
4982			</td>
4983			<td><span class="tooltip-info" data-toggle="tooltip" data-placement="right" title="" data-original-title="` +
4984      v.url +
4985      `">` +
4986      v.name +
4987      `</span><span id="checkTabHomepageItem-` +
4988      v.id +
4989      `" data-url="` +
4990      v.url +
4991      `" data-url-local="` +
4992      v.url_local +
4993      `" data-name="` +
4994      v.name +
4995      `" class="checkTabHomepageItem mouse label label-rouded label-inverse pull-right"></span></td>
4996			` +
4997      buildTabCategorySelect(array.categories, v.id, v.category_id) +
4998      `
4999			` +
5000      buildTabGroupSelect(array.groups, v.id, v.group_id, "tabGroupSelectMin") +
5001      `
5002			` +
5003      buildTabGroupSelect(
5004        array.groups,
5005        v.id,
5006        v.group_id_max,
5007        "tabGroupSelectMax"
5008      ) +
5009      `
5010			` +
5011      buildTabTypeSelect(v.id, v.type, typeDisabled) +
5012      `
5013			<td style="text-align:center"><div class="radio radio-purple"><input onclick="radioLoop(this);" type="radio" class="defaultSwitch" id="tab[` +
5014      v.id +
5015      `].default" name="tab[` +
5016      v.id +
5017      `].default" value="true" ` +
5018      tof(v.default, "c") +
5019      `><label for="tab[` +
5020      v.id +
5021      `].default"></label></div></td>
5022
5023			<td style="text-align:center"><input ` +
5024      buttonDisabled +
5025      ` type="checkbox" class="js-switch enabledSwitch ` +
5026      buttonDisabled +
5027      `" data-size="small" data-color="#99d683" data-secondary-color="#f96262" name="tab[` +
5028      v.id +
5029      `].enabled" value="true" ` +
5030      tof(v.enabled, "c") +
5031      `/><input type="hidden" class="form-control" name="tab[` +
5032      v.id +
5033      `].enabled" value="false"></td>
5034			<td style="text-align:center"><input type="checkbox" class="js-switch splashSwitch" data-size="small" data-color="#99d683" data-secondary-color="#f96262" name="tab[` +
5035      v.id +
5036      `].splash" value="true" ` +
5037      tof(v.splash, "c") +
5038      `/><input type="hidden" class="form-control" name="tab[` +
5039      v.id +
5040      `].splash" value="false"></td>
5041			<td style="text-align:center"><input type="checkbox" class="js-switch pingSwitch" data-size="small" data-color="#99d683" data-secondary-color="#f96262" name="tab[` +
5042      v.id +
5043      `].ping" value="true" ` +
5044      tof(v.ping, "c") +
5045      `/><input type="hidden" class="form-control" name="tab[` +
5046      v.id +
5047      `].ping" value="false"></td>
5048			<td style="text-align:center"><input type="checkbox" class="js-switch preloadSwitch" data-size="small" data-color="#99d683" data-secondary-color="#f96262" name="tab[` +
5049      v.id +
5050      `].preload" value="true" ` +
5051      tof(v.preload, "c") +
5052      `/><input type="hidden" class="form-control" name="tab[` +
5053      v.id +
5054      `].preload" value="false"></td>
5055			<td style="text-align:center"><input type="checkbox" class="js-switch addToAdminSwitch" data-size="small" data-color="#99d683" data-secondary-color="#f96262" name="tab[` +
5056      v.id +
5057      `].add_to_admin" value="true" ` +
5058      tof(v.add_to_admin, "c") +
5059      `/><input type="hidden" class="form-control" name="tab[` +
5060      v.id +
5061      `].add_to_admin" value="false"></td>
5062			<td style="text-align:center"><button type="button" class="btn btn-info btn-outline btn-circle btn-lg m-r-5 editTabButton popup-with-form" onclick="editTabForm('` +
5063      v.id +
5064      `')" href="#edit-tab-form" data-effect="mfp-3d-unfold"><i class="ti-pencil-alt"></i></button></td>
5065			<td style="text-align:center"><button type="button" class="btn btn-danger btn-outline btn-circle btn-lg m-r-5 ` +
5066      deleteDisabled +
5067      `"><i class="ti-trash"></i></button></td>
5068		</tr>
5069		`;
5070  });
5071  return tabList;
5072}
5073function editTabForm(id) {
5074  organizrAPI2("GET", "api/v2/tabs/" + id, true)
5075    .success(function (data) {
5076      try {
5077        let response = data.response;
5078        $(".tabIconImageList").val(null).trigger("change");
5079        $(".tabIconIconList").val(null).trigger("change");
5080        $("#edit-tab-form [name=name]").val(response.data.name);
5081        $("#originalTabName").html(response.data.name);
5082        $("#edit-tab-form [name=url]").val(response.data.url);
5083        $("#edit-tab-form [name=url_local]").val(response.data.url_local);
5084        $("#edit-tab-form [name=ping_url]").val(response.data.ping_url);
5085        $("#edit-tab-form [name=image]").val(response.data.image);
5086        $("#edit-tab-form [name=id]").val(response.data.id);
5087        $("#edit-tab-form [name=timeout_ms]").val(
5088          convertMsToMinutes(response.data.timeout_ms)
5089        );
5090        $("#edit-tab-form [name=timeout]").val(response.data.timeout);
5091        if (response.data.url.indexOf("/?v") > 0) {
5092          $("#edit-tab-form [name=url]").prop("disabled", "true");
5093        } else {
5094          $("#edit-tab-form [name=url]").prop("disabled", null);
5095        }
5096      } catch (e) {
5097        organizrCatchError(e, data);
5098      }
5099    })
5100    .fail(function (xhr) {
5101      OrganizrApiError(xhr, "Tab Error");
5102    });
5103}
5104function getSubmitSettingsFormValueSingle(form, index, value) {
5105  var values = {};
5106  if (value !== "#987654" && index.includes("disable-pwd-mgr") == false) {
5107    var input = $("#" + form + " [name='" + index + "']");
5108    var dataType = input.attr("data-type");
5109    switch (dataType) {
5110      case "switch":
5111      case "checkbox":
5112        var value = input.prop("checked") ? true : false;
5113        break;
5114      case "select2":
5115        var value = input.val() !== null ? input.val().toString() : "";
5116        break;
5117      default:
5118        var value = input.val();
5119    }
5120    values = { name: index, value: value, type: dataType };
5121    return values;
5122  }
5123  return false;
5124}
5125function getSubmitSettingsFormValueObject(form, index, value) {
5126  var values = [];
5127  $.each(value, function (i, v) {
5128    var objectList = [];
5129    var object = [];
5130    $.each(v, function (key, val) {
5131      if (val !== "#987654" && key.includes("disable-pwd-mgr") == false) {
5132        var input = $(
5133          "#" + form + " [name='" + index + "[" + i + "]." + key + "']"
5134        );
5135        var dataType = input.attr("data-type");
5136        var dataLabel = input.attr("data-label");
5137        switch (dataType) {
5138          case "switch":
5139          case "checkbox":
5140            var value = input.prop("checked") ? true : false;
5141            break;
5142          case "select2":
5143            var value = input.val() !== null ? input.val().toString() : "";
5144            break;
5145          default:
5146            var value = input.val();
5147        }
5148        var newKey = index + "[" + i + "]." + key;
5149        object.push({
5150          type: dataType,
5151          name: newKey,
5152          label: dataLabel,
5153          value: value,
5154        });
5155      }
5156    });
5157    values.push(object);
5158  });
5159  values = { name: index, value: values, type: "array" };
5160  return values;
5161}
5162function submitSettingsForm(form, homepageItem = false) {
5163  var list = $("#" + form).serializeToJSON();
5164  var size = 0;
5165  var submit = {};
5166  $.each(list, function (i, v) {
5167    var values = false;
5168
5169    if (Object.prototype.toString.call(v) === "[object Object]") {
5170      values = getSubmitSettingsFormValueObject(form, i, v);
5171    } else {
5172      values = getSubmitSettingsFormValueSingle(form, i, v);
5173    }
5174    size++;
5175    if (values) {
5176      submit[i] = values.value;
5177    }
5178  });
5179  var callbacks = $.Callbacks();
5180  // Custom Callbacks
5181  switch (form) {
5182    case "customize-appearance-form":
5183      break;
5184    default:
5185  }
5186  if (size > 0) {
5187    organizrAPI2("PUT", "api/v2/config", submit, true)
5188      .success(function (data) {
5189        try {
5190          var response = data.response;
5191        } catch (e) {
5192          organizrCatchError(e, data);
5193        }
5194        if (callbacks) {
5195          callbacks.fire();
5196        }
5197        if (homepageItem && !activeInfo.settings.misc.disableHomepageModals) {
5198          let html = `
5199		        <div class="panel panel-default">
5200                    <div class="panel-heading">${response.message}</div>
5201                    <div class="panel-wrapper collapse in">
5202                        <div class="panel-body">
5203                            <div class="overlay-box">
5204                                <div class="user-content">
5205                                    <h4 lang="en">Close Homepage Settings?</h4>
5206                                    <div class="button-box">
5207				                        <button class="btn btn-info waves-effect waves-light" type="button" onclick="swal.close();Custombox.modal.close()"><span class="btn-label"><i class="ti-check"></i></span>Yes</button>
5208				                        <button class="btn btn-danger waves-effect waves-light" type="button" onclick="swal.close()"><span class="btn-label"><i class="ti-close"></i></span>No</button>                        
5209				                    </div>
5210				                    <p class="close-homepage-timer">Auto Closing in 5 seconds...</p>
5211                                </div>
5212                            </div>
5213                        </div>
5214                    </div>
5215                </div>
5216		    `;
5217          swal({
5218            content: createElementFromHTML(html),
5219            buttons: false,
5220            className: "bg-org",
5221            timer: 5000,
5222          });
5223          textTimer(
5224            5,
5225            ".close-homepage-timer",
5226            "Seconds remaining: ",
5227            "Closing..."
5228          );
5229        } else {
5230          message(
5231            "Updated Items",
5232            response.message,
5233            activeInfo.settings.notifications.position,
5234            "#FFF",
5235            "success",
5236            "5000"
5237          );
5238        }
5239      })
5240      .fail(function (xhr) {
5241        OrganizrApiError(xhr, "Update Error");
5242      });
5243    $("#" + form + " :input").each(function () {
5244      var input = $(this);
5245      input
5246        .closest(".form-group")
5247        .removeClass("has-success")
5248        .removeClass("has-error");
5249    });
5250    $("#" + form + "-save").addClass("hidden");
5251  } else {
5252    $("#" + form + " :input").each(function () {
5253      var input = $(this);
5254      input
5255        .closest(".form-group")
5256        .removeClass("has-success")
5257        .addClass("has-error");
5258    });
5259  }
5260}
5261function textTimer(seconds, el, preText, postText) {
5262  var seconds_left = seconds;
5263  var interval = setInterval(function () {
5264    $(el).html(preText + " " + --seconds_left);
5265    if (seconds_left <= 0) {
5266      $(el).html(postText);
5267      clearInterval(interval);
5268    }
5269  }, 1000);
5270}
5271function submitHomepageOrder() {
5272  var list = $("#homepage-values").serializeToJSON();
5273  var size = 0;
5274  var submit = {};
5275  $.each(list, function (i, v) {
5276    if (v !== "") {
5277      size++;
5278      submit[i] = v;
5279    }
5280  });
5281  var callbacks = $.Callbacks();
5282  if (size > 0) {
5283    organizrAPI2("PUT", "api/v2/config", submit, true)
5284      .success(function (data) {
5285        try {
5286          var response = data.response;
5287          $("#submitHomepageOrder-save").addClass("hidden");
5288        } catch (e) {
5289          organizrCatchError(e, data);
5290        }
5291        message(
5292          "Updated Homepage Order",
5293          response.message,
5294          activeInfo.settings.notifications.position,
5295          "#FFF",
5296          "success",
5297          "5000"
5298        );
5299        if (callbacks) {
5300          callbacks.fire();
5301        }
5302      })
5303      .fail(function (xhr) {
5304        OrganizrApiError(xhr, "Update Error");
5305      });
5306  } else {
5307    console.log("add error");
5308  }
5309}
5310function submitTabOrder(newTabs) {
5311  var data = [];
5312  var process = false;
5313  $.each(newTabs.tab, function (i, v) {
5314    if (v.originalOrder == v.order) {
5315      delete newTabs.tab[i];
5316    } else {
5317      let temp = {
5318        order: v.order,
5319        id: v.id,
5320      };
5321      data.push(temp);
5322      process = true;
5323    }
5324  });
5325  if (!process) {
5326    message(
5327      "Tab Order Warning",
5328      "Order was not changed - Submission not needed",
5329      activeInfo.settings.notifications.position,
5330      "#FFF",
5331      "warning",
5332      "5000"
5333    );
5334    $(".saveTabOrderButton").addClass("hidden");
5335    return false;
5336  }
5337  var callbacks = $.Callbacks();
5338  callbacks.add(buildTabEditor);
5339  organizrAPI2("PUT", "api/v2/tabs", data, true)
5340    .success(function (data) {
5341      try {
5342        var response = data.response;
5343      } catch (e) {
5344        organizrCatchError(e, data);
5345      }
5346      message(
5347        "Tab Order Updated",
5348        response.message,
5349        activeInfo.settings.notifications.position,
5350        "#FFF",
5351        "success",
5352        "5000"
5353      );
5354      if (callbacks) {
5355        callbacks.fire();
5356      }
5357      $(".saveTabOrderButton").addClass("hidden");
5358    })
5359    .fail(function (xhr) {
5360      OrganizrApiError(xhr, "Update Error");
5361    });
5362}
5363function submitCategoryOrder() {
5364  var data = [];
5365  var categories = $("#submit-categories-form").serializeToJSON();
5366  var callbacks = $.Callbacks();
5367  callbacks.add(buildCategoryEditor);
5368  $.each(categories.category, function (i, v) {
5369    if (v.originalOrder == v.order) {
5370      delete categories.category[i];
5371    } else {
5372      let temp = {
5373        order: v.order,
5374        id: v.id,
5375      };
5376      data.push(temp);
5377    }
5378  });
5379  organizrAPI2("PUT", "api/v2/categories", data, true)
5380    .success(function (data) {
5381      try {
5382        var response = data.response;
5383      } catch (e) {
5384        organizrCatchError(e, data);
5385      }
5386      message(
5387        "Category Order Updated",
5388        response.message,
5389        activeInfo.settings.notifications.position,
5390        "#FFF",
5391        "success",
5392        "5000"
5393      );
5394      if (callbacks) {
5395        callbacks.fire();
5396      }
5397      $(".saveTabOrderButton").addClass("hidden");
5398    })
5399    .fail(function (xhr) {
5400      OrganizrApiError(xhr, "Update Error");
5401    });
5402}
5403function buildTR(array, type, badge) {
5404  var listing = "";
5405  var arrayItems = array.split("|");
5406  if (hasValue(arrayItems) === true) {
5407    $.each(arrayItems, function (i, v) {
5408      listing +=
5409        `
5410			<tr>
5411				<td  width="70"><span class="label label-` +
5412        badge +
5413        `"><span lang="en">` +
5414        type +
5415        `</span></span></td>
5416				<td class="text-capitalize">` +
5417        updateIssueLink(v) +
5418        `</td>
5419			</tr>
5420			`;
5421    });
5422    return listing;
5423  }
5424  return " ";
5425}
5426function buildVersion(array) {
5427  var x = 0;
5428  var versions =
5429    '<div class="col-md-3 col-sm-4 col-xs-6 m-b-10 pull-right"><button onclick="manualUpdateCheck()" class="btn btn-sm btn-primary btn-rounded waves-effect waves-light pull-right row b-none buttonManualUpdateCheck" type="button"><span class="btn-label"><i class="fa fa-globe"></i></span><span lang="en">Check For Updates</span></button></div><div class="clearfix"></div>';
5430  var listing = "";
5431  var currentV = currentVersion;
5432  var installed = "";
5433  var spanClass = "";
5434  var button = "";
5435  $.each(array, function (i, v) {
5436    listing += buildTR(v.new, "NEW", "info");
5437    listing += buildTR(v.fixed, "FIXED", "success");
5438    listing += buildTR(v.notes, "NOTE", "warning");
5439    if (currentV === i) {
5440      button =
5441        '<button class="btn btn-sm btn-success btn-rounded waves-effect waves-light disabled pull-right row b-none" type="button"><span class="btn-label"><i class="fa fa-check"></i></span><span lang="en">Installed</span></button>';
5442    } else if (x === 0) {
5443      button =
5444        '<button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none" type="button" onclick="updateNow();"><span class="btn-label"><i class="fa fa-download"></i></span><span lang="en">Install Update</span></button>';
5445    }
5446    let tableClass = x == 0 ? "" : "hidden";
5447    let divClassPadding = x == 0 ? "" : "p-b-0";
5448    let divClassMargin = x == 0 ? "" : "m-b-0";
5449    let toggleButtonText = x == 0 ? "Less" : "More";
5450    let toggleButtonIcon = x == 0 ? "up" : "down";
5451    let divStatus = x == 0 ? "opened" : "closed";
5452    versions +=
5453      `
5454		<div class="white-box bg-org ${divClassPadding} update-main-div-${x}" data-status="${divStatus}">
5455			<div class="col-md-3 col-sm-4 col-xs-6 pull-right">` +
5456      button +
5457      `</div>
5458			<h3 class="box-title ${divClassMargin} update-box-title-${x}">` +
5459      i +
5460      `</h3>
5461			<div class="row sales-report">
5462				<div class="col-md-12 col-sm-12 col-xs-12">
5463					<div class="pull-left">
5464						<span class="tooltip-info" data-toggle="tooltip" data-placement="right" title="" data-original-title="` +
5465      moment(v.date).format("LL") +
5466      `">` +
5467      moment.utc(v.date, "YYYY-MM-DD hh:mm[Z]").local().fromNow() +
5468      `</span>
5469						<p class="text-info p-0">` +
5470      v.title +
5471      `</p>
5472					</div>
5473					<button class="btn btn-sm btn-primary btn-rounded waves-effect waves-light pull-right" onclick="toggleGithubVersion(${x})" type="button"><span class="btn-label"><i class="fa fa-long-arrow-${toggleButtonIcon} toggleButtonIcon-${x}"></i></span><span lang="en" class="toggleButton-${x}">${toggleButtonText}</span></button>
5474				</div>
5475			</div>
5476			<div class="table-responsive ${tableClass} update-table-${x}">
5477				<table class="table inverse-bordered-table">
5478					<tbody>
5479						` +
5480      listing +
5481      `
5482					</tbody>
5483				</table>
5484			</div>
5485		</div>
5486		`;
5487    listing = "";
5488    button = "";
5489    x++;
5490  });
5491  return versions;
5492}
5493function toggleGithubVersion(id) {
5494  let status = $(".update-main-div-" + id).attr("data-status");
5495  if (status == "opened") {
5496    $(".update-main-div-" + id).attr("data-status", "closed");
5497    $(".update-main-div-" + id).addClass("p-b-0");
5498    $(".update-box-title-" + id).addClass("m-b-0");
5499    $(".update-table-" + id).addClass("hidden");
5500    $(".toggleButton-" + id).text("More");
5501    $(".toggleButtonIcon-" + id)
5502      .removeClass("fa-long-arrow-up")
5503      .addClass("fa-long-arrow-down");
5504  } else {
5505    $(".update-main-div-" + id).attr("data-status", "opened");
5506    $(".update-main-div-" + id).removeClass("p-b-0");
5507    $(".update-box-title-" + id).removeClass("m-b-0");
5508    $(".update-table-" + id).removeClass("hidden");
5509    $(".toggleButton-" + id).text("Less");
5510    $(".toggleButtonIcon-" + id)
5511      .addClass("fa-long-arrow-up")
5512      .removeClass("fa-long-arrow-down");
5513  }
5514}
5515function manualUpdateCheck() {
5516  $(".buttonManualUpdateCheck").addClass("disabled");
5517  $(".buttonManualUpdateCheck i")
5518    .removeClass("fa-globe")
5519    .addClass("fa-refresh fa-spin");
5520  setTimeout(function () {
5521    updateCheck();
5522    checkCommitLoad();
5523  }, 1000);
5524  setTimeout(function () {
5525    $(".buttonManualUpdateCheck").removeClass("disabled");
5526    $(".buttonManualUpdateCheck i")
5527      .removeClass("fa-refresh fa-spin fa-globe")
5528      .addClass("fa-check");
5529  }, 1500);
5530  return true;
5531}
5532function updateCheck() {
5533  githubVersions()
5534    .success(function (data) {
5535      try {
5536        var response = JSON.parse(data);
5537      } catch (e) {
5538        organizrCatchError(e, data);
5539      }
5540      for (var a in reverseObject(response)) {
5541        var latest = a;
5542        break;
5543      }
5544      if (latest !== currentVersion) {
5545        organizrConsole(
5546          "Update Function",
5547          "Update to " + latest + " is available",
5548          "warning"
5549        );
5550        if (activeInfo.settings.misc.docker === false) {
5551          let tabInfo = findTab("api/v2/page/settings", "access_url");
5552          if (tabInfo) {
5553            messageSingle(
5554              window.lang.translate("Update Available"),
5555              latest +
5556                " " +
5557                window.lang.translate("is available, goto") +
5558                ' <a href="javascript:void(0)" onclick="tabActions(event,\'' +
5559                tabInfo.id +
5560                "');clickPath('update')\"><span lang=\"en\">Update Tab</span></a>",
5561              activeInfo.settings.notifications.position,
5562              "#FFF",
5563              "update",
5564              "60000"
5565            );
5566          }
5567        }
5568      } else {
5569        organizrConsole(
5570          "Update Function",
5571          "Already running latest version: " + latest,
5572          "info"
5573        );
5574      }
5575      $("#githubVersions").html(buildVersion(reverseObject(response)));
5576    })
5577    .fail(function (xhr) {
5578      OrganizrApiError(xhr);
5579    });
5580}
5581function ignoreNewsId(id) {
5582  organizrAPI2("POST", "api/v2/news/" + id, {})
5583    .success(function (data) {
5584      try {
5585        let response = data.response;
5586        message(
5587          "News Item",
5588          "Item now ignored",
5589          activeInfo.settings.notifications.position,
5590          "#FFF",
5591          "success",
5592          "5000"
5593        );
5594        $(".newsItem-" + id).remove();
5595        $(".newsHeart-" + id).remove();
5596      } catch (e) {
5597        organizrCatchError(e, data);
5598      }
5599    })
5600    .fail(function (xhr) {
5601      OrganizrApiError(xhr, "News");
5602    });
5603}
5604function newsLoad() {
5605  newsJSON()
5606    .success(function (data) {
5607      try {
5608        var response = JSON.parse(data);
5609        var items = [];
5610        var limit = 5;
5611        var count = 0;
5612        organizrAPI2("get", "api/v2/news")
5613          .success(function (data) {
5614            try {
5615              let ignoredIds = data.response.data;
5616              ignoredIds = ignoredIds == null ? [] : ignoredIds;
5617              $.each(response, function (i, v) {
5618                count++;
5619                let ignore = ignoredIds.includes(v.id);
5620                let alertDefined =
5621                  typeof v.important !== "undefined" && v.important !== false;
5622                let alert =
5623                  alertDefined && ignore == false
5624                    ? `<span class="animated loop-animation flash text-danger mouse newsItem-${v.id}" onclick="ignoreNewsId('${v.id}')">&nbsp; <i class="ti-alert"></i>&nbsp; Important Message - Click me to Ignore</span>`
5625                    : "";
5626                let heartBeat =
5627                  alertDefined && ignore == false
5628                    ? `<div class="notify pull-left newsHeart-${v.id}"><span class="heartbit"></span><span class="point"></span></div>`
5629                    : "";
5630                let newBody =
5631                  `
5632			                <h5 class="pull-left"><i class="ti-calendar"></i>&nbsp;` +
5633                  moment(v.date).format("LLL") +
5634                  alert +
5635                  `</h5>
5636			                <h5 class="pull-right">` +
5637                  v.author +
5638                  `</h5>
5639			                <div class="clearfix"></div>
5640			                ` +
5641                  (v.subTitle ? "<h5>" + v.subTitle + "</h5>" : "") +
5642                  `
5643			                <p>` +
5644                  v.body +
5645                  `</p>
5646			                `;
5647                if (count <= limit) {
5648                  items[i] = {
5649                    title: v.title + heartBeat,
5650                    body: newBody,
5651                  };
5652                }
5653              });
5654              var body = buildAccordion(items, true);
5655              $("#organizrNewsPanel").html(body);
5656            } catch (e) {
5657              organizrCatchError(e, data);
5658            }
5659          })
5660          .fail(function (xhr) {
5661            OrganizrApiError(xhr, "News");
5662          });
5663      } catch (e) {
5664        organizrCatchError(e, data);
5665      }
5666    })
5667    .fail(function (xhr) {
5668      OrganizrApiError(xhr);
5669    });
5670}
5671function checkPluginUpdates() {
5672  if (!activeInfo.user.loggedin || activeInfo.user.groupID > 1) {
5673    return false;
5674  }
5675  organizrAPI2("get", "api/v2/plugins/marketplace")
5676    .success(function (data) {
5677      try {
5678        let update = false;
5679        let pluginsNeedingUpdate = [];
5680        let plugins = data.response.data;
5681        $.each(plugins, function (i, v) {
5682          if (v.needs_update) {
5683            update = true;
5684            pluginsNeedingUpdate.push(i);
5685          }
5686        });
5687        if (update) {
5688          pluginsNeedingUpdate = "[" + pluginsNeedingUpdate.join(", ") + "]";
5689          messageSingle(
5690            window.lang.translate("Update Available"),
5691            '<a href="javascript:void(0)" onclick="shortcut(\'plugin-marketplace\');"><span lang="en">The following plugin(s) need updates</span></a>: ' +
5692              pluginsNeedingUpdate,
5693            activeInfo.settings.notifications.position,
5694            "#FFF",
5695            "update",
5696            "600000"
5697          );
5698        }
5699      } catch (e) {
5700        organizrCatchError(e, data);
5701      }
5702    })
5703    .fail(function (xhr) {
5704      OrganizrApiError(xhr, "Marketplace");
5705    });
5706}
5707function checkCommitLoad() {
5708  if (
5709    activeInfo.settings.misc.docker &&
5710    activeInfo.settings.misc.githubCommit !== "n/a" &&
5711    activeInfo.settings.misc.githubCommit !== null
5712  ) {
5713    if (checkCommitLoadStatus == false) {
5714      checkCommitLoadStatus = true;
5715      getLatestCommitJSON()
5716        .success(function (data) {
5717          try {
5718            var latest = data.sha.toString().trim();
5719            var current = activeInfo.settings.misc.githubCommit
5720              .toString()
5721              .trim();
5722            var link =
5723              "https://github.com/causefx/Organizr/compare/" +
5724              current +
5725              "..." +
5726              latest;
5727            if (latest !== current) {
5728              messageSingle(
5729                window.lang.translate("Update Available"),
5730                ' <a href="' +
5731                  link +
5732                  '" target="_blank"><span lang="en">Compare Difference</span></a> <span lang="en">or</span> <a href="javascript:void(0)" onclick="updateNow()"><span lang="en">
5732Update Now</span></a>',
5733                activeInfo.settings.notifications.position,
5734                "#FFF",
5735                "update",
5736                "600000"
5737              );
5738            } else {
5739              organizrConsole(
5740                "Update Function",
5741                "Organizr Docker - Up to date"
5742              );
5743            }
5744          } catch (e) {
5745            organizrCatchError(e, data);
5746          }
5747          checkCommitLoadStatus = false;
5748        })
5749        .fail(function (xhr) {
5750          console.error("Organizr Function: Github Connection Failed");
5751          checkCommitLoadStatus = false;
5752        });
5753    }
5754  }
5755}
5756function sponsorLoad() {
5757  sponsorsJSON()
5758    .success(function (data) {
5759      try {
5760        var response = JSON.parse(data);
5761      } catch (e) {
5762        organizrCatchError(e, data);
5763      }
5764      $("#sponsorList").html(buildSponsor(response));
5765      $("#sponsorListModals").html(buildSponsorModal(response));
5766      $(".sponsor-items").owlCarousel({
5767        nav: false,
5768        autoplay: true,
5769        dots: false,
5770        margin: 10,
5771        autoWidth: true,
5772        items: 4,
5773      });
5774    })
5775    .fail(function (xhr) {
5776      OrganizrApiError(xhr);
5777    });
5778}
5779function backersLoad() {
5780  organizrAPI2("GET", "api/v2/sponsors/all")
5781    .success(function (data) {
5782      try {
5783        let json = data.response;
5784        $("#backersList").html(buildBackers(json.data));
5785        $(".backers-items").owlCarousel({
5786          nav: false,
5787          autoplay: true,
5788          dots: false,
5789          margin: 10,
5790          autoWidth: true,
5791          items: 4,
5792        });
5793      } catch (e) {
5794        organizrCatchError(e, data);
5795      }
5796    })
5797    .fail(function (xhr) {
5798      OrganizrApiError(xhr);
5799    });
5800}
5801function buildBackers(array) {
5802  let backers = "";
5803  $.each(array, function (i, v) {
5804    if (v.type == "USER" && v.role == "BACKER" && v.isActive) {
5805      v.name = v.name ? v.name : "User";
5806      v.image = v.image ? v.image : "plugins/images/default_user.png";
5807      backers += `
5808		        <!-- /.usercard -->
5809		        <div class="item lazyload recent-sponsor imageSource"  data-src="${v.image}">
5810		            <span class="elip recent-title">${v.name}</span>
5811		        </div>
5812		        <!-- /.usercard-->
5813		    `;
5814    }
5815  });
5816  backers += `
5817        <!-- /.usercard -->
5818        <div class="item lazyload recent-sponsor mouse imageSource mouse" onclick="window.open('https://opencollective.com/organizr', '_blank')" data-src="plugins/images/sponsor-open-collective.png">
5819            <span class="elip recent-title" lang="en">You</span>
5820        </div>
5821        <!-- /.usercard-->
5822    `;
5823  return backers;
5824}
5825function sponsorDetails(id) {
5826  sponsorsJSON()
5827    .success(function (data) {
5828      try {
5829        let response = JSON.parse(data);
5830        let coupon = response[id].coupon == null ? false : true;
5831        let couponAbout = response[id].coupon_about == null ? false : true;
5832        let extraInfo =
5833          coupon && couponAbout
5834            ? `
5835				<hr/>
5836		        <h3>Coupon Code:</h3>
5837		        <p><span class="label label-rouded label-info pull-right">${response[id].coupon}</span>
5838		        <span class=" pull-left">${response[id].coupon_about}</span></p>
5839		    `
5840            : "";
5841        if (typeof response[id].logo_dark !== "undefined") {
5842          if (activeInfo.style == "dark") {
5843            response[id].logo = response[id].logo_dark;
5844          }
5845        }
5846        let html = `
5847		        <div class="panel panel-default">
5848                    <div class="panel-heading">${response[id].company_name}</div>
5849                    <div class="panel-wrapper collapse in">
5850                        <div class="panel-body">
5851                            <div class="overlay-box">
5852                                <div class="user-content">
5853                                    <a href="javascript:void(0)"><img src="${response[id].logo}" class="thumb-lg img-circle" alt="img"></a>
5854                                    <h4 class="text-white">${response[id].company_name}</h4>
5855                                    <h5 class="text-white"><a href="${response[id].website}" target="_blank">Website</a></h5>
5856                                </div>
5857                            </div>
5858                            <hr/>
5859                            <div class="text-left">${response[id].about} ${extraInfo}</div>
5860                        </div>
5861                    </div>
5862                </div>
5863		    `;
5864        swal({
5865          content: createElementFromHTML(html),
5866          buttons: false,
5867          className: "bg-org",
5868        });
5869      } catch (e) {
5870        organizrCatchError(e, data);
5871      }
5872    })
5873    .fail(function (xhr) {
5874      OrganizrApiError(xhr);
5875    });
5876}
5877function sponsorAbout(id, array) {
5878  var coupon = array.coupon != null;
5879  var couponAbout = array.coupon_about != null;
5880  var extraInfo =
5881    coupon && couponAbout
5882      ? `
5883        <h3>Coupon Code:</h3>
5884        <p><span class="label label-rouded label-info pull-right">` +
5885        array.coupon +
5886        `</span>
5887        <span class=" pull-left">` +
5888        array.coupon_about +
5889        `</span></p>
5890    `
5891      : "";
5892  if (typeof array.logo_dark !== "undefined") {
5893    if (activeInfo.style == "dark") {
5894      array.logo = array.logo_dark;
5895    }
5896  }
5897  return (
5898    `
5899        <!--  modal content -->
5900        <div id="sponsor-` +
5901    id +
5902    `-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel-` +
5903    id +
5904    `" aria-hidden="true" style="display: none;">
5905            <div class="modal-dialog">
5906                <div class="modal-content">
5907                    <div class="modal-header">
5908                        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
5909                        <h4 class="modal-title" id="mySmallModalLabel-` +
5910    id +
5911    `">` +
5912    array.company_name +
5913    `</h4> </div>
5914                    <div class="modal-body">
5915                        <div class="row">
5916                            <div class="col-md-12">
5917                                <div class="comment-center p-t-10">
5918                                    <div class="comment-body b-none">
5919                                        <div class="user-img"> <img src="` +
5920    array.logo +
5921    `" alt="user" class="img-circle"> </div>
5922                                        <div class="mail-content">
5923                                            <h5><a href="` +
5924    array.website +
5925    `" target="_blank">` +
5926    array.company_name +
5927    `</a></h5>
5928                                            ` +
5929    array.about +
5930    extraInfo +
5931    `
5932                                         </div>
5933                                    </div>
5934                                </div>
5935                            </div>
5936                        </div>
5937                    </div>
5938                </div>
5939                <!-- /.modal-content -->
5940            </div>
5941            <!-- /.modal-dialog -->
5942        </div>
5943        <!-- /.modal -->
5944    `
5945  );
5946}
5947function buildSponsor(array) {
5948  var sponsors = "";
5949  $.each(array, function (i, v) {
5950    var hasCoupon = "";
5951    if (v.about) {
5952      if (v.coupon) {
5953        hasCoupon = `
5954                    <span class="text-center has-coupon-text">Has Coupon</span>
5955                    <span class="text-center has-coupon"><i class="fa fa-ticket" aria-hidden="true"></i></span>
5956                `;
5957      }
5958    }
5959    var sponsorAboutModal = v.about
5960      ? "onclick=\"sponsorDetails('" +
5961        i +
5962        "');sponsorAnalytics('" +
5963        v.company_name +
5964        "');\""
5965      : "onclick=\"window.open('" +
5966        v.website +
5967        "', '_blank');sponsorAnalytics('" +
5968        v.company_name +
5969        "');\"";
5970    if (typeof v.logo_dark !== "undefined") {
5971      if (activeInfo.style == "dark") {
5972        v.logo = v.logo_dark;
5973      }
5974    }
5975    sponsors += `
5976            <!-- /.usercard -->
5977            <div class="item lazyload recent-sponsor mouse imageSource mouse" ${sponsorAboutModal} data-src="${v.logo}" data-id="${i}">
5978                <span class="elip recent-title">${v.company_name}</span>
5979                ${hasCoupon}
5980            </div>
5981            <!-- /.usercard-->
5982        `;
5983  });
5984  sponsors += `
5985        <!-- /.usercard -->
5986        <div class="item lazyload recent-sponsor mouse imageSource mouse" onclick="window.open('https://www.patreon.com/bePatron?c=1320444&rid=2874514', '_blank')" data-src="plugins/images/sponsor-patreon.png">
5987            <span class="elip recent-title" lang="en">Patreon Sponsor</span>
5988        </div>
5989        <div class="item lazyload recent-sponsor mouse imageSource mouse" onclick="window.open('https://opencollective.com/organizr', '_blank')" data-src="plugins/images/sponsor-open-collective.png">
5990            <span class="elip recent-title" lang="en">OpenCollective Sponsor</span>
5991        </div>
5992        <!-- /.usercard-->
5993    `;
5994  return sponsors;
5995}
5996function buildSponsorModal(array) {
5997  var sponsors = "";
5998  $.each(array, function (i, v) {
5999    var sponsorAboutModal = v.about ? sponsorAbout(i, v) : "";
6000    sponsors += sponsorAboutModal;
6001  });
6002  return sponsors;
6003}
6004function sponsorAnalytics(sponsor_name) {
6005  var uuid = activeInfo.settings.misc.uuid;
6006  $.ajax({
6007    type: "POST",
6008    url: "https://api.organizr.app/",
6009    data: {
6010      sponsor_name: sponsor_name,
6011      user_uuid: uuid,
6012      cmd: "sponsor",
6013    },
6014    cache: false,
6015    async: true,
6016    complete: function (xhr, status) {
6017      if (xhr.status === 200) {
6018        let result = $.parseJSON(xhr.responseText);
6019      }
6020    },
6021  });
6022}
6023function themeAnalytics(theme_name) {
6024  var uuid = activeInfo.settings.misc.uuid;
6025  $.ajax({
6026    type: "POST",
6027    url: "https://api.organizr.app/",
6028    data: {
6029      theme_name: theme_name,
6030      user_uuid: uuid,
6031      cmd: "theme",
6032    },
6033    cache: false,
6034    async: true,
6035    complete: function (xhr, status) {
6036      if (xhr.status === 200) {
6037        let result = $.parseJSON(xhr.responseText);
6038      }
6039    },
6040  });
6041}
6042function getOrganizrBackups() {
6043  organizrAPI2("GET", "api/v2/backup")
6044    .success(function (data) {
6045      try {
6046        let json = data.response;
6047        $("#backup-file-list").html(buildOrganizrBackups(json.data));
6048      } catch (e) {
6049        organizrCatchError(e, data);
6050      }
6051    })
6052    .fail(function (xhr) {
6053      OrganizrApiError(xhr);
6054    });
6055}
6056function createOrganizrBackup() {
6057  $("#settings-settings-backup").block({
6058    message:
6059      '<p style="margin:0;padding:8px;font-size:24px;" lang="en">Backing up...</p>',
6060    css: {
6061      color: "#fff",
6062      border: "1px solid #5761a9",
6063      backgroundColor: "#707cd2",
6064    },
6065  });
6066  organizrAPI2("POST", "api/v2/backup", {})
6067    .success(function (data) {
6068      try {
6069        let response = data.response;
6070        if (response) {
6071          getOrganizrBackups();
6072        }
6073      } catch (e) {
6074        organizrCatchError(e, data);
6075      }
6076      $("#settings-settings-backup").unblock();
6077    })
6078    .fail(function (xhr) {
6079      $("#settings-settings-backup").unblock();
6080      OrganizrApiError(xhr, "Backup Error");
6081    });
6082}
6083function buildOrganizrBackups(array) {
6084  let list = "";
6085  if (array.total_files > 0) {
6086    $.each(array.files, function (i, v) {
6087      i++;
6088      let pattern = /\[[^\]]*\]/gm;
6089      let version =
6090        typeof v.name.match(pattern)[1] !== "undefined"
6091          ? v.name.match(pattern)[1]
6092          : "N/A";
6093      list += `
6094			<tr>
6095				<td>${i}</td>
6096				<td class="txt-oflo">${v.name}</td>
6097				<td><span class="label label-primary label-rouded">${version}</span> </td>
6098				<td class="txt-oflo">${v.size}</td>
6099				<td><span class="text-info tooltip-info" data-toggle="tooltip" data-placement="right" title="" data-original-title="${moment(
6100          v.date
6101        ).format("LLL")}">${moment
6102        .utc(v.date, "YYYY-MM-DD hh:mm[Z]")
6103        .local()
6104        .fromNow()}</span></td>
6105				<td><span class="text-primary"><a href="api/v2/backup/${
6106          v.name
6107        }"><i class="fa fa-download download-backup" data-file="${
6108        v.name
6109      }"></i></a> | <a href="javascript:void(0)"><i class="fa fa-trash-o delete-backup" data-file="${
6110        v.name
6111      }"></i></a></span></td>
6112			</tr>
6113			`;
6114    });
6115  } else {
6116    list =
6117      '<tr><td class="text-center" colspan="6">No Backups made yet</td></tr>';
6118  }
6119  $("#backup-total-files").html(array.total_files);
6120  $("#backup-total-size").html(array.total_size);
6121  return list;
6122}
6123function updateBar() {
6124  return `
6125	<div class="white-box m-0">
6126        <div class="row">
6127            <div class="col-lg-12">
6128                <h3 id="update-title" class="box-title pull-left"></h3><h3 id="update-time" class="box-title pull-right hidden"><span id="update-seconds"></span>&nbsp;<span lang="en">Seconds</span></h3>
6129				<div class="clearfix"></div>
6130                <div class="progress progress-lg">
6131                    <div id="update-bar" class="progress-bar progress-bar-primary progress-bar-striped active" style="width: 0%;" role="progressbar">0%</div>
6132                </div>
6133            </div>
6134            <h6>If error occurs - Use Esc key to close modal</h6>
6135        </div>
6136    </div>
6137	`;
6138}
6139function showUpdateBar() {
6140  swal({
6141    content: createElementFromHTML(updateBar()),
6142    buttons: false,
6143    className: "bg-org",
6144    closeOnClickOutside: false,
6145  });
6146}
6147function updateUpdateBar(title, percent, update = false) {
6148  $("#update-title").text(title);
6149  $("#update-bar").text(percent);
6150  $("#update-bar").css("width", percent);
6151  if (update) {
6152    $("#update-time").removeClass("hidden");
6153    countdown(10);
6154  }
6155}
6156function countdown(remaining) {
6157  if (remaining === 0) {
6158    local("set", "message", "Organizr Update|Update Successful|update");
6159    location.reload(true);
6160  }
6161  $("#update-seconds").text(remaining);
6162  setTimeout(function () {
6163    countdown(remaining - 1);
6164  }, 1000);
6165}
6166function dockerUpdate() {
6167  if (activeInfo.settings.misc.docker) {
6168    showUpdateBar();
6169    updateUpdateBar("Starting Download", "20%");
6170    messageSingle(
6171      window.lang.translate("[DO NOT CLOSE WINDOW]"),
6172      window.lang.translate("Starting Update Process"),
6173      activeInfo.settings.notifications.position,
6174      "#FFF",
6175      "success",
6176      "60000"
6177    );
6178    organizrAPI2("GET", "api/v2/update/docker")
6179      .success(function (data) {
6180        updateUpdateBar("Restarting Organizr in", "100%", true);
6181        messageSingle(
6182          window.lang.translate("[DO NOT CLOSE WINDOW]"),
6183          "Update complete",
6184          activeInfo.settings.notifications.position,
6185          "#FFF",
6186          "success",
6187          "60000"
6188        );
6189      })
6190      .fail(function (xhr) {
6191        OrganizrApiError(xhr, "Update Error");
6192      });
6193  }
6194}
6195function windowsUpdate() {
6196  if (activeInfo.serverOS == "win") {
6197    showUpdateBar();
6198    updateUpdateBar("Starting Download", "20%");
6199    messageSingle(
6200      window.lang.translate("[DO NOT CLOSE WINDOW]"),
6201      window.lang.translate("Starting Update Process"),
6202      activeInfo.settings.notifications.position,
6203      "#FFF",
6204      "success",
6205      "60000"
6206    );
6207    organizrAPI2("GET", "api/v2/update/windows")
6208      .success(function (data) {
6209        updateUpdateBar("Restarting Organizr in", "100%", true);
6210        messageSingle(
6211          window.lang.translate("[DO NOT CLOSE WINDOW]"),
6212          "Update complete",
6213          activeInfo.settings.notifications.position,
6214          "#FFF",
6215          "success",
6216          "60000"
6217        );
6218      })
6219      .fail(function (xhr) {
6220        OrganizrApiError(xhr, "Update Error");
6221      });
6222  }
6223}
6224function linuxUpdate() {
6225  if (activeInfo.serverOS !== "win" && !activeInfo.settings.misc.docker) {
6226    showUpdateBar();
6227    updateUpdateBar("Starting Download", "20%");
6228    messageSingle(
6229      window.lang.translate("[DO NOT CLOSE WINDOW]"),
6230      window.lang.translate("Starting Update Process"),
6231      activeInfo.settings.notifications.position,
6232      "#FFF",
6233      "success",
6234      "60000"
6235    );
6236    organizrAPI2("GET", "api/v2/update/linux")
6237      .success(function (data) {
6238        updateUpdateBar("Restarting Organizr in", "100%", true);
6239        messageSingle(
6240          window.lang.translate("[DO NOT CLOSE WINDOW]"),
6241          "Update complete",
6242          activeInfo.settings.notifications.position,
6243          "#FFF",
6244          "success",
6245          "60000"
6246        );
6247      })
6248      .fail(function (xhr) {
6249        OrganizrApiError(xhr, "Update Error");
6250      });
6251  }
6252}
6253function updateNow() {
6254  clearAJAX();
6255  if (activeInfo.settings.misc.docker) {
6256    dockerUpdate();
6257    return false;
6258  }
6259  if (activeInfo.serverOS === "win") {
6260    windowsUpdate();
6261    return false;
6262  }
6263  if (activeInfo.serverOS !== "win" && !activeInfo.settings.misc.docker) {
6264    linuxUpdate();
6265    return false;
6266  }
6267  organizrConsole("Update Function", "Starting Update Process");
6268  showUpdateBar();
6269  updateUpdateBar("Starting Download", "5%");
6270  messageSingle(
6271    window.lang.translate("[DO NOT CLOSE WINDOW]"),
6272    window.lang.translate("Starting Update Process"),
6273    activeInfo.settings.notifications.position,
6274    "#FFF",
6275    "success",
6276    "60000"
6277  );
6278  organizrAPI2("GET", "api/v2/update/download/" + activeInfo.branch)
6279    .success(function (data) {
6280      updateUpdateBar("Starting Unzip", "50%");
6281      messageSingle(
6282        window.lang.translate("[DO NOT CLOSE WINDOW]"),
6283        window.lang.translate("Update File Downloaded"),
6284        activeInfo.settings.notifications.position,
6285        "#FFF",
6286        "success",
6287        "60000"
6288      );
6289      organizrAPI2("GET", "api/v2/update/unzip/" + activeInfo.branch)
6290        .success(function (data) {
6291          updateUpdateBar("Starting Copy", "70%");
6292          messageSingle(
6293            window.lang.translate("[DO NOT CLOSE WINDOW]"),
6294            window.lang.translate("Update File Unzipped"),
6295            activeInfo.settings.notifications.position,
6296            "#FFF",
6297            "success",
6298            "60000"
6299          );
6300          organizrAPI2("GET", "api/v2/update/move/" + activeInfo.branch)
6301            .success(function (data) {
6302              updateUpdateBar("Starting Cleanup", "90%");
6303              messageSingle(
6304                window.lang.translate("[DO NOT CLOSE WINDOW]"),
6305                window.lang.translate("Update Files Copied"),
6306                activeInfo.settings.notifications.position,
6307                "#FFF",
6308                "success",
6309                "60000"
6310              );
6311              organizrAPI2("GET", "api/v2/update/cleanup/" + activeInfo.branch)
6312                .success(function (data) {
6313                  updateUpdateBar("Restarting Organizr in", "100%", true);
6314                  messageSingle(
6315                    window.lang.translate("[DO NOT CLOSE WINDOW]"),
6316                    window.lang.translate("Update Cleanup Finished"),
6317                    activeInfo.settings.notifications.position,
6318                    "#FFF",
6319                    "success",
6320                    "60000"
6321                  );
6322                })
6323                .fail(function (xhr) {
6324                  OrganizrApiError(xhr, "Update Error");
6325                });
6326            })
6327            .fail(function (xhr) {
6328              OrganizrApiError(xhr, "Update Error");
6329            });
6330        })
6331        .fail(function (xhr) {
6332          OrganizrApiError(xhr, "Update Error");
6333        });
6334    })
6335    .fail(function (xhr) {
6336      OrganizrApiError(xhr, "Update Error");
6337    });
6338}
6339function settingsAPI2(post, callbacks = null, asyncValue = true) {
6340  organizrAPI2("POST", post.api, post.data, asyncValue)
6341    .success(function (data) {
6342      try {
6343        var response = JSON.parse(data);
6344      } catch (e) {
6345        organizrCatchError(e, data);
6346      }
6347      message(
6348        post.messageTitle,
6349        post.messageBody,
6350        activeInfo.settings.notifications.position,
6351        "#FFF",
6352        "success",
6353        "5000"
6354      );
6355      if (callbacks) {
6356        callbacks.fire();
6357      }
6358    })
6359    .fail(function (xhr) {
6360      console.error(post.error);
6361    });
6362}
6363$.xhrPool.abortAll = function (url) {
6364  $(this).each(function (i, jqXHR) {
6365    //  cycle through list of recorded connection
6366    if (!url || url === jqXHR.requestURL) {
6367      organizrConsole("Organizr API Abort", jqXHR.requestURL, "info");
6368      jqXHR.abort(); //  aborts connection
6369      $.xhrPool.splice(i, 1); //  removes from list by index
6370    }
6371  });
6372};
6373$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
6374  //organizrConsole('Organizr API Function',options.url,'info');
6375  jqXHR.requestURL = options.url;
6376});
6377function organizrAPI2(type, path, data = null, asyncValue = true) {
6378  $.xhrPool.abortAll(path);
6379  var timeout = 10000;
6380  switch (path) {
6381    case "api/v2/update/windows":
6382    case "api/v2/update/docker":
6383    case "api/v2/login":
6384      timeout = 240000;
6385      break;
6386    default:
6387      timeout = 60000;
6388  }
6389  switch (type) {
6390    case "get":
6391    case "GET":
6392    case "g":
6393      return $.ajax({
6394        url: path,
6395        method: "GET",
6396        beforeSend: function (request) {
6397          request.setRequestHeader("Token", activeInfo.token);
6398          request.setRequestHeader("formKey", local("g", "formKey"));
6399          $.xhrPool.push(request);
6400        },
6401        complete: function (jqXHR) {
6402          var i = $.xhrPool.indexOf(jqXHR); //  get index for current connection completed
6403          if (i > -1) $.xhrPool.splice(i, 1); //  removes from list by index
6404        },
6405        timeout: timeout,
6406      });
6407    case "delete":
6408    case "DELETE":
6409    case "d":
6410      return $.ajax({
6411        url: path,
6412        method: "DELETE",
6413        beforeSend: function (request) {
6414          request.setRequestHeader("Token", activeInfo.token);
6415          request.setRequestHeader("formKey", local("g", "formKey"));
6416          $.xhrPool.push(request);
6417        },
6418        complete: function (jqXHR) {
6419          var i = $.xhrPool.indexOf(jqXHR); //  get index for current connection completed
6420          if (i > -1) $.xhrPool.splice(i, 1); //  removes from list by index
6421        },
6422        timeout: timeout,
6423      });
6424    case "post":
6425    case "POST":
6426    case "p":
6427      data.formKey = local("g", "formKey");
6428      return $.ajax({
6429        url: path,
6430        method: "POST",
6431        async: asyncValue,
6432        beforeSend: function (request) {
6433          request.setRequestHeader("Token", activeInfo.token);
6434          request.setRequestHeader("formKey", local("g", "formKey"));
6435          $.xhrPool.push(request);
6436        },
6437        complete: function (jqXHR) {
6438          var i = $.xhrPool.indexOf(jqXHR); //  get index for current connection completed
6439          if (i > -1) $.xhrPool.splice(i, 1); //  removes from list by index
6440        },
6441        data: data,
6442      });
6443    case "put":
6444    case "PUT":
6445      data.formKey = local("g", "formKey");
6446      return $.ajax({
6447        url: path,
6448        method: "PUT",
6449        async: asyncValue,
6450        beforeSend: function (request) {
6451          request.setRequestHeader("Token", activeInfo.token);
6452          request.setRequestHeader("formKey", local("g", "formKey"));
6453          $.xhrPool.push(request);
6454        },
6455        complete: function (jqXHR) {
6456          var i = $.xhrPool.indexOf(jqXHR); //  get index for current connection completed
6457          if (i > -1) $.xhrPool.splice(i, 1); //  removes from list by index
6458        },
6459        data: JSON.stringify(data),
6460        contentType: "application/json",
6461      });
6462    default:
6463      console.warn("Organizr API: Method Not Supported");
6464  }
6465}
6466function loadSettingsPage2(api, element, organizrFn) {
6467  $(element).html(
6468    '<h2 class="col-lg-12 m-t-0 text-center well bg-org"><i class="fa fa-spin fa-refresh"></i><br> <span lang="en">Loading</span></h2><div class="clearfix"></div>'
6469  );
6470  organizrAPI2("get", api)
6471    .success(function (data) {
6472      try {
6473        var response = data.response;
6474      } catch (e) {
6475        organizrCatchError(e, data);
6476      }
6477      organizrConsole("Organizr Function", "Loading " + organizrFn);
6478      $(element).html(response.data);
6479    })
6480    .fail(function (xhr) {
6481      OrganizrApiError(xhr);
6482    });
6483}
6484function loadInternal(id, split = null) {
6485  let extra = split ? "right-" : "";
6486  let tabInfo = findTab(id);
6487  if (!tabInfo) {
6488    organizrConsole("Load Internal", "No Tab Info Found... Id: " + id, "error");
6489    return false;
6490  }
6491  let url = tabInfo.access_url;
6492  organizrAPI2("get", url)
6493    .success(function (data) {
6494      try {
6495        var html = data.response;
6496        $("#internal-" + extra + id).html(html.data);
6497      } catch (e) {
6498        organizrCatchError(e, data);
6499      }
6500    })
6501    .fail(function (xhr) {
6502      OrganizrApiError(xhr);
6503    });
6504}
6505function loadInternalOriginal(url, tabName) {
6506  organizrAPI("get", url)
6507    .success(function (data) {
6508      try {
6509        var html = JSON.parse(data);
6510      } catch (e) {
6511        organizrCatchError(e, data);
6512      }
6513      $("#internal-" + tabName).html(html.data);
6514    })
6515    .fail(function (xhr) {
6516      OrganizrApiError(xhr);
6517    });
6518}
6519function loadSettingsPage(api, element, organizrFn) {
6520  organizrAPI("get", api)
6521    .success(function (data) {
6522      try {
6523        var response = JSON.parse(data);
6524      } catch (e) {
6525        organizrCatchError(e, data);
6526      }
6527      organizrConsole("Organizr Function", "Loading " + organizrFn);
6528      $(element).html(response.data);
6529    })
6530    .fail(function (xhr) {
6531      OrganizrApiError(xhr);
6532    });
6533}
6534function settingsAPI(post, callbacks = null, asyncValue = true) {
6535  organizrAPI("POST", post.api, post, asyncValue)
6536    .success(function (data) {
6537      try {
6538        var response = JSON.parse(data);
6539      } catch (e) {
6540        organizrCatchError(e, data);
6541      }
6542      message(
6543        post.messageTitle,
6544        post.messageBody,
6545        activeInfo.settings.notifications.position,
6546        "#FFF",
6547        "success",
6548        "5000"
6549      );
6550      if (callbacks) {
6551        callbacks.fire();
6552      }
6553    })
6554    .fail(function (xhr) {
6555      console.error(post.error);
6556    });
6557}
6558function organizrAPI(type, path, data = null, asyncValue = true) {
6559  var timeout = 10000;
6560  switch (path) {
6561    case "api/?v1/windows/update":
6562    case "api/?v1/docker/update":
6563      timeout = 120000;
6564      break;
6565    default:
6566      timeout = 60000;
6567  }
6568  switch (type) {
6569    case "get":
6570    case "GET":
6571    case "g":
6572      return $.ajax({
6573        url: path,
6574        method: "GET",
6575        beforeSend: function (request) {
6576          request.setRequestHeader("Token", activeInfo.token);
6577          request.setRequestHeader("formKey", local("g", "formKey"));
6578        },
6579        timeout: timeout,
6580      });
6581    case "post":
6582    case "POST":
6583    case "p":
6584      data.formKey = local("g", "formKey");
6585      return $.ajax({
6586        url: path,
6587        method: "POST",
6588        async: asyncValue,
6589        beforeSend: function (request) {
6590          request.setRequestHeader("Token", activeInfo.token);
6591          request.setRequestHeader("formKey", local("g", "formKey"));
6592        },
6593        data: {
6594          data: data,
6595        },
6596      });
6597    default:
6598      console.warn("Organizr API: Method Not Supported");
6599  }
6600}
6601function githubVersions() {
6602  return $.ajax({
6603    url:
6604      "https://raw.githubusercontent.com/causefx/Organizr/" +
6605      activeInfo.branch +
6606      "/js/version.json",
6607  });
6608}
6609function sponsorsJSON() {
6610  return $.ajax({
6611    url: "https://raw.githubusercontent.com/causefx/Organizr/v2-develop/js/sponsors.json",
6612  });
6613}
6614function newsJSON() {
6615  return $.ajax({
6616    url: "https://raw.githubusercontent.com/causefx/Organizr/v2-develop/js/news.json",
6617  });
6618}
6619function getLatestCommitJSON() {
6620  return $.ajax({
6621    url:
6622      "https://api.github.com/repos/causefx/Organizr/commits/" +
6623      activeInfo.branch,
6624  });
6625}
6626function marketplaceJSON(type) {
6627  return $.ajax({
6628    url:
6629      "https://raw.githubusercontent.com/causefx/Organizr/v2-" +
6630      type +
6631      "/" +
6632      type +
6633      ".json",
6634  });
6635}
6636function allIcons() {
6637  return $.ajax({
6638    url: "js/icons.json",
6639  });
6640}
6641function organizrConnect(path) {
6642  return $.ajax({
6643    url: path,
6644  });
6645}
6646function changeSettingsMenu(path) {
6647  var menuItems = path.split("::");
6648  var menu = "";
6649  if (Array.isArray(menuItems)) {
6650    $.each(menuItems, function (i, v) {
6651      menu += '<li><a lang="en">' + v + "</a></li>";
6652    });
6653  }
6654  $("#settingsBreadcrumb").html(menu);
6655}
6656function buildWizard() {
6657  organizrAPI2("GET", "api/v2/page/wizard")
6658    .success(function (data) {
6659      try {
6660        var json = data.response;
6661      } catch (e) {
6662        organizrCatchError(e, data);
6663      }
6664      organizrConsole("Organizr Function", "Starting Install Wizard");
6665      $(json.data).appendTo($(".organizr-area"));
6666      $(".organizr-area").removeClass("hidden");
6667    })
6668    .fail(function (xhr) {
6669      OrganizrApiError(xhr, "Wiizard Error");
6670    });
6671  $("#preloader").fadeOut();
6672}
6673function buildDependencyCheck(orgdata) {
6674  organizrAPI2("GET", "api/v2/page/dependencies")
6675    .success(function (data) {
6676      try {
6677        var json = data.response;
6678      } catch (e) {
6679        organizrCatchError(e, data);
6680      }
6681      organizrConsole("Organizr Function", "Starting Dependencies Check");
6682      $(json.data).appendTo($(".organizr-area"));
6683      $(".organizr-area").removeClass("hidden");
6684      $(buildBrowserInfo()).appendTo($("#browser-info"));
6685      $("#web-folder").html(buildWebFolder(orgdata));
6686      $("#php-version-check").html(buildPHPCheck(orgdata));
6687      $(buildDependencyInfo(orgdata)).appendTo($("#depenency-info"));
6688    })
6689    .fail(function (xhr) {
6690      OrganizrApiError(xhr, "Dependency Error");
6691    });
6692  $("#preloader").fadeOut();
6693}
6694function buildDependencyInfo(arrayItems) {
6695  let listing = "";
6696  $.each(arrayItems.data.status.dependenciesActive, function (i, v) {
6697    listing +=
6698      '<li class="depenency-item" data-name="' +
6699      v +
6700      '"><a href="javascript:void(0)"><i class="fa fa-check text-success"></i> ' +
6701      v +
6702      "</a></li>";
6703  });
6704  $.each(arrayItems.data.status.dependenciesInactive, function (i, v) {
6705    listing +=
6706      '<li class="depenency-item" data-name="' +
6707      v +
6708      '"><a href="javascript:void(0)"><i class="fa fa-close text-danger"><div class="notify"><span class="heartbit depend-heartbit"></span></div></i> ' +
6709      v +
6710      "</a></li>";
6711  });
6712
6713  let className =
6714    arrayItems.data.status.dependenciesInactive.length !== 0
6715      ? "bg-danger text-warning"
6716      : "bg-primary";
6717  let icon =
6718    arrayItems.data.status.dependenciesInactive.length !== 0
6719      ? "fa fa-exclamation-triangle"
6720      : "fa fa-check-circle"; //dependency-dependencies-check-listing-header
6721  let header =
6722    arrayItems.data.status.dependenciesInactive.length !== 0
6723      ? "panel-danger"
6724      : "panel-info";
6725  let listingIcon =
6726    arrayItems.data.status.dependenciesInactive.length !== 0
6727      ? "ti-alert"
6728      : "ti-check-box";
6729  let listingText =
6730    arrayItems.data.status.dependenciesInactive.length !== 0
6731      ? "Dependencies Missing"
6732      : "Dependencies OK";
6733
6734  $(".dependency-dependencies-check-listing-header")
6735    .removeClass("panel-danger")
6736    .addClass(header);
6737  $(".dependency-dependencies-check-listing i")
6738    .first()
6739    .removeClass("ti-alert")
6740    .addClass(listingIcon);
6741  $(".dependency-dependencies-check-listing span").text(listingText);
6742  $(".dependency-dependencies-check")
6743    .removeClass("bg-warning")
6744    .addClass(className);
6745  $(".dependency-dependencies-check i")
6746    .removeClass("fa fa-spin fa-spinner")
6747    .addClass(icon);
6748  return listing;
6749}
6750function buildWebFolder(arrayItems) {
6751  let writable = "Not Writable - Please fix permissions";
6752  let className = "bg-danger text-warning";
6753  let icon = "fa fa-exclamation-triangle";
6754  if (arrayItems.data.status.writable == "yes") {
6755    writable = "Writable - All Good";
6756    className = "bg-primary";
6757    icon = "fa fa-check-circle";
6758  }
6759  $(".dependency-permissions-check")
6760    .removeClass("bg-warning")
6761    .addClass(className);
6762  $(".dependency-permissions-check i")
6763    .removeClass("fa fa-spin fa-spinner")
6764    .addClass(icon);
6765  $("#web-folder").addClass(className);
6766  return writable;
6767}
6768function buildPHPCheck(arrayItems) {
6769  let phpTest = "Upgrade PHP Version to 7.2+";
6770  let className = "bg-danger text-warning";
6771  let icon = "fa fa-exclamation-triangle";
6772  if (arrayItems.data.status.minVersion == "yes") {
6773    phpTest = "PHP Version Approved";
6774    className = "bg-primary";
6775    icon = "fa fa-check-circle";
6776  }
6777  $(".dependency-phpversion-check")
6778    .removeClass("bg-warning")
6779    .addClass(className);
6780  $(".dependency-phpversion-check i")
6781    .removeClass("fa fa-spin fa-spinner")
6782    .addClass(icon);
6783  $("#php-version-check").addClass(className);
6784  $("#php-version-check-user").html(
6785    '<span lang="en">
6785Webserver User</span>: ' + arrayItems.data.status.php_user
6786  );
6787  return phpTest;
6788}
6789function buildBrowserInfo() {
6790  var listing = "";
6791  $.each(activeInfo, function (i, v) {
6792    listing +=
6793      `
6794		<tr>
6795			<td>` +
6796      i +
6797      `</td>
6798			<td>` +
6799      tof(v) +
6800      `</td>
6801		</tr>
6802		`;
6803  });
6804  return (
6805    `
6806	<table class="table table-hover">
6807		<tbody>
6808			` +
6809    listing +
6810    `
6811		</tbody>
6812	</table>
6813	`
6814  );
6815}
6816function tof(string, type) {
6817  var result;
6818  if (
6819    typeof string == "undefined" ||
6820    string == "false" ||
6821    string == false ||
6822    string == null ||
6823    string == 0 ||
6824    string == "off" ||
6825    string == "no"
6826  ) {
6827    result = "0";
6828  } else if (
6829    string == "true" ||
6830    string == true ||
6831    string == 1 ||
6832    string == "on" ||
6833    string == "yes"
6834  ) {
6835    result = "1";
6836  }
6837  switch (type) {
6838    case "bool":
6839    case "b":
6840      return result == "0" ? false : result == "1" ? true : string;
6841    case "switch":
6842    case "s":
6843      return result == "0" ? "off" : result == "1" ? "on" : string;
6844    case "checkbox":
6845    case "c":
6846      return result == "0" ? "" : result == "1" ? "checked" : string;
6847    case "integer":
6848    case "number":
6849    case "i":
6850    case "n":
6851      return result == "0" ? 0 : result == "1" ? 1 : string;
6852    case "question":
6853    case "q":
6854      return result == "0" ? "yes" : result == "1" ? "no" : string;
6855    case "string":
6856      return string.toString();
6857    default:
6858      return result == "0" ? "false" : result == "1" ? "true" : string;
6859  }
6860}
6861function createRandomString(length) {
6862  var str = "";
6863  for (; str.length < length; str += Math.random().toString(36).substr(2));
6864  return str.substr(0, length);
6865}
6866function generateAPI() {
6867  var string = createRandomString(20);
6868  $("#form-api").focus();
6869  $("#form-api").val(string);
6870  $("#form-api").focusout();
6871  $("#verify-api").text(string);
6872  $("#form-username").focus();
6873}
6874function getCookie(cname) {
6875  var name = cname + "=";
6876  var decodedCookie = decodeURIComponent(document.cookie);
6877  var ca = decodedCookie.split(";");
6878  for (var i = 0; i < ca.length; i++) {
6879    var c = ca[i];
6880    while (c.charAt(0) == " ") {
6881      c = c.substring(1);
6882    }
6883    if (c.indexOf(name) == 0) {
6884      return c.substring(name.length, c.length);
6885    }
6886  }
6887  return "";
6888}
6889function localStorageSupport() {
6890  return "localStorage" in window && window["localStorage"] !== null;
6891}
6892function local(type, key, value = null) {
6893  if (localStorageSupport) {
6894    switch (type) {
6895      case "set":
6896      case "s":
6897        localStorage.setItem(key, value);
6898        break;
6899      case "get":
6900      case "g":
6901        return localStorage.getItem(key);
6902      case "remove":
6903      case "r":
6904        localStorage.removeItem(key);
6905        break;
6906      default:
6907        console.warn("Organizr Function: localStorage action not defined");
6908    }
6909  }
6910}
6911function language(language) {
6912  var language = language.split("-");
6913  return language[0];
6914}
6915function logIcon(type, label = false) {
6916  type = type.toLowerCase();
6917  let info = { color: "info", icon: "fa fa-check" };
6918  switch (type) {
6919    case "success":
6920      info.color = "info";
6921      info.icon = "fa fa-check";
6922      break;
6923    case "info":
6924      info.color = "info";
6925      info.icon = "mdi mdi-information";
6926      break;
6927    case "notice":
6928      info.color = "inverse";
6929      info.icon = "mdi mdi-information-variant";
6930      break;
6931    case "debug":
6932      info.color = "primary";
6933      info.icon = "mdi mdi-code-tags-check";
6934      break;
6935    case "warning":
6936      info.color = "warning";
6937      info.icon = "mdi mdi-alert-box";
6938      break;
6939    case "error":
6940      info.color = "danger";
6941      info.icon = "mdi mdi-alert-outline";
6942      break;
6943    case "critical":
6944      info.color = "danger";
6945      info.icon = "mdi mdi-alert";
6946      break;
6947    case "alert":
6948      info.color = "danger";
6949      info.icon = "mdi mdi-alert-octagon";
6950      break;
6951    case "emergency":
6952      info.color = "danger";
6953      info.icon = "mdi mdi-alert-octagram";
6954      break;
6955    default:
6956      info = { color: "info", icon: "fa fa-check" };
6957      break;
6958  }
6959  if (label) {
6960    return (
6961      '<span class="label label-' +
6962      info.color +
6963      ' log-label"> <i class="fa ' +
6964      info.icon +
6965      ' m-l-5 fa-fw"></i>&nbsp; <span lang="en" class="text-uppercase">' +
6966      type +
6967      "</span></span>"
6968    );
6969  } else {
6970    return (
6971      '<button class="btn btn-xs btn-' +
6972      info.color +
6973      ' log-label no-mouse" type="button"><span class="btn-label pull-left"><i class="' +
6974      info.icon +
6975      ' fa-fw"></i></span><span class="text-uppercase" lang="en">' +
6976      type +
6977      "</span></button>"
6978    );
6979  }
6980}
6981function toggleKillOrganizrLiveUpdate(interval = 5000) {
6982  if ($(".organizr-log-live-update").hasClass("kill-organizr-log")) {
6983    clearTimeout(timeouts["organizr-log"]);
6984    $(".organizr-log-live-update i").toggleClass(
6985      "fa-dot-circle-o animated loop-animation swing"
6986    );
6987    $(".organizr-log-live-update").toggleClass("kill-organizr-log");
6988  } else {
6989    $(".organizr-log-live-update").toggleClass("kill-organizr-log");
6990    organizrLogLiveUpdate(interval);
6991  }
6992}
6993function organizrLogLiveUpdate(interval = 5000) {
6994  var timeout = interval;
6995  let timeoutTitle = "organizr-log";
6996  $(".organizr-log-live-update i").toggleClass(
6997    "fa-dot-circle-o animated loop-animation swing"
6998  );
6999  organizrLogTable.ajax.reload(null, false);
7000  setTimeout(function () {
7001    if ($(".organizr-log-live-update").hasClass("kill-organizr-log")) {
7002      $(".organizr-log-live-update i").toggleClass(
7003        "fa-dot-circle-o animated loop-animation swing"
7004      );
7005    }
7006  }, interval - 500);
7007  if (typeof timeouts[timeoutTitle] !== "undefined") {
7008    clearTimeout(timeouts[timeoutTitle]);
7009  }
7010  timeouts[timeoutTitle] = setTimeout(function () {
7011    organizrLogLiveUpdate(timeout);
7012  }, timeout);
7013  delete timeout;
7014}
7015function radioLoop(element) {
7016  $('[type=radio][id!="' + element.id + '"]').each(function () {
7017    this.checked = false;
7018  });
7019}
7020function loadAppearance(appearance) {
7021  var cssSettings = "";
7022  document.title = appearance.title;
7023  if (appearance.useLogo === false) {
7024    $("#main-logo").html(appearance.title);
7025    $("#side-logo").html(appearance.title);
7026  } else {
7027    $("#main-logo").html(
7028      '<img alt="home" class="dark-logo" src="' + appearance.logo + '">'
7029    );
7030    $("#side-logo").html(
7031      '<img alt="home" class="dark-logo-side" src="' + appearance.logo + '">'
7032    );
7033  }
7034  if (appearance.headerColor !== "") {
7035    cssSettings +=
7036      `
7037		    .navbar-header{
7038			    background: ` +
7039      appearance.headerColor +
7040      `;
7041		    }
7042		`;
7043  }
7044  if (appearance.headerTextColor !== "") {
7045    cssSettings +=
7046      `
7047		    .navbar-top-links > li > a {
7048			    color: ` +
7049      appearance.headerTextColor +
7050      `;
7051		    }
7052		`;
7053  }
7054  if (appearance.sidebarColor !== "") {
7055    cssSettings +=
7056      `
7057		    .sidebar, .sidebar .sidebar-head{
7058			    background: ` +
7059      appearance.sidebarColor +
7060      `;
7061		    }
7062		`;
7063  }
7064  if (appearance.sidebarTextColor !== "") {
7065    cssSettings +=
7066      `
7067		    #side-menu li a,
7068			.sidebar .sidebar-head h3,
7069			#side-menu > li > a.active, #side-menu > li > ul > li > a.active
7070			{
7071			    color: ` +
7072      appearance.sidebarTextColor +
7073      `;
7074		    }
7075		`;
7076  }
7077  if (appearance.accentColor !== "") {
7078    cssSettings +=
7079      `
7080			.bg-info,
7081			.fc-toolbar,
7082			.progress-bar-info,
7083			.label-info,
7084			.tabs-style-iconbox nav ul li.tab-current a,
7085			.swapLog.active {
7086			    background-color: ` +
7087      appearance.accentColor +
7088      ` !important;
7089			}
7090			.panel-blue .panel-heading, .panel-info .panel-heading {
7091			    border-color: ` +
7092      appearance.accentColor +
7093      `;
7094			}
7095			.tabs-style-iconbox nav ul li.tab-current a::after {
7096				border-top-color: ` +
7097      appearance.accentColor +
7098      `;
7099			}
7100			.customvtab .tabs-vertical li.active a,
7101			.customvtab .tabs-vertical li.active a:focus,
7102			.customvtab .tabs-vertical li.active a:hover {
7103				border-right: 2px solid ` +
7104      appearance.accentColor +
7105      `;
7106			}
7107			.text-info,
7108			.btn-link, a {
7109			    color: ` +
7110      appearance.accentColor +
7111      `;
7112			}
7113		`;
7114  }
7115  if (appearance.accentTextColor !== "") {
7116    cssSettings +=
7117      `
7118			.bg-info,
7119			.progress-bar,
7120			.panel-default .panel-heading,
7121			.mailbox-widget .customtab li.active a, .mailbox-widget .customtab li.active, .mailbox-widget .customtab li.active a:focus,
7122			.mailbox-widget .customtab li a,
7123			.tabs-style-iconbox nav ul li.tab-current a
7124			.swapLog.active {
7125				color: ` +
7126      appearance.accentTextColor +
7127      `;
7128			}
7129		`;
7130  }
7131  if (appearance.buttonColor !== "") {
7132    cssSettings +=
7133      `
7134			.btn-info, .btn-info.disabled,
7135			.btn,
7136			.paginate_button.current,
7137			.paginate_button:hover {
7138				background: ` +
7139      appearance.buttonColor +
7140      ` !important;
7141				border: 1px solid ` +
7142      appearance.buttonColor +
7143      ` !important;
7144			}
7145		`;
7146  }
7147  if (appearance.buttonTextColor !== "") {
7148    cssSettings +=
7149      `
7150			.btn-info, .btn-info.disabled,
7151			.btn
7152			.paginate_button.current
7153			.paginate_button:hover {
7154				color: ` +
7155      appearance.buttonTextColor +
7156      ` !important;
7157			}
7158		`;
7159  }
7160  if (appearance.loginWallpaper !== "" || appearance.randomMediaImage) {
7161    if (appearance.randomMediaImage) {
7162      appearance.loginWallpaper = appearance.randomMediaImage;
7163    }
7164    cssSettings +=
7165      `
7166		    .login-register {
7167			    background: url(` +
7168      randomCSV(appearance.loginWallpaper) +
7169      `) center center/cover no-repeat!important;
7170			    height: 100%;
7171			    position: fixed;
7172		    }
7173			.lock-screen {
7174				background: url(` +
7175      randomCSV(appearance.loginWallpaper) +
7176      `) center center/cover no-repeat!important;
7177			    height: 100%;
7178			    position: fixed;
7179			    z-index: 1001;
7180			    top: 0;
7181			    width: 100%;
7182			    -webkit-user-select: none;
7183			    -moz-user-select: none;
7184			    -ms-user-select: none;
7185			    -o-user-select: none;
7186			    user-select: none;
7187			}
7188		`;
7189  }
7190  if (activeInfo["settings"]["misc"]["autoExpandNavBar"] == false) {
7191    cssSettings += `
7192			@media only screen and (min-width: 768px) {
7193				.sidebar:hover .hide-menu {
7194					display: none;
7195				}
7196				.sidebar:hover .sidebar-head,
7197				.sidebar:hover {
7198					width: 60px;
7199				}
7200				.sidebar:hover .nav-second-level li a {
7201					padding-left: 15px;
7202				}
7203			}
7204		`;
7205  }
7206  if (cssSettings !== "") {
7207    $("#user-appearance").html(cssSettings);
7208  }
7209  if (appearance.customThemeCss !== "") {
7210    $("#custom-theme-css").html(appearance.customThemeCss);
7211  }
7212  if (appearance.customCss !== "") {
7213    $("#custom-css").html(appearance.customCss);
7214  }
7215}
7216function resetCustomColors() {
7217  let colors = [
7218    "headerColor",
7219    "headerTextColor",
7220    "sidebarColor",
7221    "sidebarTextColor",
7222    "accentColor",
7223    "accentTextColor",
7224    "buttonColor",
7225    "buttonTextColor",
7226  ];
7227  $.each(colors, function (i, v) {
7228    $("#customize-appearance-form [name=" + v + "]")
7229      .val("")
7230      .trigger("change");
7231  });
7232  messageSingle(
7233    window.lang.translate("Colors Reverted"),
7234    window.lang.translate("Please Save"),
7235    activeInfo.settings.notifications.position,
7236    "#FFF",
7237    "success",
7238    "10000"
7239  );
7240}
7241function randomCSV(values) {
7242  if (typeof values == "string") {
7243    if (values.includes(",")) {
7244      var csv = values.split(",");
7245      var luckyNumber = Math.floor(Math.random() * csv.length);
7246      return csv[luckyNumber];
7247    } else {
7248      return values;
7249    }
7250  }
7251  return false;
7252}
7253function loadCustomJava(appearance) {
7254  if (appearance.customThemeJava !== "") {
7255    $("#custom-theme-javascript").html(appearance.customThemeJava);
7256  }
7257  if (appearance.customJava !== "") {
7258    $("#custom-javascript").html(appearance.customJava);
7259  }
7260}
7261function clearForm(form) {
7262  $(form + " input[type=text]").each(function () {
7263    $(this).val("");
7264  });
7265  $(form + " input[type=password]").each(function () {
7266    $(this).val("");
7267  });
7268}
7269function checkMessage() {
7270  var check = local("get", "message") ? local("get", "message") : false;
7271  if (check) {
7272    local("remove", "message");
7273    var message = check.split("|");
7274    messageSingle(
7275      window.lang.translate(message[0]),
7276      window.lang.translate(message[1]),
7277      activeInfo.settings.notifications.position,
7278      "#FFF",
7279      message[2],
7280      "10000"
7281    );
7282  }
7283}
7284function setError(error) {
7285  local("set", "error", error);
7286  var url = window.location.href.split("?")[0];
7287  url = url.split("#")[0];
7288  window.location.href = url + "?error";
7289}
7290function buildErrorPage(error) {
7291  var description = "";
7292  var message = "";
7293  var color = "";
7294  switch (error) {
7295    case "401":
7296      description = "Unauthorized";
7297      message = "Look, you dont belong here";
7298      color = "danger";
7299      break;
7300    case "404":
7301      description = "Not Found";
7302      message = "I think I lost it...";
7303      color = "primary";
7304      break;
7305    default:
7306      description = "Something happened";
7307      message = "But I dont know what";
7308      color = "muted";
7309  }
7310  return (
7311    `
7312	<div class="error-box">
7313		<div class="error-body text-center">
7314			<h1 class="text-` +
7315    color +
7316    `">` +
7317    error +
7318    `</h1>
7319			<h3 class="text-uppercase">` +
7320    description +
7321    `</h3>
7322			<p class="text-muted m-t-30 m-b-30" lang="en">` +
7323    message +
7324    `</p>
7325			<a href="javascript:void(0);" class="btn btn-` +
7326    color +
7327    ` btn-rounded waves-effect waves-light m-b-40 closeErrorPage animated tada loop-animation" lang="en">OK</a>
7328		</div>
7329	</div>
7330	`
7331  );
7332}
7333$.urlParam = function (name) {
7334  var results = new RegExp("[?&]" + name + "=([^&#]*)").exec(
7335    window.location.href
7336  );
7337  if (results == null) {
7338    return null;
7339  } else {
7340    return decodeURI(results[1]) || 0;
7341  }
7342};
7343function errorPage(error = null, uri = null) {
7344  if (error) {
7345    local("set", "error", error);
7346  }
7347  if (uri) {
7348    local("set", "uri", uri);
7349  }
7350  //var urlParams = new URLSearchParams(window.location.search);
7351
7352  if ($.urlParam("error") !== null && !isNaN(Number($.urlParam("error")))) {
7353    local("set", "error", $.urlParam("error"));
7354  }
7355  if ($.urlParam("return") !== null && activeInfo.user.loggedin !== true) {
7356    local("set", "uri", $.urlParam("return"));
7357  }
7358  if (window.location !== window.parent.location) {
7359    var count = 0;
7360    for (var k in window.parent.location) {
7361      if (window.parent.location.hasOwnProperty(k)) {
7362        ++count;
7363      }
7364    }
7365    if (count == 0 || count == "undefined") {
7366      return false;
7367    }
7368    var iframeError = local("get", "error");
7369    parent.errorPage(iframeError);
7370    local("remove", "uri");
7371    $("html").html("");
7372    return false;
7373  }
7374  if (local("get", "error")) {
7375    //show error page
7376    $(".error-page").html(buildErrorPage(local("get", "error")));
7377    $(".error-page").fadeIn();
7378    local("remove", "error");
7379    window.history.pushState({}, document.title, "./");
7380  }
7381}
7382function uriRedirect(uri = null) {
7383  if (uri) {
7384    local("set", "uri", uri);
7385  }
7386  if (activeInfo.user.loggedin === true && activeInfo.user.locked !== 1) {
7387    var redirect = local("get", "uri");
7388    local("remove", "uri");
7389    if (redirect !== null) {
7390      window.location.href = decodeURIComponent(decodeURI(redirect));
7391    }
7392  }
7393}
7394function changeTheme(theme) {
7395  //$("#preloader").fadeIn();
7396  $("#theme").attr({
7397    href: theme + ".css?v=" + activeInfo.version,
7398  });
7399  //$("#preloader").fadeOut();
7400  console.info(
7401    "%c Theme %c ".concat(theme, " "),
7402    "color: white; background: #AD80FD; font-weight: 700;",
7403    "color: #AD80FD; background: white; font-weight: 700;"
7404  );
7405}
7406function changeStyle(style) {
7407  //$("#preloader").fadeIn();
7408  $("#style").attr({
7409    href: "css/" + style + ".min.css?v=" + activeInfo.version,
7410  });
7411  //$("#preloader").fadeOut();
7412  console.info(
7413    "%c Style %c ".concat(style, " "),
7414    "color: white; background: #AD80FD; font-weight: 700;",
7415    "color: #AD80FD; background: white; font-weight: 700;"
7416  );
7417}
7418function setSSO() {
7419  $.each(activeInfo.sso, function (i, v) {
7420    if (v !== false) {
7421      local("set", i, v);
7422    } else {
7423      local("r", i);
7424    }
7425  });
7426  // other items to remove
7427  $.each(localStorage, function (i, v) {
7428    if (typeof v == "string") {
7429      if (i.startsWith("user-")) {
7430        if (typeof activeInfo.sso[i] == "undefined") {
7431          local("r", i);
7432        }
7433      }
7434    }
7435  });
7436}
7437function buildStreamItem(array, source) {
7438  var cards = "";
7439  var count = 0;
7440  var total = array.length;
7441  var sourceIcon = source === "jellyfin" ? "fish" : source;
7442  var streamDetails = {
7443    direct: 0,
7444    transcode: 0,
7445  };
7446  var bandwidthDetails = {
7447    wan: 0,
7448    lan: 0,
7449  };
7450  cards += '<div class="flexbox">';
7451  $.each(array, function (i, v) {
7452    var icon = "";
7453    var width = 100;
7454    var bg = "";
7455    count++;
7456    v.nowPlayingImageURL = v.useImage ? v.useImage : v.nowPlayingImageURL;
7457    switch (v.type) {
7458      case "music":
7459        icon = "icon-music-tone-alt";
7460        width =
7461          v.nowPlayingImageURL !== "plugins/images/homepage/no-np.png"
7462            ? 56
7463            : 100;
7464        bg =
7465          v.nowPlayingImageURL !== "plugins/images/homepage/no-np.png"
7466            ? `
7467				<img class="imageSource imageSourceLeft" src="` +
7468              v.nowPlayingImageURL +
7469              `">
7470				<img class="imageSource imageSourceRight" src="` +
7471              v.nowPlayingImageURL +
7472              `">
7473				`
7474            : "";
7475        break;
7476      case "movie":
7477        icon = "icon-film";
7478        break;
7479      case "tv":
7480        icon = "icon-screen-desktop";
7481        break;
7482      case "video":
7483        icon = "icon-screen-film";
7484        break;
7485      default:
7486    }
7487    var userThumb = v.userThumb
7488      ? '<img src="' + v.userThumb + '" class="nowPlayingUserThumb" alt="User">'
7489      : "";
7490    if (v.sessionType == "Direct Playing") {
7491      var userStream = "Direct Play";
7492      var userVideo = "Direct Play";
7493      var userAudio = "Direct Play";
7494      streamDetails["direct"] = streamDetails["direct"] + 1;
7495    } else {
7496      var userStream = v.userStream.stream;
7497      var userVideo =
7498        v.userStream.videoDecision +
7499        " (" +
7500        v.userStream.sourceVideoCodec +
7501        ' <i class="mdi mdi-ray-start-arrow"></i> ' +
7502        v.userStream.videoCodec +
7503        " " +
7504        v.userStream.videoResolution +
7505        ")";
7506      var userAudio =
7507        v.userStream.audioDecision +
7508        " (" +
7509        v.userStream.sourceAudioCodec +
7510        ' <i class="mdi mdi-ray-start-arrow"></i> ' +
7511        v.userStream.audioCodec +
7512        ")";
7513      streamDetails["transcode"] = streamDetails["transcode"] + 1;
7514    }
7515    var streamInfo = "";
7516    streamInfo +=
7517      `<div class="text-muted m-t-20 text-uppercase"><span class="text-uppercase"><i class="mdi mdi-play-circle-outline"></i> Stream: ` +
7518      userStream +
7519      `</span></div>`;
7520    streamInfo += v.userStream.videoResolution
7521      ? `<div class="text-muted m-t-20 text-uppercase"><span class="text-uppercase"><i class="mdi mdi-video"></i> Video: ` +
7522        userVideo +
7523        `</span></div>`
7524      : "";
7525    streamInfo +=
7526      `<div class="text-muted m-t-20 text-uppercase"><span class="text-uppercase"><i class="mdi mdi-speaker"></i> Audio: ` +
7527      userAudio +
7528      `</span></div>`;
7529    v.session = v.session.replace(/[\W_]+/g, "-");
7530    bandwidthDetails[v.bandwidthType] =
7531      bandwidthDetails[v.bandwidthType] + parseFloat(v.bandwidth);
7532    cards +=
7533      `
7534		<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6 col-xs-12 nowPlayingItem">
7535			<div class="white-box">
7536				<div class="el-card-item p-b-10">
7537					<div class="el-card-avatar el-overlay-1 m-b-0">` +
7538      bg +
7539      `<img class="imageSource" style="width:` +
7540      width +
7541      `%;margin-left: auto;margin-right: auto;" src="` +
7542      v.nowPlayingImageURL +
7543      `">
7544						<div class="el-overlay">
7545							<ul class="el-info p-t-20 m-t-20">
7546								<li><a class="btn b-none inline-popups" href="#` +
7547      v.session +
7548      `" data-effect="mfp-zoom-out"><i class="mdi mdi-server-network mdi-24px"></i></a></li>
7549								<li><a class="btn b-none metadata-get" data-source="` +
7550      source +
7551      `" data-key="` +
7552      v.metadataKey +
7553      `" data-uid="` +
7554      v.uid +
7555      `"><i class="mdi mdi-information mdi-24px"></i></a></li>
7556								<li><a class="btn b-none openTab" data-tab-name="` +
7557      v.tabName +
7558      `" data-type="` +
7559      v.type +
7560      `" data-open-tab="` +
7561      v.openTab +
7562      `" data-url="` +
7563      v.address +
7564      `" href="javascript:void(0);"><i class=" mdi mdi-` +
7565      sourceIcon +
7566      ` mdi-24px"></i></a></li>
7567								<li><a class="btn b-none refreshImage" data-type="nowPlaying" data-image="` +
7568      v.nowPlayingOriginalImage +
7569      `" href="javascript:void(0);"><i class="mdi mdi-refresh mdi-24px"></i></a></li>
7570								<a class="inline-popups ` +
7571      v.uid +
7572      ` hidden" href="#` +
7573      v.uid +
7574      `-metadata-div" data-effect="mfp-zoom-out"></a>
7575							</ul>
7576						</div>
7577					</div>
7578					<div class="el-card-content">
7579						<div class="progress">
7580							<div class="progress-bar progress-bar-info" style="width: ` +
7581      v.watched +
7582      `%;" role="progressbar"><span class="hidden">` +
7583      v.watched +
7584      `%</span></div>
7585							<div class="progress-bar progress-bar-inverse" style="width: ` +
7586      v.transcoded +
7587      `%;" role="progressbar"></div>
7588						</div>
7589						<h3 class="box-title pull-left p-l-10 elip" style="width:90%">` +
7590      v.nowPlayingTitle +
7591      `</h3>
7592						<h3 class="box-title pull-right vertical-middle" style="width:10%"><i class="icon-control-` +
7593      v.state +
7594      ` fa-fw text-info" style=""></i></h3>
7595						<div class="clearfix"></div>
7596						<small class="pull-left p-l-10 w-50 elip"><span class="pull-left"><i class="` +
7597      icon +
7598      ` fa-fw text-info"></i>` +
7599      v.nowPlayingBottom +
7600      `</span></small>
7601						<small class="pull-right p-r-10 w-50"><span class="pull-right"><span class="">` +
7602      v.user +
7603      ` <i class="icon-user"></i></span></span></small>
7604						<br>
7605					</div>
7606				</div>
7607			</div>
7608		</div>
7609		<div id="` +
7610      v.session +
7611      `" class="white-popup mfp-with-anim mfp-hide">
7612			<div class="col-md-6 col-md-offset-3">
7613				<div class="white-box m-b-0 bg-info">
7614					<h3 class="text-white box-title m-b-0">` +
7615      v.sessionType +
7616      `<span class="pull-right"><i class="mdi mdi-network-upload"></i> ` +
7617      v.bandwidth +
7618      ` kbps <button type="button" class="btn bg-org btn-circle close-popup m-l-10"><i class="fa fa-times"></i> </button></span></h3>
7619				</div>
7620				<div class="white-box">
7621					<div class="row">
7622						<div class="p-l-20 p-r-20">
7623							<div class="pull-left">
7624								<span class="text-uppercase"><i class="mdi mdi-` +
7625      v.bandwidthType +
7626      `"></i> ` +
7627      v.bandwidthType +
7628      `</span>
7629								<span class="text-uppercase"><i class="mdi mdi-account-network"></i> ` +
7630      v.userAddress +
7631      `</span>
7632								` +
7633      streamInfo +
7634      `
7635								<div class="text-muted m-t-20 text-uppercase"><span class="text-uppercase"><i class="mdi mdi-` +
7636      source +
7637      `"></i> Product: ` +
7638      v.userStream.product +
7639      `</span></div>
7640								<div class="text-muted m-t-20 text-uppercase"><span class="text-uppercase"><i class="mdi mdi-laptop-mac"></i> Device: ` +
7641      v.userStream.device +
7642      `</span></div>
7643							</div>
7644							<div data-label="` +
7645      v.watched +
7646      `%" class="css-bar css-bar-` +
7647      Math.ceil(v.watched / 5) * 5 +
7648      ` css-bar-lg m-b-0  css-bar-info pull-right">` +
7649      userThumb +
7650      `</div>
7651						</div>
7652					</div>
7653				</div>
7654            </div>
7655		</div>
7656		<div id="` +
7657      v.uid +
7658      `-metadata-div" class="white-popup mfp-with-anim mfp-hide">
7659	        <div class="col-md-8 col-md-offset-2 ` +
7660      v.uid +
7661      `-metadata-info"></div>
7662	    </div>
7663		`;
7664  });
7665  cards += "</div><!--end-->";
7666  cards += buildStreamTooltip(bandwidthDetails, streamDetails, source);
7667  return cards;
7668}
7669function buildStreamTooltip(bandwidth, streams, type) {
7670  var html = "";
7671  var streamText = "Streams: ";
7672  var bandwidthText = " | Bandwidth: ";
7673  var bandwidthTotal =
7674    parseFloat(bandwidth["wan"]) + parseFloat(bandwidth["lan"]);
7675  if (type !== "plex") {
7676    bandwidthText += (parseFloat(bandwidth["wan"]) / 1000).toFixed(1) + " Mbps";
7677  } else {
7678    bandwidthText += (parseFloat(bandwidthTotal) / 1000).toFixed(1) + " Mbps";
7679    if (bandwidth["wan"] !== 0) {
7680      bandwidthText +=
7681        " | WAN: " + (parseFloat(bandwidth["wan"]) / 1000).toFixed(1) + " Mbps";
7682    }
7683    if (bandwidth["lan"] !== 0) {
7684      bandwidthText +=
7685        " | LAN: " + (parseFloat(bandwidth["lan"]) / 1000).toFixed(1) + " Mbps";
7686    }
7687  }
7688  var spacer = "";
7689  if (streams["direct"] !== 0) {
7690    streamText += streams["direct"] + " Direct Play(s)";
7691    spacer = " & ";
7692  }
7693  if (streams["transcode"] !== 0) {
7694    streamText += spacer + streams["transcode"] + " Transcode(s)";
7695  }
7696  html +=
7697    '<span class="label label-info m-l-20 mouse" title="" data-toggle="tooltip" data-original-title="' +
7698    streamText +
7699    bandwidthText +
7700    '" data-placement="bottom"><i class="fa fa-info"></i></span>';
7701  return (
7702    `
7703    <script>$('.streamDetails-` +
7704    type +
7705    `').html('` +
7706    html +
7707    `');$('[data-toggle="tooltip"]').tooltip();</script>
7708    `
7709  );
7710}
7711function buildRecentItem(array, type, extra = null) {
7712  var items = "";
7713  $.each(array, function (i, v) {
7714    if (extra == null) {
7715      var className = "";
7716      var extraImg = "";
7717      switch (v.type) {
7718        case "music":
7719          className = "recent-cover recent-item recent-music";
7720          extraImg =
7721            '<img src="' +
7722            v.imageURL +
7723            '" class="imageSourceAlt imageSourceTop recent-cover"><img src="' +
7724            v.imageURL +
7725            '" class="imageSourceAlt imageSourceBottom recent-cover">';
7726          break;
7727        case "movie":
7728          className = "recent-poster recent-item recent-movie";
7729          break;
7730        case "tv":
7731          className = "recent-poster recent-item recent-tv";
7732          break;
7733        case "video":
7734          className = "recent-poster recent-item recent-video";
7735          break;
7736        default:
7737      }
7738      items +=
7739        `
7740			<div class="item lazyload ` +
7741        className +
7742        ` metadata-get mouse imageSource" data-source="` +
7743        type +
7744        `" data-key="` +
7745        v.metadataKey +
7746        `" data-uid="` +
7747        v.uid +
7748        `" data-src="` +
7749        v.imageURL +
7750        `">
7751				` +
7752        extraImg +
7753        `
7754				<div class="hover-homepage-item">
7755				    <span class="elip request-title-movie">
7756					    <a class="text-white refreshImage" data-type="recent-item" data-image="` +
7757        v.originalImage +
7758        `" href="javascript:void(0);"><i class="mdi mdi-refresh mdi-24px"></i></a>
7759					</span>
7760				</div>
7761				<span class="elip recent-title">` +
7762        v.title +
7763        `<br/>` +
7764        v.secondaryTitle +
7765        `</span>
7766				<div id="` +
7767        v.uid +
7768        `-metadata-div" class="white-popup mfp-with-anim mfp-hide">
7769			        <div class="col-md-8 col-md-offset-2 ` +
7770        v.uid +
7771        `-metadata-info"></div>
7772			    </div>
7773			</div>
7774			`;
7775    } else {
7776      items +=
7777        `
7778			<a class="inline-popups ` +
7779        v.uid +
7780        ` hidden" href="#` +
7781        v.uid +
7782        `-metadata-div" data-effect="mfp-zoom-out"></a>
7783			`;
7784    }
7785  });
7786  return items;
7787}
7788function buildPlaylistItem(array, type, extra = null) {
7789  var items = "";
7790  $.each(array, function (i, v) {
7791    if (i !== "title") {
7792      if (extra == null) {
7793        items +=
7794          `
7795				<div class="item lazyload recent-poster metadata-get mouse imageSource" data-source="` +
7796          type +
7797          `" data-key="` +
7798          v.metadataKey +
7799          `" data-uid="` +
7800          v.uid +
7801          `" data-src="` +
7802          v.imageURL +
7803          `">
7804					<div class="hover-homepage-item">
7805					    <span class="elip request-title-movie">
7806						    <a class="text-white refreshImage" data-type="recent-item" data-image="` +
7807          v.originalImage +
7808          `" href="javascript:void(0);"><i class="mdi mdi-refresh mdi-24px"></i></a>
7809						</span>
7810					</div>
7811					<span class="elip recent-title">` +
7812          v.title +
7813          `</span>
7814					<div id="` +
7815          v.uid +
7816          `-metadata-div" class="white-popup mfp-with-anim mfp-hide">
7817				        <div class="col-md-8 col-md-offset-2 ` +
7818          v.uid +
7819          `-metadata-info"></div>
7820				    </div>
7821				</div>
7822				`;
7823      } else {
7824        items +=
7825          `
7826				<a class="inline-popups ` +
7827          v.uid +
7828          ` hidden" href="#` +
7829          v.uid +
7830          `-metadata-div" data-effect="mfp-zoom-out"></a>
7831				`;
7832      }
7833    }
7834  });
7835  return items;
7836}
7837function buildRequestAdminMenuItem(value, category, id, type) {
7838  var action = "";
7839  var text = "";
7840  var extra = "";
7841  switch (category) {
7842    case "approved":
7843      if (value) {
7844        //nada
7845      } else {
7846        action = "approve";
7847        text = "Approve";
7848        extra =
7849          `<li><a class="mouse" onclick="requestActions('` +
7850          id +
7851          `', 'deny', '` +
7852          type +
7853          `');" lang="en">Deny</a></li>`;
7854      }
7855      break;
7856    case "available":
7857      if (value) {
7858        action = "unavailable";
7859        text = "Mark as Unavailable";
7860      } else {
7861        action = "available";
7862        text = "Mark as Available";
7863      }
7864      break;
7865    default:
7866  }
7867  return action
7868    ? `<li><a class="mouse" onclick="requestActions('` +
7869        id +
7870        `', '` +
7871        action +
7872        `', '` +
7873        type +
7874        `');" lang="en">` +
7875        text +
7876        `</a></li>` +
7877        extra
7878    : "";
7879}
7880function buildRequestItem(array, extra = null) {
7881  var items = "";
7882  let service = activeInfo.settings.homepage.requests.service;
7883  $.each(array, function (i, v) {
7884    if (extra == null) {
7885      var approveID =
7886        v.type == "tv" && service === "ombi" ? v.id : v.request_id;
7887      var iconType = v.type == "tv" ? "fa-tv " : "fa-film";
7888      var badge = "";
7889      var badge2 = "";
7890      var bg = v.background.includes(".")
7891        ? v.background
7892        : "plugins/images/homepage/no-np.png";
7893      v.user =
7894        (activeInfo.settings.homepage.ombi.alias && service === "ombi") ||
7895        (activeInfo.settings.homepage.overseerr.enabled &&
7896          service === "overseerr")
7897          ? v.userAlias
7898          : v.user;
7899      //Set Status
7900      var status = v.approved
7901        ? '<span class="badge bg-org m-r-10" lang="en">Approved</span>'
7902        : '<span class="badge bg-danger m-r-10" lang="en">Unapproved</span>';
7903      status += v.available
7904        ? '<span class="badge bg-org m-r-10" lang="en">Available</span>'
7905        : '<span class="badge bg-danger m-r-10" lang="en">Unavailable</span>';
7906      status += v.denied
7907        ? '<span class="badge bg-danger m-r-10" lang="en">Denied</span>'
7908        : "";
7909      //Set Class
7910      var className = v.approved ? "request-approved" : "request-unapproved";
7911      className += v.available ? " request-available" : " request-unavailable";
7912      className += v.denied ? " request-denied" : " request-notdenied";
7913      //Set badge
7914      badge = v.approved ? "bg-info" : "bg-warning";
7915      badge = v.denied ? "bg-danger" : badge;
7916      badge2 = v.available ? "bg-success" : "bg-danger";
7917      //Is Admin?
7918      var adminFunctions =
7919        `<div class="btn-group m-r-10">
7920                    <button aria-expanded="false" data-toggle="dropdown" class="btn btn-info btn-outline dropdown-toggle waves-effect waves-light" type="button"> <i class="fa fa-ellipsis-v m-r-5"></i> <span class="caret"></span></button>
7921                    <ul role="menu" class="dropdown-menu">
7922						<li><h5 class="text-center" lang="en">Request Options</h5></li>
7923						<li class="divider"></li>
7924						` +
7925        buildRequestAdminMenuItem(v.approved, "approved", approveID, v.type) +
7926        `
7927						` +
7928        buildRequestAdminMenuItem(v.available, "available", approveID, v.type) +
7929        `
7930						<li><a class="mouse" onclick="requestActions('` +
7931        v.request_id +
7932        `', 'delete', '` +
7933        v.type +
7934        `');" lang="en">Delete</a></li>
7935                    </ul>
7936                </div>`;
7937      adminFunctions = activeInfo.user.groupID <= 1 ? adminFunctions : "";
7938      var user =
7939        activeInfo.user.groupID <= 1
7940          ? '<span lang="en">Requested By:</span> ' + v.user
7941          : "";
7942      var user2 = activeInfo.user.groupID <= 1 ? "<br>" + v.user : "";
7943      var divId = v.type == "movie" ? v.request_id : v.id;
7944      items +=
7945        `
7946				<div class="item lazyload recent-poster request-item request-` +
7947        v.type +
7948        ` ` +
7949        className +
7950        ` request-` +
7951        divId +
7952        `-div mouse" data-target="request-` +
7953        v.id +
7954        `" data-src="` +
7955        v.poster +
7956        `">
7957					<div class="outside-request-div">
7958						<div class="inside-over-request-div ` +
7959        badge2 +
7960        `"></div>
7961						<div class="inside-request-div ` +
7962        badge +
7963        `"></div>
7964					</div>
7965					<div class="hover-homepage-item"></div>
7966					<span class="elip request-title-` +
7967        v.type +
7968        `"><i class="fa ` +
7969        iconType +
7970        `"></i></span>
7971					<span class="elip recent-title">` +
7972        v.title +
7973        user2 +
7974        `</span>
7975					<div id="request-` +
7976        v.id +
7977        `" class="white-popup mfp-with-anim mfp-hide">
7978						<div class="col-md-8 col-md-offset-2">
7979							<div class="white-box m-b-0">
7980								<div class="user-bg lazyload" data-src="` +
7981        bg +
7982        `">
7983									<div class="col-xs-2 p-10">` +
7984        adminFunctions +
7985        `</div>
7986									<div class="col-xs-10">
7987										<h2 class="m-b-0 font-medium pull-right text-right">
7988											` +
7989        v.title +
7990        `<button type="button" class="btn bg-org btn-circle close-popup m-l-10"><i class="fa fa-times"></i> </button><br>
7991											<small class="m-t-0 text-white">` +
7992        user +
7993        `</small><br>
7994											` +
7995        buildYoutubeLink(v.title + " " + v.type) +
7996        `
7997										</h2>
7998									</div>
7999									<div class="genre-list p-10">` +
8000        status +
8001        `</div>
8002								</div>
8003							</div>
8004							<div class="panel panel-info p-b-0 p-t-0">
8005								<div class="panel-body p-b-0 p-t-0 m-b-0">
8006									<div class="p-20 text-center">
8007										<p class="">` +
8008        v.overview +
8009        `</p>
8010									</div>
8011									<div class="row">
8012										<div class="col-lg-12">
8013											<div class="owl-carousel owl-theme metadata-actors p-b-10"></div>
8014										</div>
8015									</div>
8016								</div>
8017							</div>
8018						</div>
8019					</div>
8020				</div>
8021				`;
8022    } else {
8023      items +=
8024        `
8025				<a class="inline-popups hidden" id="link-request-` +
8026        v.id +
8027        `" href="#request-` +
8028        v.id +
8029        `" data-effect="mfp-zoom-out" ></a>
8030				`;
8031    }
8032  });
8033  return items !== "" || extra == null
8034    ? items
8035    : '<h2 class="text-center">No items</h2>';
8036}
8037function buildStream(array, type) {
8038  var streams =
8039    typeof array.content !== "undefined" ? array.content.length : false;
8040  var originalType = type;
8041  //type = (type === 'emby' && activeInfo.settings.homepage.media.jellyfin) ? 'jellyfin' : type;
8042  return streams
8043    ? `
8044	<div id="` +
8045        type +
8046        `Streams">
8047		<div class="el-element-overlay row">
8048		    <div class="col-md-12">
8049		        <h4 class="pull-left homepage-element-title"><span lang="en">Active</span> ` +
8050        toUpper(type) +
8051        ` <span lang="en">Streams</span> : </h4><h4 class="pull-left">&nbsp;<span class="label label-info m-l-20 checkbox-circle mouse" onclick="homepageStream('` +
8052        originalType +
8053        `')">` +
8054        streams +
8055        `</span><span class="streamDetails-` +
8056        type +
8057        `"></span></h4>
8058		        <hr class="hidden-xs">
8059		    </div>
8060			<div class="clearfix"></div>
8061		    <!-- .cards -->
8062			` +
8063        buildStreamItem(array.content, type) +
8064        `
8065		    <!-- /.cards-->
8066		</div>
8067	</div>
8068	<div class="clearfix"></div>
8069	`
8070    : "";
8071}
8072function buildRecent(array, type) {
8073  var recent = typeof array.content !== "undefined" ? true : false;
8074  array.content = recent ? Object.values(array.content) : false;
8075  var movie = recent
8076    ? array.content.filter((p) => p.type == "movie").length > 0
8077      ? true
8078      : false
8079    : false;
8080  var tv = recent
8081    ? array.content.filter((p) => p.type == "tv").length > 0
8082      ? true
8083      : false
8084    : false;
8085  var video = recent
8086    ? array.content.filter((p) => p.type == "video").length > 0
8087      ? true
8088      : false
8089    : false;
8090  var music = recent
8091    ? array.content.filter((p) => p.type == "music").length > 0
8092      ? true
8093      : false
8094    : false;
8095  var dropdown = "";
8096  var header = "";
8097  var headerAlt = "";
8098  var refreshType = type;
8099  //type = (type === 'emby' && activeInfo.settings.homepage.media.jellyfin) ? 'jellyfin' : type;
8100  dropdown +=
8101    recent && movie
8102      ? `<li><a data-filter="recent-movie" server-filter="` +
8103        type +
8104        `" href="javascript:void(0);">Movies</a></li>`
8105      : "";
8106  dropdown +=
8107    recent && tv
8108      ? `<li><a data-filter="recent-tv" server-filter="` +
8109        type +
8110        `" href="javascript:void(0);">Shows</a></li>`
8111      : "";
8112  dropdown +=
8113    recent && video
8114      ? `<li><a data-filter="recent-video" server-filter="` +
8115        type +
8116        `" href="javascript:void(0);">Videos</a></li>`
8117      : "";
8118  dropdown +=
8119    recent && music
8120      ? `<li><a data-filter="recent-music" server-filter="` +
8121        type +
8122        `" href="javascript:void(0);">Music</a></li>`
8123      : "";
8124  var dropdownMenu =
8125    `
8126	<div class="btn-group pull-right">
8127		<button type="button" class="btn btn-info waves-effect hidden-xs" onclick="owlChange('` +
8128    type +
8129    `-recent','previous');"><i class="fa fa-chevron-left"></i></button>
8130		<button type="button" class="btn btn-info waves-effect hidden-xs" onclick="owlChange('` +
8131    type +
8132    `-recent','next');"><i class="fa fa-chevron-right"></i></button>
8133		<button aria-expanded="false" data-toggle="dropdown" class="btn btn-info dropdown-toggle waves-effect waves-light" type="button">
8134			<i class="fa fa-filter m-r-5"></i><span class="caret"></span>
8135		</button>
8136		<ul role="menu" class="dropdown-menu recent-filter">
8137			<li><a data-filter="all" server-filter="` +
8138    type +
8139    `" href="javascript:void(0);">All</a></li>
8140			<li class="divider"></li>
8141			` +
8142    dropdown +
8143    `
8144		</ul>
8145	</div>`;
8146  if (activeInfo.settings.homepage.options.alternateHomepageHeaders) {
8147    var headerAlt =
8148      `
8149		<div class="col-md-12">
8150			<h4 class="pull-left homepage-element-title"><span class="mouse" onclick="homepageRecent('` +
8151      type +
8152      `')" lang="en">Recently Added</span> : </h4><h4 class="pull-left">&nbsp;</h4>
8153			` +
8154      dropdownMenu +
8155      `
8156			<hr class="hidden-xs"><div class="clearfix"></div>
8157		</div>
8158		<div class="clearfix"></div>
8159		`;
8160  } else {
8161    var header =
8162      `
8163		<div class="panel-heading bg-info p-t-10 p-b-10">
8164			<span onclick="homepageRecent('` +
8165      type +
8166      `')" class="pull-left m-t-5 mouse"><img class="lazyload homepageImageTitle" data-src="plugins/images/tabs/` +
8167      type +
8168      `.png"> &nbsp; <span lang="en">Recently Added</span></span>
8169			` +
8170      dropdownMenu +
8171      `
8172			<div class="clearfix"></div>
8173		</div>
8174		`;
8175  }
8176  return recent
8177    ? `
8178	<div id="` +
8179        type +
8180        `Recent" class="row">
8181		` +
8182        headerAlt +
8183        `
8184        <div class="col-lg-12">
8185            <div class="panel panel-default">
8186				` +
8187        header +
8188        `
8189                <div class="panel-wrapper p-b-0 collapse in">
8190					<div class="` +
8191        type +
8192        `-recent-hidden hidden"></div>
8193                    <div class="owl-carousel owl-theme recent-items ` +
8194        type +
8195        `-recent">
8196						` +
8197        buildRecentItem(array.content, type) +
8198        `
8199                    </div>
8200					` +
8201        buildRecentItem(array.content, type, true) +
8202        `
8203                </div>
8204            </div>
8205        </div>
8206    </div>
8207	`
8208    : "";
8209}
8210function owlChange(elm, action) {
8211  switch (action) {
8212    case "next":
8213      $("." + elm).trigger("next.owl");
8214      break;
8215    case "previous":
8216      $("." + elm).trigger("prev.owl");
8217      break;
8218    default:
8219      return false;
8220  }
8221  return false;
8222}
8223function cleanPlaylistTitle(string) {
8224  var test = string.split(".");
8225  if (test.length > 1) {
8226    if (!isNaN(test[0])) {
8227      return test[1];
8228    }
8229  }
8230  return string;
8231}
8232function buildPlaylist(array, type) {
8233  var playlist =
8234    typeof array.content !== "undefined"
8235      ? Object.keys(array.content).length
8236      : false;
8237  var dropdown = "";
8238  var first = "";
8239  var firstButton = "";
8240  var hidden = "";
8241  var count = 0;
8242  var items = "";
8243  var header = "";
8244  var headerAlt = "";
8245  if (playlist) {
8246    $.each(array.content, function (i, v) {
8247      v.title = cleanPlaylistTitle(v.title);
8248      count++;
8249      first = count == 1 ? v.title : first;
8250      firstButton = count == 1 ? i + "-playlist" : firstButton;
8251      hidden = count == 1 ? "" : " owl-hidden hidden";
8252      dropdown +=
8253        `<li><a data-filter="` +
8254        i +
8255        `" server-filter="` +
8256        type +
8257        `" data-title="` +
8258        encodeURI(v.title) +
8259        `" href="javascript:void(0);">` +
8260        v.title +
8261        `</a></li>`;
8262
8263      items +=
8264        `
8265			<div class="owl-carousel owl-theme playlist-items ` +
8266        type +
8267        `-playlist ` +
8268        hidden +
8269        ` ` +
8270        i +
8271        `-playlist">
8272				` +
8273        buildPlaylistItem(v, type) +
8274        `
8275			</div>
8276			` +
8277        buildPlaylistItem(v, type, true) +
8278        `
8279			`;
8280    });
8281    var builtDropdown =
8282      `
8283		<button type="button" class="btn btn-info waves-effect hidden-xs playlist-previous" onclick="owlChange('` +
8284      firstButton +
8285      `','previous');"><i class="fa fa-chevron-left"></i></button>
8286		<button type="button" class="btn btn-info waves-effect hidden-xs playlist-next" onclick="owlChange('` +
8287      firstButton +
8288      `','next');"><i class="fa fa-chevron-right"></i></button>
8289		<button aria-expanded="false" data-toggle="dropdown" class="btn btn-info dropdown-toggle waves-effect waves-light" type="button">
8290			<i class="mdi mdi-playlist-play m-r-5 fa-lg"></i><span class="caret"></span>
8291		</button>
8292		<ul role="menu" class="dropdown-menu playlist-filter">
8293			` +
8294      dropdown +
8295      `
8296		</ul>
8297		`;
8298  }
8299  if (activeInfo.settings.homepage.options.alternateHomepageHeaders) {
8300    var headerAlt =
8301      `
8302		<div class="col-md-12">
8303			<h4 class="pull-left homepage-element-title"><span onclick="homepagePlaylist('` +
8304      type +
8305      `')" class="` +
8306      type +
8307      `-playlistTitle mouse">` +
8308      first +
8309      `</span> : </h4><h4 class="pull-left">&nbsp;</h4>
8310			<div class="btn-group pull-right">
8311				` +
8312      builtDropdown +
8313      `
8314			</div>
8315			<hr class="hidden-xs"><div class="clearfix"></div>
8316		</div>
8317		<div class="clearfix"></div>
8318		`;
8319  } else {
8320    var header =
8321      `
8322		<div class="panel-heading bg-info p-t-10 p-b-10">
8323			<span class="pull-left m-t-5 mouse homepage-element-title" onclick="homepagePlaylist('` +
8324      type +
8325      `')"><img class="lazyload homepageImageTitle" data-src="plugins/images/tabs/` +
8326      type +
8327      `.png"> &nbsp; <span class="` +
8328      type +
8329      `-playlistTitle">` +
8330      first +
8331      `</span></span>
8332			<div class="btn-group pull-right">
8333					` +
8334      builtDropdown +
8335      `
8336			</div>
8337			<div class="clearfix"></div>
8338		</div>
8339		`;
8340  }
8341  return playlist
8342    ? `
8343	<div id="` +
8344        type +
8345        `Playlist" class="row">
8346		` +
8347        headerAlt +
8348        `
8349        <div class="col-lg-12">
8350            <div class="panel panel-default">
8351                ` +
8352        header +
8353        `
8354                <div class="panel-wrapper p-b-0 collapse in">
8355                    ` +
8356        items +
8357        `
8358                </div>
8359            </div>
8360        </div>
8361    </div>
8362	`
8363    : "";
8364}
8365function buildRequest(service, div, array) {
8366  var requests = typeof array.content !== "undefined";
8367  var dropdown = "";
8368  var headerAlt = "";
8369  var header = "";
8370  var requestButton =
8371    activeInfo["settings"]["homepage"][service]["enabled"] === true
8372      ? `<button href="#new-request" id="newRequestButton" class="btn btn-info waves-effect waves-light inline-popups" data-effect="mfp-zoom-out"><i class="fa fa-search m-l-5"></i></button>`
8373      : "";
8374  if (requests) {
8375    var builtDropdown =
8376      `
8377		<button type="button" class="btn btn-info waves-effect hidden-xs" onclick="owlChange('request-items-${service}','previous');"><i class="fa fa-chevron-left"></i></button>
8378		<button type="button" class="btn btn-info waves-effect hidden-xs" onclick="owlChange('request-items-${service}','next');"><i class="fa fa-chevron-right"></i></button>
8379		<button aria-expanded="false" data-toggle="dropdown" class="btn btn-info dropdown-toggle waves-effect waves-light" type="button">
8380			<i class="fa fa-filter m-r-5"></i><span class="caret"></span>
8381		</button>
8382		` +
8383      requestButton +
8384      `
8385		<div role="menu" class="dropdown-menu request-filter">
8386			<div class="checkbox checkbox-success m-l-20 checkbox-circle">
8387				<input id="request-filter-available-${service}" data-filter="request-available" class="filter-request-input" type="checkbox" checked="">
8388				<label for="request-filter-available-${service}"> <span lang="en">Available</span> </label>
8389			</div>
8390			<div class="checkbox checkbox-danger m-l-20 checkbox-circle">
8391				<input id="request-filter-unavailable-${service}" data-filter="request-unavailable"  class="filter-request-input" type="checkbox" checked="">
8392				<label for="request-filter-unavailable-${service}"> <span lang="en">Unavailable</span> </label>
8393			</div>
8394			<div class="checkbox checkbox-info m-l-20 checkbox-circle">
8395				<input id="request-filter-approved-${service}" data-filter="request-approved" class="filter-request-input" type="checkbox"  checked="">
8396				<label for="request-filter-approved-${service}"> <span lang="en">Approved</span> </label>
8397			</div>
8398			<div class="checkbox checkbox-warning m-l-20 checkbox-circle">
8399				<input id="request-filter-unapproved-${service}" data-filter="request-unapproved" class="filter-request-input" type="checkbox" checked="">
8400				<label for="request-filter-unapproved-${service}"> <span lang="en">Unapproved</span> </label>
8401			</div>
8402			<div class="checkbox checkbox-purple m-l-20 checkbox-circle">
8403				<input id="request-filter-denied-${service}" data-filter="request-denied" class="filter-request-input" type="checkbox" checked="">
8404				<label for="request-filter-denied-${service}"> <span lang="en">Denied</span> </label>
8405			</div>
8406			<div class="checkbox checkbox-inverse m-l-20 checkbox-circle">
8407				<input id="request-filter-movie-${service}" data-filter="request-movie" class="filter-request-input" type="checkbox" checked="">
8408				<label for="request-filter-movie-${service}"> <span lang="en">Movie</span> </label>
8409			</div>
8410			<div class="checkbox checkbox-inverse m-l-20 checkbox-circle">
8411				<input id="request-filter-tv-${service}" data-filter="request-tv" class="filter-request-input" type="checkbox" checked="">
8412				<label for="request-filter-tv-${service}"> <span lang="en">TV</span> </label>
8413			</div>
8414		</div>
8415
8416		`;
8417  }
8418  if (activeInfo.settings.homepage.options.alternateHomepageHeaders) {
8419    var headerAlt =
8420      `
8421		<div class="col-md-12">
8422			<h4 class="pull-left homepage-element-title"><span class="mouse" onclick="homepageRequests('${service}')" lang="en">Requests</span> : </h4><h4 class="pull-left">&nbsp;</h4>
8423			<div class="btn-group pull-right">
8424				` +
8425      builtDropdown +
8426      `
8427			</div>
8428			<hr class="hidden-xs"><div class="clearfix"></div>
8429		</div>
8430		<div class="clearfix"></div>
8431		`;
8432  } else {
8433    var header =
8434      `
8435		<div class="panel-heading bg-info p-t-10 p-b-10">
8436			<span class="pull-left m-t-5 mouse homepage-element-title" onclick="homepageRequests('${service}')"><img class="lazyload homepageImageTitle" data-src="plugins/images/tabs/` +
8437      service +
8438      `.png"> &nbsp; <span lang="en">Requests</span></span>
8439			<div class="btn-group pull-right">
8440					` +
8441      builtDropdown +
8442      `
8443			</div>
8444			<div class="clearfix"></div>
8445		</div>
8446		`;
8447  }
8448  return requests
8449    ? `
8450	<div id="${service}-requests" class="row">
8451		` +
8452        headerAlt +
8453        `
8454        <div class="col-lg-12">
8455            <div class="panel panel-default">
8456				` +
8457        header +
8458        `
8459                <div class="panel-wrapper p-b-0 collapse in">
8460				<div class="owl-carousel owl-theme request-items-` +
8461        service +
8462        `">
8463					` +
8464        buildRequestItem(array.content) +
8465        `
8466				</div>
8467				` +
8468        buildRequestItem(array.content, true) +
8469        `
8470                </div>
8471            </div>
8472        </div>
8473    </div>
8474	<div id="new-request" class="white-popup mfp-with-anim mfp-hide">
8475		<div class="col-md-8 col-md-offset-2">
8476			<div class="white-box m-b-0 search-div resultBox-outside">
8477				<div class="form-group m-b-0">
8478					<div id="request-input-div" class="input-group">
8479						<input id="request-input" lang="en" placeholder="Request a Show or Movie" type="text" class="form-control inline-focus">
8480                        <input id="request-page" type="hidden" class="form-control">
8481                        <div class="input-group-btn">
8482                            <button type="button" class="btn waves-effect waves-light btn-info dropdown-toggle" data-toggle="dropdown" aria-expanded="false"><span lang="en">Suggestions</span> <span class="caret"></span></button>
8483                            <ul class="dropdown-menu dropdown-menu-right">
8484								<li><a onclick="requestList('org-mod', 'movie');" href="javascript:void(0)" lang="en">Organizr Mod Picks</a></li>
8485								<li><a onclick="requestList('theatre-movie', 'movie');" href="javascript:void(0)" lang="en">In Theatres</a></li>
8486								<li><a onclick="requestList('top-movie', 'movie');" href="javascript:void(0)" lang="en">Top Movies</a></li>
8487								<li><a onclick="requestList('pop-movie', 'movie');" href="javascript:void(0)" lang="en">Popular Movies</a></li>
8488								<li><a onclick="requestList('up-movie', 'movie');" href="javascript:void(0)" lang="en">Upcoming Movies</a></li>
8489								<li><a onclick="requestList('top-tv', 'tv');" href="javascript:void(0)" lang="en">Top TV</a></li>
8490								<li><a onclick="requestList('pop-tv', 'tv');" href="javascript:void(0)" lang="en">Popular TV</a></li>
8491                                <li><a onclick="requestList('today-tv', 'tv');" href="javascript:void(0)" lang="en">Airs Today TV</a></li>
8492                            </ul>
8493                        </div>
8494                    </div>
8495					<div class="clearfix"></div>
8496				</div>
8497				<div id="request-results" class="row el-element-overlay resultBox-inside"></div>
8498			</div>
8499		</div>
8500	</div>
8501	`
8502    : "";
8503}
8504function pagination(c, m) {
8505  var current = c,
8506    last = m,
8507    delta = 2,
8508    left = current - delta,
8509    right = current + delta + 1,
8510    range = [],
8511    rangeWithDots = [],
8512    l;
8513
8514  for (let i = 1; i <= last; i++) {
8515    if (i == 1 || i == last || (i >= left && i < right)) {
8516      range.push(i);
8517    }
8518  }
8519
8520  for (let i of range) {
8521    if (l) {
8522      if (i - l === 2) {
8523        rangeWithDots.push(l + 1);
8524      } else if (i - l !== 1) {
8525        rangeWithDots.push("...");
8526      }
8527    }
8528    rangeWithDots.push(i);
8529    l = i;
8530  }
8531
8532  return rangeWithDots;
8533}
8534function buildRequestResult(
8535  array,
8536  media_type = null,
8537  list = null,
8538  page = null,
8539  search = false
8540) {
8541  var comments = typeof array.comments !== "undefined" ? true : false;
8542  var comment = "";
8543  var results = ``;
8544  var buttons = ``;
8545  var next = ``;
8546  var tv = 0;
8547  var movie = 0;
8548  var total = 0;
8549  var totalPages = array.total_pages;
8550  var currentPage = page * 1;
8551  var pagePrevious = page * 1 - 1;
8552  var pageNext = page * 1 + 1;
8553  var pageFirst = 1;
8554  var pageLast = totalPages;
8555  var previousHidden = currentPage == 1 ? "disabled" : "";
8556  var nextHidden = currentPage == totalPages ? "disabled" : "";
8557  var pageList = "";
8558  let previousEnabled = pagePrevious !== 0;
8559  let nextEnabled = pageNext <= totalPages;
8560  if (array.results.length == 0) {
8561    return '<h2 class="text-center" lang="en">No Results</h2>';
8562  }
8563  $.each(array.results, function (i, v) {
8564    media_type = v.media_type ? v.media_type : media_type;
8565    if (media_type == "tv" || media_type == "movie") {
8566      total = total + 1;
8567      tv = media_type == "tv" ? tv + 1 : tv;
8568      movie = media_type == "movie" ? movie + 1 : movie;
8569      var bg =
8570        v.poster_path !== null
8571          ? `https://image.tmdb.org/t/p/w300/` + v.poster_path
8572          : "plugins/images/homepage/no-list.png";
8573      var top = v.title
8574        ? v.title
8575        : v.original_title
8576        ? v.original_title
8577        : v.original_name
8578        ? v.original_name
8579        : "";
8580      var bottom = v.release_date
8581        ? v.release_date
8582        : v.first_air_date
8583        ? v.first_air_date
8584        : "";
8585      if (comments) {
8586        if (array.comments[media_type + ":" + v.id] !== null) {
8587          comment = array.comments[media_type + ":" + v.id];
8588        }
8589      }
8590      results +=
8591        `
8592			<div class="col-lg-3 col-md-4 col-sm-6 col-xs-12 m-t-20 request-result-item request-result-` +
8593        media_type +
8594        `">
8595	            <div class="white-box m-b-10">
8596	                <div class="el-card-item p-b-0">
8597	                    <div class="el-card-avatar el-overlay-1 m-b-5 preloader-` +
8598        v.id +
8599        `"> <img class="lazyload resultImages" data-src="` +
8600        bg +
8601        `">
8602	                        <div class="el-overlay">
8603								<span class="text-info p-a-5 font-normal">` +
8604        comment +
8605        `</span>
8606	                            <ul class="el-info">
8607	                                <li><a class="btn default btn-outline" href="javascript:void(0);" onclick="processRequest('` +
8608        v.id +
8609        `','` +
8610        media_type +
8611        `');"><i class="icon-link"></i>&nbsp; <span lang="en">Request</span></a></li>
8612	                                <li><a class="btn default btn-outline" href="https://www.themoviedb.org/` +
8613        media_type +
8614        `/` +
8615        v.id +
8616        `" target="_blank"><i class="icon-info"></i></a></li>
8617	                            </ul>
8618	                        </div>
8619	                    </div>
8620	                    <div class="el-card-content bg-org">
8621	                        <h3 class="box-title elip">` +
8622        top +
8623        `</h3> <small>` +
8624        bottom +
8625        `</small>
8626	                        <br>
8627						</div>
8628	                </div>
8629	            </div>
8630	        </div>
8631			`;
8632    }
8633    comment = "";
8634  });
8635  if (list && page && search == false) {
8636    $.each(pagination(currentPage, totalPages), function (key, value) {
8637      var activePage = currentPage == value ? "active" : "";
8638      var disabled = value == "..." ? "disabled" : "";
8639      var pageLink =
8640        value == "..."
8641          ? ""
8642          : `onclick="requestList('` +
8643            list +
8644            `', '` +
8645            media_type +
8646            `', '` +
8647            value +
8648            `');"`;
8649      pageList +=
8650        '<li class="' +
8651        activePage +
8652        disabled +
8653        '"> <a ' +
8654        pageLink +
8655        ' href="javascript:void(0)">' +
8656        value +
8657        "</a> </li>";
8658    });
8659    let previousOnclick = previousEnabled
8660      ? `onclick="requestList('${list}', '${media_type}', '${pagePrevious}')";`
8661      : ``;
8662    let nextOnclick = nextEnabled
8663      ? `onclick="requestList('${list}', '${media_type}', '${pageNext}')";`
8664      : ``;
8665    next =
8666      `
8667		<div class="clearfix"></div>
8668		<div class="button-box text-center p-b-0">
8669            <ul class="pagination m-b-0">
8670                <li class="` +
8671      previousHidden +
8672      `"> <a href="javascript:void(0)" ${previousOnclick}><i class="fa fa-angle-left"></i></a> </li>
8673                ` +
8674      pageList +
8675      `
8676                <li class="` +
8677      nextHidden +
8678      `"> <a href="javascript:void(0)" ${nextOnclick}><i class="fa fa-angle-right"></i></a> </li>
8679            </ul>
8680        </div>
8681		`;
8682  }
8683  if (list && page && search == true) {
8684    $.each(pagination(currentPage, totalPages), function (key, value) {
8685      var activePage = currentPage == value ? "active" : "";
8686      var disabled = value == "..." ? "disabled" : "";
8687      var pageLink =
8688        value == "..."
8689          ? ""
8690          : `onclick="$('#request-page').val(` + value + `);doneTyping();"`;
8691      pageList +=
8692        '<li class="' +
8693        activePage +
8694        disabled +
8695        '"> <a ' +
8696        pageLink +
8697        ' href="javascript:void(0)">' +
8698        value +
8699        "</a> </li>";
8700    });
8701    next =
8702      `
8703		<div class="clearfix"></div>
8704		<div class="button-box text-center p-b-0">
8705            <ul class="pagination m-b-0">
8706                <li class="` +
8707      previousHidden +
8708      `"> <a href="javascript:void(0)" onclick="$('#request-page').val(` +
8709      pagePrevious +
8710      `);doneTyping();"><i class="fa fa-angle-left"></i></a> </li>
8711                ` +
8712      pageList +
8713      `
8714                <li class="` +
8715      nextHidden +
8716      `"> <a href="javascript:void(0)" onclick="$('#request-page').val(` +
8717      pageNext +
8718      `);doneTyping();"><i class="fa fa-angle-right"></i></a> </li>
8719            </ul>
8720        </div>
8721		`;
8722  }
8723  var buttons =
8724    `
8725	<div class="button-box p-20 text-center p-b-0">
8726		<button class="btn btn-inverse waves-effect waves-light filter-request-result" data-filter="request-result-all"><span>` +
8727    total +
8728    `</span> <i class="fa fa-th-large m-l-5 fa-fw"></i></button>
8729		<button class="btn btn-primary waves-effect waves-light filter-request-result" data-filter="request-result-movie"><span>` +
8730    movie +
8731    `</span> <i class="fa fa-film m-l-5 fa-fw"></i></button>
8732        <button class="btn btn-info waves-effect waves-light filter-request-result" data-filter="request-result-tv"><span>` +
8733    tv +
8734    `</span> <i class="fa fa-tv m-l-5 fa-fw"></i></button>
8735    </div>
8736	`;
8737  return buttons + next + results + next;
8738}
8739function buildRequestOverseerrSeasons(array) {
8740  var hasSeasons = typeof array.data.seasons !== "undefined";
8741  if (hasSeasons) {
8742    let seasons = array.data.seasons;
8743    let id = array.data.id;
8744    let SeasonItems = "";
8745    $.each(seasons, function (i, v) {
8746      if (v.seasonNumber !== 0) {
8747        SeasonItems += `
8748					<tr>
8749						<td><input type="checkbox" name="overseerr-season-${v.seasonNumber}" class="js-switch overseerr-season" data-seasonNumber="${v.seasonNumber}" data-color="#6164c1" data-size="small" /></td>
8750						<td>${v.name}</td>
8751						<td>${v.episodeCount}</td>
8752					</tr>
8753				`;
8754      }
8755    });
8756    let html = `
8757			<div class="panel">
8758				<div class="bg-org2">
8759					<div class="panel-heading">Choose Seasons</div>
8760					<div class="panel-wrapper collapse in text-left">
8761						<div class="table-responsive">
8762							<table class="table color-bordered-table primary-bordered-table">
8763								<thead>
8764									<tr>
8765										<th width="20"><input type="checkbox" class="js-switch select-all-overseerr-seasons" data-color="#6164c1" data-size="small" /></th>
8766										<th lang="en">Season</th>
8767										<th lang="en"># Of Episodes</th>
8768									</tr>
8769								</thead>
8770								<tbody>${SeasonItems}</tbody>
8771							</table>
8772						</div>
8773						<div class="pull-right p-b-20">
8774							<button class="fcbtn btn btn-info btn-outline btn-1c" lang="en" onclick="swal.close();">Cancel</button>
8775							<button class="fcbtn btn btn-success btn-outline btn-1c submit-overseerr-seasons" lang="en" data-seasons="[]" data-id="${id}" disabled onclick="processOverseerrSeasons(this)">Request Seasons</button>
8776						</div>
8777					</div>
8778				</div>
8779			</div>
8780			`;
8781    swal({
8782      content: createElementFromHTML(html),
8783      button: null,
8784      className: "bg-org",
8785      dangerMode: false,
8786    });
8787  }
8788}
8789
8790function processOverseerrSeasons(el) {
8791  let seasons = $(el).attr("data-seasons");
8792  let id = $(el).attr("data-id");
8793  overseerrActions(id, "add", "tv", seasons);
8794}
8795function processRequest(id, type) {
8796  let service = activeInfo.settings.homepage.requests.service;
8797  switch (service) {
8798    case "ombi":
8799      requestActions(id, "add", type);
8800      return false;
8801    case "overseerr":
8802      if (
8803        type === "tv" &&
8804        activeInfo.settings.homepage.overseerr.userSelectTv === true
8805      ) {
8806        organizrAPI2(
8807          "GET",
8808          "api/v2/homepage/overseerr/metadata/" + type + "/" + id
8809        )
8810          .success(function (data) {
8811            try {
8812              let response = data.response;
8813              buildRequestOverseerrSeasons(response);
8814            } catch (e) {
8815              organizrCatchError(e, data);
8816            }
8817          })
8818          .fail(function (xhr) {
8819            OrganizrApiError(xhr, "Overseerr Error");
8820          });
8821      } else {
8822        requestActions(id, "add", type);
8823      }
8824      return false;
8825    default:
8826      organizrConsole(
8827        "Request Function",
8828        "Service for Processing not setup",
8829        "error"
8830      );
8831      return false;
8832  }
8833}
8834function requestActions(id = null, action = null, type = null, extra = null) {
8835  let service = activeInfo.settings.homepage.requests.service;
8836  switch (service) {
8837    case "ombi":
8838      ombiActions(id, action, type, extra);
8839      break;
8840    case "overseerr":
8841      overseerrActions(id, action, type, extra);
8842      break;
8843    default:
8844      organizrConsole(
8845        "Request Function",
8846        "Service for Request not setup",
8847        "error"
8848      );
8849      return false;
8850  }
8851}
8852//Overseerr Actions
8853function overseerrActions(id, action, type = null, extra = null) {
8854  ajaxloader(".request-" + id + "-div", "in");
8855  ajaxloader(".preloader-" + id, "in");
8856  //$.magnificPopup.close();
8857  messageSingle(
8858    window.lang.translate("Submitting Action to Overseerr"),
8859    "",
8860    activeInfo.settings.notifications.position,
8861    "#FFF",
8862    "success",
8863    "10000"
8864  );
8865  switch (action) {
8866    case "add":
8867      let seasons = extra !== null ? "/" + extra : "";
8868      var method = "POST";
8869      var apiUrl =
8870        "api/v2/homepage/overseerr/requests/" + type + "/" + id + seasons;
8871      var data = {};
8872      break;
8873    case "available":
8874    case "pending":
8875    case "unavailable":
8876    case "approve":
8877      var method = "POST";
8878      var apiUrl =
8879        "api/v2/homepage/overseerr/requests/" + type + "/" + id + "/" + action;
8880      var data = {};
8881      break;
8882    case "deny":
8883      var method = "PUT";
8884      var apiUrl =
8885        "api/v2/homepage/overseerr/requests/" + type + "/" + id + "/" + action;
8886      var data = {};
8887      break;
8888    case "delete":
8889      var method = "DELETE";
8890      var apiUrl = "api/v2/homepage/overseerr/requests/" + type + "/" + id;
8891      var data = {};
8892      break;
8893    default:
8894      return false;
8895  }
8896  organizrAPI2(method, apiUrl, data)
8897    .success(function (data) {
8898      try {
8899        let response = data.response;
8900        if (action == "add") {
8901          addTempRequest();
8902          setTimeout(function () {
8903            ajaxloader();
8904          }, 2000);
8905        }
8906        messageSingle(
8907          response.message,
8908          "",
8909          activeInfo.settings.notifications.position,
8910          "#FFF",
8911          "success",
8912          "5000"
8913        );
8914        homepageRequests("overseerr");
8915        cleanCloseSwal();
8916      } catch (e) {
8917        organizrCatchError(e, data);
8918      }
8919    })
8920    .fail(function (xhr) {
8921      ajaxloader();
8922      OrganizrApiError(xhr, "Overseerr Error");
8923    });
8924}
8925//Ombi actions
8926function ombiActions(id, action, type, extra = null) {
8927  var msg =
8928    activeInfo.user.groupID <= 1
8929      ? '<a href="https://github.com/tidusjar/Ombi/issues/2176" target="_blank">Not Org Fault - Ask Ombi</a>'
8930      : "Connection Error to Request Server";
8931  ajaxloader(".request-" + id + "-div", "in");
8932  ajaxloader(".preloader-" + id, "in");
8933  //$.magnificPopup.close();
8934  messageSingle(
8935    window.lang.translate("Submitting Action to Ombi"),
8936    "",
8937    activeInfo.settings.notifications.position,
8938    "#FFF",
8939    "success",
8940    "10000"
8941  );
8942  switch (action) {
8943    case "add":
8944      var method = "POST";
8945      var apiUrl = "api/v2/homepage/ombi/requests/" + type + "/" + id;
8946      var data = {};
8947      break;
8948    case "available":
8949    case "unavailable":
8950    case "approve":
8951      var method = "POST";
8952      var apiUrl =
8953        "api/v2/homepage/ombi/requests/" + type + "/" + id + "/" + action;
8954      var data = {};
8955      break;
8956    case "deny":
8957      var method = "PUT";
8958      var apiUrl =
8959        "api/v2/homepage/ombi/requests/" + type + "/" + id + "/" + action;
8960      var data = {};
8961      break;
8962    case "delete":
8963      var method = "DELETE";
8964      var apiUrl = "api/v2/homepage/ombi/requests/" + type + "/" + id;
8965      var data = {};
8966      break;
8967    default:
8968      return false;
8969  }
8970  organizrAPI2(method, apiUrl, data)
8971    .success(function (data) {
8972      try {
8973        let response = data.response;
8974        if (action == "add") {
8975          addTempRequest();
8976        }
8977        messageSingle(
8978          response.message,
8979          "",
8980          activeInfo.settings.notifications.position,
8981          "#FFF",
8982          "success",
8983          "5000"
8984        );
8985        homepageRequests("ombi");
8986        ajaxloader();
8987      } catch (e) {
8988        organizrCatchError(e, data);
8989      }
8990    })
8991    .fail(function (xhr) {
8992      ajaxloader();
8993      OrganizrApiError(xhr, "Ombi Error");
8994    });
8995}
8996
8997function addTempRequest() {
8998  let service = activeInfo.settings.homepage.requests.service;
8999  let html = `
9000	<div class="item lazyload recent-poster request-item request-adding  mouse" data-src="">
9001		<div class="outside-request-div">
9002			<div class="inside-over-request-div bg-danger"></div>
9003			<div class="inside-request-div bg-info"></div>
9004		</div>
9005		<div class="hover-homepage-item"></div>
9006		<span class="elip request-title-tv"><i class="fa fa-tv"></i></span>
9007		<span class="elip recent-title">Adding Request</span>
9008	</div>
9009	`;
9010  $(".request-items-" + service)
9011    .trigger("add.owl", [html, 0])
9012    .trigger("refresh.owl");
9013  setTimeout(function () {
9014    ajaxloader(".request-adding", "in");
9015  }, 100);
9016}
9017function cleanCloseSwal() {
9018  let state = swal.getState().isOpen;
9019  if (state === true) {
9020    swal.close();
9021  }
9022}
9023function doneTyping() {
9024  let title = $("#request-input").val();
9025  if (title == "") {
9026    return false;
9027  }
9028  var page = $("#request-page").val() ? $("#request-page").val() : 1;
9029  if (typeof searchTerm !== "undefined") {
9030    if (searchTerm !== $("#request-input").val()) {
9031      page = 1;
9032    }
9033  }
9034  ajaxloader(".search-div", "in");
9035  searchTerm = title;
9036  $("#request-page").val(page);
9037  requestSearch(title, page)
9038    .success(function (data) {
9039      $("#request-results").html(
9040        buildRequestResult(data, "", title, page, true)
9041      );
9042      if (bowser.mobile !== true) {
9043        $(".resultBox-inside").slimScroll({
9044          height: "100%",
9045          position: "right",
9046          size: "5px",
9047          color: "#dcdcdc",
9048        });
9049      }
9050      $(".mfp-wrap").animate(
9051        {
9052          scrollTop: "0",
9053        },
9054        500
9055      );
9056      ajaxloader();
9057    })
9058    .fail(function (xhr) {
9059      OrganizrApiError(xhr, "TMDB Error");
9060      ajaxloader();
9061    });
9062}
9063function requestList(list, type, page = 1) {
9064  ajaxloader(".search-div", "in");
9065  requestSearchList(list, page)
9066    .success(function (data) {
9067      if (typeof data.results !== "undefined") {
9068        var results = data.results;
9069      } else if (typeof data.items !== "undefined") {
9070        var results = data.items;
9071      }
9072      $("#request-results").html(buildRequestResult(data, type, list, page));
9073      if (bowser.mobile !== true) {
9074        $(".resultBox-inside").slimScroll({
9075          height: "100%",
9076          position: "right",
9077          size: "5px",
9078          color: "#dcdcdc",
9079        });
9080      }
9081      $(".mfp-wrap").animate(
9082        {
9083          scrollTop: "0",
9084        },
9085        500
9086      );
9087      ajaxloader();
9088    })
9089    .fail(function (xhr) {
9090      OrganizrApiError(xhr, "TMDB Error");
9091      ajaxloader();
9092    });
9093}
9094function buildDownloaderItem(array, source, type = "none") {
9095  var queue = "";
9096  var count = 0;
9097  var history = "";
9098  switch (source) {
9099    case "jdownloader":
9100      if (array.content === false) {
9101        queue =
9102          '<tr><td class="max-texts" lang="en">
9102Connection Error to ' +
9103          source +
9104          "</td></tr>";
9105        break;
9106      }
9107
9108      if (
9109        array.content.queueItems.length == 0 &&
9110        array.content.grabberItems.length == 0 &&
9111        array.content.encryptedItems.length == 0 &&
9112        array.content.offlineItems.length == 0
9113      ) {
9114        queue =
9115          '<tr><td class="max-texts" lang="en">Nothing in queue</td></tr>';
9116      } else {
9117        if (array.content.$status[0] == "RUNNING") {
9118          queue += `
9119                        <tr><td>
9120                            <a href="#" onclick="return false;"><span class="downloader mouse" data-source="jdownloader" data-action="pause" data-target="main"><i class="fa fa-pause"></i></span></a>
9121                            <a href="#" onclick="return false;"><span class="downloader mouse" data-source="jdownloader" data-action="stop" data-target="main"><i class="fa fa-stop"></i></span></a>
9122                        </td></tr>
9123                        `;
9124        } else if (array.content.$status[0] == "PAUSE") {
9125          queue += `<tr><td><a href="#" onclick="return false;"><span class="downloader mouse" data-source="jdownloader" data-action="resume" data-target="main"><i class="fa fa-fast-forward"></i></span></a></td></tr>`;
9126        } else {
9127          queue += `<tr><td><a href="#" onclick="return false;"><span class="downloader mouse" data-source="jdownloader" data-action="start" data-target="main"><i class="fa fa-play"></i></span></a></td></tr>`;
9128        }
9129        if (array.content.$status[1]) {
9130          queue += `<tr><td><a href="#" onclick="return false;"><span class="downloader mouse" data-source="jdownloader" data-action="update" data-target="main"><i class="fa fa-globe"></i></span></a></td></tr>`;
9131        }
9132      }
9133      $.each(array.content.queueItems, function (i, v) {
9134        count = count + 1;
9135        if (v.speed == null) {
9136          v.speed = "Stopped";
9137        }
9138        if (v.eta == null) {
9139          if (v.percentage == "100") {
9140            v.speed = "Completed";
9141            v.eta = "--";
9142          } else {
9143            v.eta = "--";
9144          }
9145        }
9146        if (v.enabled == null) {
9147          v.speed = "Disabled";
9148        }
9149        queue +=
9150          `
9151                <tr>
9152                    <td class="max-texts">` +
9153          v.name +
9154          `</td>
9155                    <td>` +
9156          v.speed +
9157          `</td>
9158                    <td class="hidden-xs" alt="` +
9159          v.done +
9160          `">` +
9161          v.size +
9162          `</td>
9163                    <td class="hidden-xs">` +
9164          v.eta +
9165          `</td>
9166                    <td class="text-right">
9167                        <div class="progress progress-lg m-b-0">
9168                            <div class="progress-bar progress-bar-info" style="width: ` +
9169          v.percentage +
9170          `%;" role="progressbar">` +
9171          v.percentage +
9172          `%</div>
9173                        </div>
9174                    </td>
9175                </tr>
9176                `;
9177      });
9178      $.each(array.content.grabberItems, function (i, v) {
9179        count = count + 1;
9180        queue +=
9181          `
9182                <tr>
9183                    <td class="max-texts">` +
9184          v.name +
9185          `</td>
9186                    <td>Online</td>
9187                    <td class="hidden-xs"> -- </td>
9188                    <td class="hidden-xs"> -- </td>
9189                    <td class="text-right">
9190                        <div class="progress progress-lg m-b-0">
9191                            <div class="progress-bar progress-bar-info" style="width: 0%;" role="progressbar">0%</div>
9192                        </div>
9193                    </td>
9194                </tr>
9195                `;
9196      });
9197      $.each(array.content.encryptedItems, function (i, v) {
9198        count = count + 1;
9199        queue +=
9200          `
9201                <tr>
9202                    <td class="max-texts">` +
9203          v.name +
9204          `</td>
9205                    <td>Encrypted</td>
9206                    <td class="hidden-xs"> -- </td>
9207                    <td class="hidden-xs"> -- </td>
9208                    <td class="text-right">
9209                        <div class="progress progress-lg m-b-0">
9210                            <div class="progress-bar progress-bar-info" style="width: 0%;" role="progressbar">0%</div>
9211                        </div>
9212                    </td>
9213                </tr>
9214                `;
9215      });
9216      $.each(array.content.offlineItems, function (i, v) {
9217        count = count + 1;
9218        queue +=
9219          `
9220                <tr>
9221                    <td class="max-texts">` +
9222          v.name +
9223          `</td>
9224                    <td>Offline</td>
9225                    <td class="hidden-xs"> -- </td>
9226                    <td class="hidden-xs"> -- </td>
9227                    <td class="text-right">
9228                        <div class="progress progress-lg m-b-0">
9229                            <div class="progress-bar progress-bar-info" style="width: 0%;" role="progressbar">0%</div>
9230                        </div>
9231                    </td>
9232                </tr>
9233                `;
9234      });
9235      break;
9236    case "sabnzbd":
9237      if (array.content === false) {
9238        queue =
9239          '<tr><td class="max-texts" lang="en">
9239Connection Error to ' +
9240          source +
9241          "</td></tr>";
9242        break;
9243      }
9244      if (array.content.queueItems.queue.paused) {
9245        var state = `<a href="#" onclick="return false;"><span class="downloader mouse" data-source="sabnzbd" data-action="resume" data-target="main"><i class="fa fa-play"></i></span></a>`;
9246        var active = "grayscale";
9247      } else {
9248        var state = `<a href="#" onclick="return false;"><span class="downloader mouse" data-source="sabnzbd" data-action="pause" data-target="main"><i class="fa fa-pause"></i></span></a>`;
9249        var active = "";
9250      }
9251      $(".sabnzbd-downloader-action").html(state);
9252
9253      if (array.content.queueItems.queue.slots.length == 0) {
9254        queue =
9255          '<tr><td class="max-texts" lang="en">Nothing in queue</td></tr>';
9256      }
9257      $.each(array.content.queueItems.queue.slots, function (i, v) {
9258        count = count + 1;
9259        var action = v.status == "Downloading" ? "pause" : "resume";
9260        var actionIcon = v.status == "Downloading" ? "pause" : "play";
9261        queue +=
9262          `
9263                <tr>
9264                    <td class="max-texts">` +
9265          v.filename +
9266          `</td>
9267                    <td class="hidden-xs sabnzbd-` +
9268          cleanClass(v.status) +
9269          `">` +
9270          v.status +
9271          `</td>
9272                    <td class="downloader mouse" data-target="` +
9273          v.nzo_id +
9274          `" data-source="sabnzbd" data-action="` +
9275          action +
9276          `"><i class="fa fa-` +
9277          actionIcon +
9278          `"></i></td>
9279                    <td class="hidden-xs"><span class="label label-info">` +
9280          v.cat +
9281          `</span></td>
9282                    <td class="hidden-xs">` +
9283          v.size +
9284          `</td>
9285                    <td class="hidden-xs" alt="` +
9286          v.eta +
9287          `">` +
9288          v.timeleft +
9289          `</td>
9290                    <td class="text-right">
9291                        <div class="progress progress-lg m-b-0">
9292                            <div class="progress-bar progress-bar-info" style="width: ` +
9293          v.percentage +
9294          `%;" role="progressbar">` +
9295          v.percentage +
9296          `%</div>
9297                        </div>
9298                    </td>
9299                </tr>
9300                `;
9301      });
9302      if (array.content.historyItems.history.slots.length == 0) {
9303        history =
9304          '<tr><td class="max-texts" lang="en">Nothing in history</td></tr>';
9305      }
9306      $.each(array.content.historyItems.history.slots, function (i, v) {
9307        history +=
9308          `
9309                <tr>
9310                    <td class="max-texts">` +
9311          v.name +
9312          `</td>
9313                    <td class="hidden-xs sabnzbd-` +
9314          cleanClass(v.status) +
9315          `">` +
9316          v.status +
9317          `</td>
9318                    <td class="hidden-xs"><span class="label label-info">` +
9319          v.category +
9320          `</span></td>
9321                    <td class="hidden-xs">` +
9322          v.size +
9323          `</td>
9324                    <td class="text-right">
9325                        <div class="progress progress-lg m-b-0">
9326                            <div class="progress-bar progress-bar-info" style="width: 100%;" role="progressbar">100%</div>
9327                        </div>
9328                    </td>
9329                </tr>
9330                `;
9331      });
9332      break;
9333    case "nzbget":
9334      if (array.content === false) {
9335        queue =
9336          '<tr><td class="max-texts" lang="en">Connection Error to ' +
9337          source +
9338          "</td></tr>";
9339        break;
9340      }
9341      if (array.content.queueItems.result.length == 0) {
9342        queue =
9343          '<tr><td class="max-texts" lang="en">Nothing in queue</td></tr>';
9344      }
9345      $.each(array.content.queueItems.result, function (i, v) {
9346        count = count + 1;
9347        var action = v.Status == "Downloading" ? "pause" : "resume";
9348        var actionIcon = v.Status == "Downloading" ? "pause" : "play";
9349        var percent = Math.floor(
9350          ((v.FileSizeMB - v.RemainingSizeMB) * 100) / v.FileSizeMB
9351        );
9352        var size = v.FileSizeMB * 1000000;
9353        v.Category = v.Category !== "" ? v.Category : "Not Set";
9354        queue +=
9355          `
9356                <tr>
9357                    <td class="max-texts">` +
9358          v.NZBName +
9359          `</td>
9360                    <td class="hidden-xs nzbget-` +
9361          cleanClass(v.Status) +
9362          `">` +
9363          v.Status +
9364          `</td>
9365                    <!--<td class="downloader mouse" data-target="` +
9366          v.NZBID +
9367          `" data-source="sabnzbd" data-action="` +
9368          action +
9369          `"><i class="fa fa-` +
9370          actionIcon +
9371          `"></i></td>-->
9372                    <td class="hidden-xs"><span class="label label-info">` +
9373          v.Category +
9374          `</span></td>
9375                    <td class="hidden-xs">` +
9376          humanFileSize(size, true) +
9377          `</td>
9378                    <td class="text-right">
9379                        <div class="progress progress-lg m-b-0">
9380                            <div class="progress-bar progress-bar-info" style="width: ` +
9381          percent +
9382          `%;" role="progressbar">` +
9383          percent +
9384          `%</div>
9385                        </div>
9386                    </td>
9387                </tr>
9388                `;
9389      });
9390      if (array.content.historyItems.result.length == 0) {
9391        history =
9392          '<tr><td class="max-texts" lang="en">Nothing in history</td></tr>';
9393      }
9394      $.each(array.content.historyItems.result, function (i, v) {
9395        v.Category = v.Category !== "" ? v.Category : "Not Set";
9396        var size = v.FileSizeMB * 1000000;
9397        history +=
9398          `
9399                <tr>
9400                    <td class="max-texts">` +
9401          v.NZBName +
9402          `</td>
9403                    <td class="hidden-xs nzbget-` +
9404          cleanClass(v.Status) +
9405          `">` +
9406          v.Status +
9407          `</td>
9408                    <td class="hidden-xs"><span class="label label-info">` +
9409          v.Category +
9410          `</span></td>
9411                    <td class="hidden-xs">` +
9412          humanFileSize(size, true) +
9413          `</td>
9414                    <td class="text-right">
9415                        <div class="progress progress-lg m-b-0">
9416                            <div class="progress-bar progress-bar-info" style="width: 100%;" role="progressbar">100%</div>
9417                        </div>
9418                    </td>
9419                </tr>
9420                `;
9421      });
9422      break;
9423    case "transmission":
9424      if (array.content === false) {
9425        queue =
9426          '<tr><td class="max-texts" lang="en">
9426Connection Error to ' +
9427          source +
9428          "</td></tr>";
9429        break;
9430      }
9431      if (array.content.queueItems == 0) {
9432        queue =
9433          '<tr><td class="max-texts" lang="en">Nothing in queue</td></tr>';
9434      }
9435      $.each(array.content.queueItems, function (i, v) {
9436        count = count + 1;
9437        switch (v.status) {
9438          case 7:
9439          case "7":
9440            var status = "No Peers";
9441            break;
9442          case 6:
9443          case "6":
9444            var status = "Seeding";
9445            break;
9446          case 5:
9447          case "5":
9448            var status = "Seeding Queued";
9449            break;
9450          case 4:
9451          case "4":
9452            var status = "Downloading";
9453            break;
9454          case 3:
9455          case "3":
9456            var status = "Queued";
9457            break;
9458          case 2:
9459          case "2":
9460            var status = "Checking Files";
9461            break;
9462          case 1:
9463          case "1":
9464            var status = "File Check Queued";
9465            break;
9466          case 0:
9467          case "0":
9468            var status = "Complete";
9469            break;
9470          default:
9471            var status = "Complete";
9472        }
9473        var percent = Math.floor(v.percentDone * 100);
9474        v.Category = v.Category !== "" ? v.Category : "Not Set";
9475        queue +=
9476          `
9477                <tr>
9478                    <td class="max-texts">` +
9479          v.name +
9480          `</td>
9481                    <td class="hidden-xs transmission-` +
9482          cleanClass(status) +
9483          `">` +
9484          status +
9485          `</td>
9486                    <td class="hidden-xs">` +
9487          v.downloadDir +
9488          `</td>
9489                    <td class="hidden-xs">` +
9490          humanFileSize(v.totalSize, true) +
9491          `</td>
9492                    <td class="text-right">
9493                        <div class="progress progress-lg m-b-0">
9494                            <div class="progress-bar progress-bar-info" style="width: ` +
9495          percent +
9496          `%;" role="progressbar">` +
9497          percent +
9498          `%</div>
9499                        </div>
9500                    </td>
9501                </tr>
9502                `;
9503      });
9504      break;
9505    case "rTorrent":
9506      if (array.content === false) {
9507        queue =
9508          '<tr><td class="max-texts" lang="en">Connection Error to ' +
9509          source +
9510          "</td></tr>";
9511        break;
9512      }
9513      if (array.content.queueItems == 0) {
9514        queue =
9515          '<tr><td class="max-texts" lang="en">Nothing in queue</td></tr>';
9516      }
9517      $.each(array.content.queueItems, function (i, v) {
9518        count = count + 1;
9519        var percent = Math.floor((v.downloaded / v.size) * 100);
9520        var size = v.size != -1 ? humanFileSize(v.size, false) : "?";
9521        var upload = v.seed !== "" ? humanFileSize(v.seed, true) : "0 B";
9522        var download = v.leech !== "" ? humanFileSize(v.leech, true) : "0 B";
9523        var upTotal =
9524          v.upTotal !== "" ? humanFileSize(v.upTotal, false) : "0 B";
9525        var downTotal =
9526          v.downTotal !== "" ? humanFileSize(v.downTotal, false) : "0 B";
9527        var date = new Date(0);
9528        date.setUTCSeconds(v.date);
9529        date = moment(date).format("LLL");
9530        queue +=
9531          `
9532                <tr>
9533                    <td class="max-texts"><span class="tooltip-info" data-toggle="tooltip" data-placement="right" title="" data-original-title="` +
9534          date +
9535          `">` +
9536          v.name +
9537          `</span></td>
9538                    <td class="hidden-xs rtorrent-` +
9539          cleanClass(v.status) +
9540          `">` +
9541          v.status +
9542          `</td>
9543                    <td class="hidden-xs"><span class="tooltip-info" data-toggle="tooltip" data-placement="right" title="" data-original-title="` +
9544          downTotal +
9545          `"><i class="fa fa-download"></i>&nbsp;` +
9546          download +
9547          `</span></td>
9548                    <td class="hidden-xs"><span class="tooltip-info" data-toggle="tooltip" data-placement="right" title="" data-original-title="` +
9549          upTotal +
9550          `"><i class="fa fa-upload"></i>&nbsp;` +
9551          upload +
9552          `</span></td>
9553                    <td class="hidden-xs">` +
9554          size +
9555          `</td>
9556                    <td class="hidden-xs"><span class="label label-info">` +
9557          v.label +
9558          `</span></td>
9559                    <td class="text-right">
9560                        <div class="progress progress-lg m-b-0">
9561                            <div class="progress-bar progress-bar-info" style="width: ` +
9562          percent +
9563          `%;" role="progressbar">` +
9564          percent +
9565          `%</div>
9566                        </div>
9567                    </td>
9568                </tr>
9569                `;
9570      });
9571      break;
9572    case "utorrent":
9573      if (array.content === false) {
9574        queue =
9575          '<tr><td class="max-texts" lang="en">
9575Connection Error to ' +
9576          source +
9577          "</td></tr>";
9578        break;
9579      }
9580      if (array.content.queueItems == 0) {
9581        queue =
9582          '<tr><td class="max-texts" lang="en">Nothing in queue</td></tr>';
9583      }
9584      $.each(array.content.queueItems, function (i, v) {
9585        count = count + 1;
9586        var upload = v.upSpeed !== "" ? humanFileSize(v.upSpeed, false) : "0 B";
9587        var download =
9588          v.downSpeed !== "" ? humanFileSize(v.downSpeed, false) : "0 B";
9589        var size = v.Size !== "" ? humanFileSize(v.Size, false) : "0 B";
9590        queue +=
9591          `
9592                <tr>
9593                    <td class="max-texts"><span class="tooltip-info" data-toggle="tooltip" data-placement="right" title="">` +
9594          v.Name +
9595          `</span></td>
9596		    <td class="hidden-xs utorrent-` +
9597          cleanClass(v.Status) +
9598          `">` +
9599          v.Status +
9600          `</td>
9601                    <td class="hidden-xs"><span class="label label-info">` +
9602          v.Labels +
9603          `</span></td>
9604		    <td class="hidden-xs"><span class="tooltip-info" data-toggle="tooltip" data-placement="right" title="" data-original-title="` +
9605          download +
9606          `"><i class="fa fa-download"></i>&nbsp;` +
9607          download +
9608          `</span></td>
9609                    <td class="hidden-xs"><span class="tooltip-info" data-toggle="tooltip" data-placement="right" title="" data-original-title="` +
9610          upload +
9611          `"><i class="fa fa-upload"></i>&nbsp;` +
9612          upload +
9613          `</span></td>
9614		    <td class="hidden-xs">` +
9615          size +
9616          `</td>
9617                    <td class="text-right">
9618                        <div class="progress progress-lg m-b-0">
9619                            <div class="progress-bar progress-bar-info" style="width: ` +
9620          v.Percent +
9621          `;" role="progressbar">` +
9622          v.Percent +
9623          `</div>
9624                        </div>
9625                    </td>
9626                </tr>
9627                `;
9628      });
9629      break;
9630    case "sonarr":
9631      if (array.content === false) {
9632        queue =
9633          '<tr><td class="max-texts" lang="en">Connection Error to ' +
9634          source +
9635          "</td></tr>";
9636        break;
9637      }
9638      if (array.content.queueItems == 0) {
9639        queue =
9640          '<tr><td class="max-texts" lang="en">Nothing in queue</td></tr>';
9641        break;
9642      }
9643      if (array.content.queueItems.records == 0) {
9644        queue =
9645          '<tr><td class="max-texts" lang="en">Nothing in queue</td></tr>';
9646        break;
9647      }
9648      let sonarrQueueSet =
9649        typeof array.content.queueItems.records == "undefined"
9650          ? array.content.queueItems
9651          : array.content.queueItems.records;
9652      $.each(sonarrQueueSet, function (i, v) {
9653        count = count + 1;
9654        var percent = Math.floor(((v.size - v.sizeleft) / v.size) * 100);
9655        percent = isNaN(percent) ? "0" : percent;
9656        var size = v.size != -1 ? humanFileSize(v.size, false) : "?";
9657        v.name = typeof v.series == "undefined" ? v.title : v.series.title;
9658        queue +=
9659          `
9660                <tr>
9661                    <td class="">` +
9662          v.name +
9663          `</td>
9664                    <td class="">S` +
9665          pad(v.episode.seasonNumber, 2) +
9666          `E` +
9667          pad(v.episode.episodeNumber, 2) +
9668          `</td>
9669                    <td class="max-texts">` +
9670          v.episode.title +
9671          `</td>
9672                    <td class="hidden-xs sonarr-` +
9673          cleanClass(v.status) +
9674          `">` +
9675          v.status +
9676          `</td>
9677                    <td class="hidden-xs">` +
9678          size +
9679          `</td>
9680                    <td class="hidden-xs"><span class="label label-info">` +
9681          v.protocol +
9682          `</span></td>
9683                    <td class="text-right">
9684                        <div class="progress progress-lg m-b-0">
9685                            <div class="progress-bar progress-bar-info" style="width: ` +
9686          percent +
9687          `%;" role="progressbar">` +
9688          percent +
9689          `%</div>
9690                        </div>
9691                    </td>
9692                </tr>
9693                `;
9694      });
9695      break;
9696    case "radarr":
9697      if (array.content === false) {
9698        queue =
9699          '<tr><td class="max-texts" lang="en">
9699Connection Error to ' +
9700          source +
9701          "</td></tr>";
9702        break;
9703      }
9704      if (array.content.queueItems == 0) {
9705        queue =
9706          '<tr><td class="max-texts" lang="en">Nothing in queue</td></tr>';
9707        break;
9708      }
9709      if (array.content.queueItems.records == 0) {
9710        queue =
9711          '<tr><td class="max-texts" lang="en">Nothing in queue</td></tr>';
9712        break;
9713      }
9714      let queueSet =
9715        typeof array.content.queueItems.records == "undefined"
9716          ? array.content.queueItems
9717          : array.content.queueItems.records;
9718      $.each(queueSet, function (i, v) {
9719        count = count + 1;
9720        var percent = Math.floor(((v.size - v.sizeleft) / v.size) * 100);
9721        percent = isNaN(percent) ? "0" : percent;
9722        var size = v.size != -1 ? humanFileSize(v.size, false) : "?";
9723        v.name = typeof v.movie == "undefined" ? v.title : v.movie.title;
9724        queue += `
9725                <tr>
9726                    <td class="max-texts">${v.name}</td>
9727                    <td class="hidden-xs sonarr-${cleanClass(v.status)}">${
9728          v.status
9729        }</td>
9730                    <td class="hidden-xs">${size}</td>
9731                    <td class="hidden-xs"><span class="label label-info">${
9732                      v.protocol
9733                    }</span></td>
9734                    <td class="text-right">
9735                        <div class="progress progress-lg m-b-0">
9736                            <div class="progress-bar progress-bar-info" style="width: ${percent}%;" role="progressbar">${percent}%</div>
9737                        </div>
9738                    </td>
9739                </tr>
9740                `;
9741      });
9742      if (queue == "") {
9743        queue =
9744          '<tr><td class="max-texts" lang="en">Nothing in queue</td></tr>';
9745      }
9746      break;
9747    case "qBittorrent":
9748      if (array.content === false) {
9749        queue =
9750          '<tr><td class="max-texts" lang="en">Connection Error to ' +
9751          source +
9752          "</td></tr>";
9753        break;
9754      }
9755      if (array.content.queueItems == 0) {
9756        queue =
9757          '<tr><td class="max-texts" lang="en">Nothing in queue</td></tr>';
9758      }
9759      $.each(array.content.queueItems, function (i, v) {
9760        count = count + 1;
9761        switch (v.state) {
9762          case "stalledDL":
9763            var status = "No Peers";
9764            break;
9765          case "metaDL":
9766            var status = "Getting Metadata";
9767            break;
9768          case "uploading":
9769            var status = "Seeding";
9770            break;
9771          case "queuedUP":
9772            var status = "Seeding Queued";
9773            break;
9774          case "downloading":
9775            var status = "Downloading";
9776            break;
9777          case "queuedDL":
9778            var status = "Queued";
9779            break;
9780          case "checkingDL":
9781          case "checkingUP":
9782            var status = "Checking Files";
9783            break;
9784          case "pausedDL":
9785            var status = "Paused";
9786            break;
9787          case "pausedUP":
9788            var status = "Complete";
9789            break;
9790          default:
9791            var status = "Complete";
9792        }
9793        var percent = Math.floor(v.progress * 100);
9794        var size = v.total_size != -1 ? humanFileSize(v.total_size, true) : "?";
9795        queue +=
9796          `
9797                <tr>
9798                    <td class="max-texts">` +
9799          v.name +
9800          `</td>
9801                    <td class="hidden-xs qbit-` +
9802          cleanClass(status) +
9803          `">` +
9804          status +
9805          `</td>
9806                    <td class="hidden-xs">` +
9807          v.save_path +
9808          `</td>
9809                    <td class="hidden-xs">` +
9810          size +
9811          `</td>
9812                    <td class="text-right">
9813                        <div class="progress progress-lg m-b-0">
9814                            <div class="progress-bar progress-bar-info" style="width: ` +
9815          percent +
9816          `%;" role="progressbar">` +
9817          percent +
9818          `%</div>
9819                        </div>
9820                    </td>
9821                </tr>
9822                `;
9823      });
9824      break;
9825    case "deluge":
9826      if (array.content === false) {
9827        queue =
9828          '<tr><td class="max-texts" lang="en">
9828Connection Error to ' +
9829          source +
9830          "</td></tr>";
9831        break;
9832      }
9833      if (array.content.queueItems.length == 0) {
9834        queue =
9835          '<tr><td class="max-texts" lang="en">Nothing in queue</td></tr>';
9836      }
9837      $.each(array.content.queueItems, function (i, v) {
9838        count = count + 1;
9839        var percent = Math.floor(v.progress);
9840        var size = v.total_size != -1 ? humanFileSize(v.total_size, true) : "?";
9841        var upload =
9842          v.upload_payload_rate != -1
9843            ? humanFileSize(v.upload_payload_rate, true)
9844            : "?";
9845        var download =
9846          v.download_payload_rate != -1
9847            ? humanFileSize(v.download_payload_rate, true)
9848            : "?";
9849        var action = v.Status == "Downloading" ? "pause" : "resume";
9850        var actionIcon = v.Status == "Downloading" ? "pause" : "play";
9851        queue +=
9852          `
9853                <tr>
9854                    <td class="max-texts">` + v.name;
9855        if (v.tracker_status != "")
9856          queue +=
9857            `<i class="fa fa-caret-down ml-2" style="cursor:pointer" onclick="$(this).toggleClass('fa-caret-down');$(this).toggleClass('fa-caret-up');$('#status-` +
9858            v.hash +
9859            `').toggleClass('d-none');" aria-hidden="true"></i><br /><div class="well mb-0 mt-2 p-3 d-none" id="status-` +
9860            v.hash +
9861            `">` +
9862            v.tracker_status +
9863            `</div>`;
9864        queue +=
9865          `</td>
9866                    <td class="hidden-xs deluge-` +
9867          cleanClass(v.state) +
9868          `">` +
9869          v.state +
9870          `</td>
9871                    <td class="hidden-xs">` +
9872          size +
9873          `</td>
9874                    <td class="hidden-xs"><i class="fa fa-download"></i>&nbsp;` +
9875          download +
9876          `</td>
9877                    <td class="hidden-xs"><i class="fa fa-upload"></i>&nbsp;` +
9878          upload +
9879          `</td>
9880                    <td class="text-right">
9881                        <div class="progress progress-lg m-b-0">
9882                            <div class="progress-bar progress-bar-info" style="width: ` +
9883          percent +
9884          `%;" role="progressbar">` +
9885          percent +
9886          `%</div>
9887                        </div>
9888                    </td>
9889                </tr>
9890                `;
9891      });
9892      break;
9893    default:
9894      return false;
9895  }
9896  if (queue !== "") {
9897    $("." + source + "-queue").html(queue);
9898  }
9899  if (history !== "") {
9900    $("." + source + "-history").html(history);
9901  }
9902  $("#count-" + source).html(count);
9903}
9904function buildDownloader(source) {
9905  var queueButton = "QUEUE";
9906  var historyButton = "HISTORY";
9907  switch (source) {
9908    case "jdownloader":
9909      var queue = true;
9910      var history = false;
9911      queueButton = "REFRESH";
9912      break;
9913    case "sabnzbd":
9914    case "nzbget":
9915      var queue = true;
9916      var history = true;
9917      break;
9918    case "transmission":
9919    case "qBittorrent":
9920    case "deluge":
9921    case "utorrent":
9922      var queue = true;
9923      break;
9924    case "rTorrent":
9925    case "sonarr":
9926    case "radarr":
9927      var queue = true;
9928      var history = false;
9929      queueButton = "REFRESH";
9930      break;
9931    default:
9932      var queue = false;
9933      var history = false;
9934  }
9935  var menu = `<ul class="nav customtab nav-tabs pull-right" role="tablist">`;
9936  var listing = "";
9937  var state = "";
9938  var active = "";
9939  var headerAlt = "";
9940  var header = "";
9941  //console.log(array);
9942  //console.log(queueItems);
9943  //console.log(historyItems);
9944  //console.log(downloader);
9945  if (queue) {
9946    menu +=
9947      `
9948			<li role="presentation" class="active" onclick="homepageDownloader('` +
9949      source +
9950      `')"><a href="#` +
9951      source +
9952      `-queue" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="true"><span class="visible-xs"><i class="ti-download"></i></span><span class="hidden-xs">` +
9953      queueButton +
9954      `</span></a></li>
9955			`;
9956    listing +=
9957      `
9958		<div role="tabpanel" class="tab-pane fade active in" id="` +
9959      source +
9960      `-queue">
9961			<div class="inbox-center table-responsive">
9962				<table class="table table-hover">
9963					<tbody class="` +
9964      source +
9965      `-queue"></tbody>
9966				</table>
9967			</div>
9968			<div class="clearfix"></div>
9969		</div>
9970		`;
9971  }
9972  if (history) {
9973    menu +=
9974      `
9975		<li role="presentation" class=""><a href="#` +
9976      source +
9977      `-history" aria-controls="profile" role="tab" data-toggle="tab" aria-expanded="false"><span class="visible-xs"><i class="ti-time"></i></span> <span class="hidden-xs">` +
9978      historyButton +
9979      `</span></a></li>
9980		`;
9981    listing +=
9982      `
9983		<div role="tabpanel" class="tab-pane fade" id="` +
9984      source +
9985      `-history">
9986			<div class="inbox-center table-responsive">
9987				<table class="table table-hover">
9988					<tbody class="` +
9989      source +
9990      `-history"></tbody>
9991				</table>
9992			</div>
9993			<div class="clearfix"></div>
9994		</div>
9995		`;
9996  }
9997  menu += "</ul>";
9998  if (activeInfo.settings.homepage.options.alternateHomepageHeaders) {
9999    var headerAlt =
10000      `
10001		<div class="col-md-12">
10002			<h2 class="text-white m-0 pull-left text-uppercase"><img class="lazyload homepageImageTitle ` +
10003      active +
10004      `" data-src="plugins/images/tabs/` +
10005      source +
10006      `.png">  &nbsp; ` +
10007      state +
10008      `</h2>
10009			` +
10010      menu +
10011      `
10012			<hr class="hidden-xs"><div class="clearfix"></div>
10013		</div>
10014		<div class="clearfix"></div>
10015		`;
10016  } else {
10017    var header =
10018      `
10019		<div class="white-box bg-info m-b-0 p-b-0 p-t-10 mailbox-widget">
10020			<h2 class="text-white m-0 pull-left text-uppercase"><img class="lazyload homepageImageTitle ` +
10021      active +
10022      `" data-src="plugins/images/tabs/` +
10023      source +
10024      `.png">  &nbsp; ` +
10025      state +
10026      `</h2>
10027			` +
10028      menu +
10029      `
10030			<div class="clearfix"></div>
10031		</div>
10032		`;
10033  }
10034  return (
10035    `
10036	<div class="row">
10037		` +
10038    headerAlt +
10039    `
10040		<div class="col-lg-12">
10041	        ` +
10042    header +
10043    `
10044	        <div class="white-box p-0">
10045	            <div class="tab-content m-t-0">` +
10046    listing +
10047    `</div>
10048	        </div>
10049		</div>
10050	</div>
10051	`
10052  );
10053}
10054function buildDownloaderCombined(source) {
10055  var first = $(".combinedDownloadRow").length == 0 ? true : false;
10056  var active = first ? "active" : "";
10057  var queueButton = "QUEUE";
10058  var historyButton = "HISTORY";
10059  switch (source) {
10060    case "jdownloader":
10061      var queue = true;
10062      var history = false;
10063      queueButton = "REFRESH";
10064      break;
10065    case "sabnzbd":
10066    case "nzbget":
10067      var queue = true;
10068      var history = true;
10069      break;
10070    case "utorrent":
10071      var queue = true;
10072      break;
10073    case "transmission":
10074    case "qBittorrent":
10075    case "deluge":
10076    case "rTorrent":
10077    case "sonarr":
10078    case "radarr":
10079      var queue = true;
10080      var history = false;
10081      queueButton = "REFRESH";
10082      break;
10083    default:
10084      var queue = false;
10085      var history = false;
10086  }
10087  var mainMenu = `<ul class="nav customtab nav-tabs combinedMenuList" role="tablist">`;
10088  var addToMainMenu =
10089    `<li role="presentation" class="` +
10090    active +
10091    `"><a onclick="homepageDownloader('` +
10092    source +
10093    `')" href="#combined-` +
10094    source +
10095    `" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="true"><span class=""><img src="./plugins/images/tabs/` +
10096    source +
10097    `.png" class="homepageImageTitle"><span class="badge bg-org downloaderCount" id="count-` +
10098    source +
10099    `"><i class="fa fa-spinner fa-spin"></i></span></span></a></li>`;
10100  var listing = "";
10101  var headerAlt = "";
10102  var header = "";
10103  var menu = `<ul class="nav customtab nav-tabs m-t-5" role="tablist">`;
10104  if (queue) {
10105    menu +=
10106      `
10107			<li role="presentation" class="active" onclick="homepageDownloader('` +
10108      source +
10109      `')"><a href="#` +
10110      source +
10111      `-queue" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="true"><span class="visible-xs"><i class="ti-download"></i></span><span class="hidden-xs">` +
10112      queueButton +
10113      `</span></a></li>
10114			`;
10115    listing +=
10116      `
10117		<div role="tabpanel" class="tab-pane fade active in" id="` +
10118      source +
10119      `-queue">
10120			<div class="inbox-center table-responsive">
10121				<table class="table table-hover">
10122					<tbody class="` +
10123      source +
10124      `-queue"></tbody>
10125				</table>
10126			</div>
10127			<div class="clearfix"></div>
10128		</div>
10129		`;
10130  }
10131  if (history) {
10132    menu +=
10133      `
10134		<li role="presentation" class=""><a href="#` +
10135      source +
10136      `-history" aria-controls="profile" role="tab" data-toggle="tab" aria-expanded="false"><span class="visible-xs"><i class="ti-time"></i></span> <span class="hidden-xs">` +
10137      historyButton +
10138      `</span></a></li>
10139		`;
10140    listing +=
10141      `
10142		<div role="tabpanel" class="tab-pane fade" id="` +
10143      source +
10144      `-history">
10145			<div class="inbox-center table-responsive">
10146				<table class="table table-hover">
10147					<tbody class="` +
10148      source +
10149      `-history"></tbody>
10150				</table>
10151			</div>
10152			<div class="clearfix"></div>
10153		</div>
10154		`;
10155  }
10156  menu +=
10157    '<li class="' +
10158    source +
10159    '-downloader-action"></li></ul><div class="clearfix"></div>';
10160  menu = queue && history ? menu : "";
10161  var listingMain =
10162    '<div role="tabpanel" class="tab-pane fade ' +
10163    active +
10164    ' in" id="combined-' +
10165    source +
10166    '">' +
10167    menu +
10168    '<div class="tab-content m-t-0 listingSingle">' +
10169    listing +
10170    "</div></div>";
10171  mainMenu += first ? addToMainMenu + "</ul>" : "";
10172  if (first) {
10173    if (activeInfo.settings.homepage.options.alternateHomepageHeaders) {
10174      var headerAlt =
10175        `
10176            <div class="col-md-12">
10177                ` +
10178        mainMenu +
10179        `
10180                <div class="clearfix"></div>
10181            </div>
10182            <div class="clearfix"></div>
10183            `;
10184    } else {
10185      var header =
10186        `
10187            <div class="white-box bg-info m-b-0 p-b-0 p-10 mailbox-widget">
10188                ` +
10189        mainMenu +
10190        `
10191                <div class="clearfix"></div>
10192            </div>
10193            `;
10194    }
10195    var built =
10196      `
10197        <div class="row combinedDownloadRow">
10198            ` +
10199      headerAlt +
10200      `
10201            <div class="col-lg-12">
10202                ` +
10203      header +
10204      `
10205                <div class="white-box p-0">
10206                    <div class="tab-content m-t-0 listingMain">` +
10207      listingMain +
10208      `</div>
10209                </div>
10210            </div>
10211        </div>
10212        `;
10213    $("#homepageOrderdownloader").html(built);
10214  } else {
10215    $(addToMainMenu).appendTo(".combinedMenuList");
10216    $(listingMain).appendTo(".listingMain");
10217  }
10218}
10219function buildMetadata(array, source) {
10220  var metadata = "";
10221  var genres = "";
10222  var actors = "";
10223  var rating = '<div class="col-xs-2 p-10"></div>';
10224  var sourceIcon = source === "jellyfin" ? "fish" : source;
10225  $.each(array.content, function (i, v) {
10226    // Normalize per-item source when coming from JellyStat or unknown
10227    var itemSource = source;
10228    try {
10229      if (
10230        source === "jellystat" ||
10231        (source !== "emby" && source !== "jellyfin")
10232      ) {
10233        if (v.tabName) {
10234          var tn = String(v.tabName).toLowerCase();
10235          if (tn.indexOf("emby") !== -1) {
10236            itemSource = "emby";
10237          } else if (tn.indexOf("jellyfin") !== -1) {
10238            itemSource = "jellyfin";
10239          }
10240        }
10241        // Fallback inference from address if tabName did not resolve
10242        if (
10243          (itemSource === source || itemSource === "jellystat") &&
10244          v.address
10245        ) {
10246          var addr = String(v.address).toLowerCase();
10247          if (addr.indexOf("jellyfin") !== -1) {
10248            itemSource = "jellyfin";
10249          } else if (addr.indexOf("emby") !== -1) {
10250            itemSource = "emby";
10251          }
10252        }
10253      }
10254    } catch (e) {}
10255    // Normalize to lowercase to avoid casing issues like 'Emby'
10256    itemSource = (itemSource || "").toString().toLowerCase();
10257    var hasActor = typeof v.metadata.actors !== "string" ? true : false;
10258    var hasGenre = typeof v.metadata.genres !== "string" ? true : false;
10259    if (hasActor) {
10260      $.each(v.metadata.actors, function (i, v) {
10261        actors +=
10262          '<div class="item lazyload recent-poster" data-src="' +
10263          v.thumb.replace("http://", "https://") +
10264          '" alt="' +
10265          v.name +
10266          '" ><span class="elip recent-title p-a-5">' +
10267          v.name +
10268          '<br><small class="font-light">' +
10269          v.role +
10270          "</small></span></div>";
10271      });
10272    }
10273    if (hasGenre) {
10274      $.each(v.metadata.genres, function (i, v) {
10275        genres += '<span class="badge bg-org m-r-10">' + v + "</span>";
10276      });
10277    }
10278    if (v.metadata.rating) {
10279      var ratingRound = Math.ceil(v.metadata.rating) * 10;
10280      rating =
10281        `<div class="col-xs-2 p-10"><div data-label="` +
10282        v.metadata.rating * 10 +
10283        `%" class="css-bar css-bar-` +
10284        Math.ceil(ratingRound / 5) * 5 +
10285        ` css-bar-sm m-b-0  css-bar-info"><img src="plugins/images/rotten.png" class="nowPlayingUserThumb" alt="User"></div></div>`;
10286    }
10287    var seconds = v.metadata.duration / 1000; // or "2000"
10288    seconds = parseInt(seconds); //because moment js dont know to handle number in string format
10289    var format =
10290      Math.floor(moment.duration(seconds, "seconds").asHours()) +
10291      ":" +
10292      moment.duration(seconds, "seconds").minutes() +
10293      ":" +
10294      moment.duration(seconds, "seconds").seconds();
10295    // Build icon HTML: use image for Emby to avoid missing MDI glyphs; keep MDI for others
10296    var sourceIconHtml = "";
10297    var iconChoice = itemSource === "jellyfin" ? "fish" : itemSource;
10298    if (itemSource === "emby") {
10299      sourceIconHtml =
10300        '<img src="plugins/images/tabs/emby.png" class="metadata-source-image" style="height:24px;width:24px;" />';
10301    } else {
10302      sourceIconHtml = '<i class="fa mdi mdi-' + iconChoice + ' fa-2x"></i>';
10303    }
10304    metadata =
10305      `
10306		<div class="white-box m-b-0">
10307			<div class="user-bg lazyload" data-src="` +
10308      v.nowPlayingImageURL +
10309      `">
10310				` +
10311      rating +
10312      `
10313				<div class="col-xs-10">
10314	                <h2 class="m-b-0 font-medium pull-right text-right">
10315						` +
10316      v.title +
10317      `<button type="button" class="btn bg-org btn-circle close-popup m-l-10"><i class="fa fa-times"></i> </button><br>
10318						<small class="m-t-0 text-white">` +
10319      v.metadata.tagline +
10320      `</small><br>
10321						<button class="btn waves-effect waves-light openTab bg-` +
10322      itemSource +
10323      `" type="button" data-tab-name="` +
10324      cleanClass(v.tabName) +
10325      `" data-type="` +
10326      v.type +
10327      `" data-open-tab="` +
10328      v.openTab +
10329      `" data-url="` +
10330      v.address +
10331      `" href="javascript:void(0);"> ` +
10332      sourceIconHtml +
10333      ` </button>
10334						` +
10335      buildYoutubeLink(v.title + " " + v.metadata.year + " " + v.type) +
10336      `
10337					</h2>
10338	            </div>
10339				<div class="genre-list p-10">` +
10340      genres +
10341      `</div>
10342			</div>
10343		</div>
10344		<div class="panel panel-info p-b-0 p-t-0">
10345            <div class="panel-body p-b-0 p-t-0 m-b-0">
10346				<div class="p-20 text-center">
10347					<p class="">` +
10348      v.metadata.summary +
10349      `</p>
10350				</div>
10351				<div class="row">
10352					<div class="col-lg-12">
10353						<div class="owl-carousel owl-theme metadata-actors p-b-10">` +
10354      actors +
10355      `</div>
10356					</div>
10357				</div>
10358            </div>
10359        </div>
10360
10361		`;
10362  });
10363  return metadata;
10364}
10365function buildYoutubeLink(title) {
10366  if (title) {
10367    var str = createRandomString(10);
10368    return (
10369      `
10370		<button class="btn btn-youtube waves-effect waves-light" type="button" onclick="youtubeCheck('` +
10371      escape(title) +
10372      `','` +
10373      str +
10374      `')"> <i class="fa fa-youtube-play fa-2x"></i> </button>
10375		<a class="hidden inline-popups ` +
10376      str +
10377      `" href="#open-youtube" data-effect="mfp-zoom-out"></a>
10378		`
10379    );
10380  }
10381}
10382function buildPVRLink(href, ico = "", frame = "", showLink = true) {
10383  if (href && showLink) {
10384    var styleOverride = `width:55px;height:44px;background-image: url(${ico});background-repeat:no-repeat;background-size:25px;background-position:center;`;
10385    if (frame) {
10386      return `
10387			<div class="btn btn-inverse waves-effect waves-light" type="button" onclick="tabActions(event,'${frame}','${href}');" style="${styleOverride}"></div>
10388			`;
10389    } else {
10390      return `
10391			<div class="btn btn-inverse waves-effect waves-light" type="button" onclick="window.open('${href}')" style="${styleOverride}"></div>
10392			`;
10393    }
10394  } else {
10395    return `
10396		 
10397		`;
10398  }
10399}
10400function buildCalendarMetadata(array) {
10401  var metadata = "";
10402  var genres = "";
10403  var actors = "";
10404  var rating = '<div class="col-xs-2 p-10"></div>';
10405  var hasGenre = typeof array.genres !== "string" ? true : false;
10406  if (hasGenre) {
10407    $.each(array.genres, function (i, v) {
10408      genres += '<span class="badge bg-org m-r-10">' + v + "</span>";
10409    });
10410  }
10411  if (array.ratings) {
10412    var ratingRound = Math.ceil(array.ratings) * 10;
10413    rating =
10414      `<div class="col-xs-2 p-10"><div data-label="` +
10415      array.ratings * 10 +
10416      `%" class="css-bar css-bar-` +
10417      Math.ceil(ratingRound / 5) * 5 +
10418      ` css-bar-sm m-b-0  css-bar-info"><img src="plugins/images/rotten.png" class="nowPlayingUserThumb" alt="User"></div></div>`;
10419  }
10420  var seconds = array.runtime / 1000; // or "2000"
10421  seconds = parseInt(seconds); //because moment js dont know to handle number in string format
10422  var format =
10423    Math.floor(moment.duration(seconds, "seconds").asHours()) +
10424    ":" +
10425    moment.duration(seconds, "seconds").minutes() +
10426    ":" +
10427    moment.duration(seconds, "seconds").seconds();
10428  metadata =
10429    `
10430		<div class="white-box m-b-0">
10431			<div class="user-bg lazyload" data-src="` +
10432    array.image +
10433    `">
10434				` +
10435    rating +
10436    `
10437				<div class="col-xs-10">
10438	                <h2 class="m-b-0 font-medium pull-right text-right">
10439						` +
10440    array.topTitle +
10441    `<button type="button" class="btn bg-org btn-circle close-popup m-l-10"><i class="fa fa-times"></i> </button><br>
10442						<small class="m-t-0 text-white">` +
10443    array.bottomTitle +
10444    `</small><br>
10445						` +
10446    buildPVRLink(array.href, array.icon, array.frame, array.showLink) +
10447    buildYoutubeLink(array.topTitle) +
10448    `
10449					</h2>
10450	            </div>
10451				<div class="genre-list p-10">` +
10452    genres +
10453    `</div>
10454			</div>
10455		</div>
10456		<div class="panel panel-info p-b-0 p-t-0">
10457            <div class="panel-body p-b-0 p-t-0 m-b-0">
10458				<div class="p-20 text-center">
10459					<p class="">` +
10460    array.overview +
10461    `</p>
10462				</div>
10463            </div>
10464        </div>
10465
10466		`;
10467  return metadata;
10468}
10469function buildHealthChecks(array) {
10470  if (array === false) {
10471    return "";
10472  }
10473  var checks =
10474    typeof array.content.checks !== "undefined"
10475      ? array.content.checks.length
10476      : false;
10477  return checks
10478    ? `
10479	<div id="allHealthChecks" class="m-b-30">
10480		<div class="el-element-overlay row">
10481		    <div class="col-md-12">
10482		        <h4 class="pull-left homepage-element-title"><span lang="en">Health Checks</span> : </h4><h4 class="pull-left">&nbsp;<span class="label label-info m-l-20 checkbox-circle good-health-checks mouse" onclick="homepageHealthChecks()">` +
10483        checks +
10484        `</span></h4>
10485		        <hr class="hidden-xs">
10486		    </div>
10487			<div class="clearfix"></div>
10488		    <!-- .cards -->
10489		    <div class="healthCheckCards">
10490			    ` +
10491        buildHealthChecksItem(array) +
10492        `
10493			</div>
10494		    <!-- /.cards-->
10495		</div>
10496	</div>
10497	<div class="clearfix"></div>
10498	`
10499    : "";
10500}
10501function buildPihole(array) {
10502  if (array === false) {
10503    return "";
10504  }
10505  var html = `
10506    <div id="allPihole">
10507        <div class="el-element-overlay row">`;
10508  if (array["options"]["title"]) {
10509    html += `
10510            <div class="col-md-12">
10511                <h4 class="pull-left homepage-element-title"><span lang="en">
10511Pi-hole</span> : </h4><h4 class="pull-left">&nbsp;</h4>
10512                <hr class="hidden-xs ml-2">
10513            </div>
10514            <div class="clearfix"></div>
10515        `;
10516  }
10517  html +=
10518    `
10519		    <div class="piholeCards col-sm-12 my-3">
10520			    ` +
10521    buildPiholeItem(array) +
10522    `
10523			</div>
10524		</div>
10525	</div>
10526    `;
10527  return array ? html : "";
10528}
10529function buildAdGuard(array) {
10530  if (array === false) {
10531    return "";
10532  }
10533  var html = `
10534    <div id="allAdGuard">
10535        <div class="el-element-overlay row">`;
10536  if (array["options"]["title"]) {
10537    html += `
10538            <div class="col-md-12">
10539                <h4 class="pull-left homepage-element-title"><span lang="en">AdGuard Home</span> : </h4><h4 class="pull-left">&nbsp;</h4>
10540                <hr class="hidden-xs ml-2">
10541            </div>
10542            <div class="clearfix"></div>
10543        `;
10544  }
10545  html +=
10546    `
10547		    <div class="adguardCards col-sm-12 my-3">
10548			    ` +
10549    buildAdGuardItem(array) +
10550    `
10551			</div>
10552		</div>
10553	</div>
10554    `;
10555  return array ? html : "";
10556}
10557function buildUnifi(array) {
10558  if (array === false) {
10559    return "";
10560  }
10561  var items =
10562    typeof array.content.unifi.data !== "undefined"
10563      ? array.content.unifi.data.length
10564      : false;
10565  return items
10566    ? `
10567	<div id="allUnifi">
10568	    
10569	    <!-- <div class="row">
10570            <div class="col-md-12">
10571                <div class="white-box">
10572                    <h3 class="box-title">Unifi</h3>
10573                    ` +
10574        buildUnifiItemNew(array.content.unifi.data) +
10575        `
10576                </div>
10577            </div>
10578        </div>  -->      
10579         
10580                
10581		<div class="row">
10582		    <div class="col-md-12">
10583		        <h4 class="pull-left homepage-element-title"><span lang="en">UniFi</span> : </h4><h4 class="pull-left">&nbsp;</h4>
10584		        <hr class="hidden-xs">
10585		    </div>
10586			<div class="clearfix"></div>
10587		    <!-- .cards -->
10588		    <div class="unifiCards">
10589		        ` +
10590        buildUnifiItem(array.content.unifi.data) +
10591        `
10592			</div>
10593		    <!-- /.cards-->
10594		</div>
10595	</div>
10596	<div class="clearfix"></div>
10597	`
10598    : "";
10599}
10600function buildUnifiItemNew(array) {
10601  let items = "";
10602  let count = 0;
10603  let navTabItems = "";
10604  $.each(array, function (i, v) {
10605    let name = typeof v.subsystem !== "undefined" ? v.subsystem : "";
10606    let stats = {};
10607    let panelColor = "";
10608    let proceed = v.status == "ok";
10609    let active = count <= 0 ? "active in" : "";
10610    let icon = "ti-home";
10611    count++;
10612    switch (name) {
10613      case "wlan":
10614        panelColor = "info";
10615        stats["clients"] = v.num_user;
10616        stats["tx"] = v["tx_bytes-r"];
10617        stats["rx"] = v["rx_bytes-r"];
10618        break;
10619      case "wan":
10620        panelColor = "success";
10621        stats["IP"] = v.wan_ip;
10622        stats["tx"] = v["tx_bytes-r"];
10623        stats["rx"] = v["rx_bytes-r"];
10624        break;
10625      case "lan":
10626        panelColor = "primary";
10627        stats["clients"] = v.num_user;
10628        stats["tx"] = v["tx_bytes-r"];
10629        stats["rx"] = v["rx_bytes-r"];
10630        break;
10631      case "www":
10632        panelColor = "warning";
10633        stats["drops"] = v.drops;
10634        stats["latency"] = v.latency;
10635        stats["uptime"] = v.uptime;
10636        stats["tx"] = v["tx_bytes-r"];
10637        stats["rx"] = v["rx_bytes-r"];
10638        break;
10639      case "vpn":
10640        panelColor = "inverse";
10641        stats["clients"] = v.remote_user_num_active;
10642        stats["tx"] = v.remote_user_tx_bytes;
10643        stats["rx"] = v.remote_user_rx_bytes;
10644        break;
10645      default:
10646    }
10647    var statItems = "";
10648    if (proceed) {
10649      navTabItems += `<li role="presentation" class="${active}"><a href="#unifi-${name}" aria-controls="unifi-${name}" role="tab" data-toggle="tab" aria-expanded="false"><span class="visible-xs"><i class="${icon}"></i></span><span class="hidden-xs text-uppercase"> ${name}</span></a></li>`;
10650      $.each(stats, function (istat, vstat) {
10651        statItems += `
10652                    <div class="stat-item">
10653                        <h6 class="text-uppercase">${istat}</h6>
10654                        <b>${vstat}</b>
10655                    </div>
10656                    `;
10657      });
10658      items += `
10659                <div role="tabpanel" class="tab-pane fade ${active}" id="unifi-${name}">
10660                    <div class="col-md-12">${statItems}</div>
10661                    <div class="clearfix"></div>
10662                </div>
10663            `;
10664    }
10665  });
10666  let navTabs = `<ul class="nav customtab nav-tabs" role="tablist">${navTabItems}</ul>`;
10667  items = `<div class="tab-content">${items}</div>`;
10668  return navTabs + items;
10669}
10670function buildUnifiItem(array) {
10671  var items = "";
10672  $.each(array, function (i, v) {
10673    var name = typeof v.subsystem !== "undefined" ? v.subsystem : "";
10674    var stats = {};
10675    var panelColor = "";
10676    var proceed = v.status == "ok";
10677    switch (name) {
10678      case "wlan":
10679        panelColor = "info";
10680        stats["clients"] = v.num_user;
10681        stats["tx"] = v["tx_bytes-r"];
10682        stats["rx"] = v["rx_bytes-r"];
10683        break;
10684      case "wan":
10685        panelColor = "success";
10686        stats["IP"] = v.wan_ip;
10687        stats["tx"] = v["tx_bytes-r"];
10688        stats["rx"] = v["rx_bytes-r"];
10689        break;
10690      case "lan":
10691        panelColor = "primary";
10692        stats["clients"] = v.num_user;
10693        stats["tx"] = v["tx_bytes-r"];
10694        stats["rx"] = v["rx_bytes-r"];
10695        break;
10696      case "www":
10697        panelColor = "warning";
10698        stats["drops"] = v.drops;
10699        stats["latency"] = v.latency;
10700        stats["uptime"] = v.uptime;
10701        stats["tx"] = v["tx_bytes-r"];
10702        stats["rx"] = v["rx_bytes-r"];
10703        break;
10704      case "vpn":
10705        panelColor = "inverse";
10706        stats["clients"] = v.remote_user_num_active;
10707        stats["tx"] = v.remote_user_tx_bytes;
10708        stats["rx"] = v.remote_user_rx_bytes;
10709        break;
10710      default:
10711    }
10712    var statItems = "";
10713    if (proceed) {
10714      $.each(stats, function (istat, vstat) {
10715        statItems += `
10716                    <div class="stat-item">
10717                        <h6 class="text-uppercase">${istat}</h6>
10718                        <b>${vstat}</b>
10719                    </div>
10720                    `;
10721      });
10722      items += `
10723                <div class="col-lg-4 col-md-6 col-center">
10724                    <div class="panel panel-${panelColor}">
10725                        <div class="panel-heading"> <span class="text-uppercase">${name}</span>
10726                            <div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-minus"></i></a></div>
10727                        </div>
10728                        <div class="panel-wrapper collapse in" aria-expanded="true">
10729                            <div class="panel-body">
10730                               ${statItems}
10731                            </div>
10732                        </div>
10733                    </div>
10734                </div>
10735            `;
10736    }
10737  });
10738  return items;
10739}
10740function healthCheckIcon(tags) {
10741  var allTags = tags.split(" ");
10742  var useIcon = "";
10743  $.each(allTags, function (i, v) {
10744    //check for image
10745    var file =
10746      v.substring(v.lastIndexOf(".") + 1, v.length).toLowerCase() ||
10747      v.toLowerCase();
10748    switch (file) {
10749      case "png":
10750      case "jpg":
10751      case "jpeg":
10752      case "gif":
10753        useIcon =
10754          '<img class="lazyload loginTitle" data-src="' + v + '">&nbsp;';
10755        break;
10756      default:
10757    }
10758  });
10759  return useIcon;
10760}
10761function buildHealthChecksItem(array) {
10762  var checks = "";
10763  $.each(array.content.checks, function (i, v) {
10764    var hasIcon = healthCheckIcon(v.tags);
10765    v.name = v.name ? v.name : "New Item";
10766    v.desc =
10767      array.options.desc && v.desc ? "<h5>Notes: " + v.desc + "</h5>" : "";
10768    switch (v.status) {
10769      case "up":
10770        var statusColor = "success";
10771        var statusIcon = "ti-link text-success";
10772        var nextPing = moment
10773          .utc(v.next_ping, "YYYY-MM-DD hh:mm[Z]")
10774          .local()
10775          .fromNow();
10776        var lastPing = moment
10777          .utc(v.last_ping, "YYYY-MM-DD hh:mm[Z]")
10778          .local()
10779          .fromNow();
10780        break;
10781      case "down":
10782        var statusColor = "danger animated-3 loop-animation flash";
10783        var statusIcon = "ti-unlink text-danger";
10784        var nextPing = "Service Down";
10785        var lastPing = moment
10786          .utc(v.last_ping, "YYYY-MM-DD hh:mm[Z]")
10787          .local()
10788          .fromNow();
10789        break;
10790      case "new":
10791        var statusColor = "info";
10792        var statusIcon = "ti-time text-info";
10793        var nextPing = "Waiting...";
10794        var lastPing = "n/a";
10795        break;
10796      case "grace":
10797        var statusColor = "warning";
10798        var statusIcon = "ti-alert text-warning";
10799        var nextPing = moment
10800          .utc(v.next_ping, "YYYY-MM-DD hh:mm[Z]")
10801          .local()
10802          .fromNow();
10803        var lastPing = "Missed";
10804        break;
10805      case "paused":
10806        var statusColor = "primary";
10807        var statusIcon = "ti-control-pause text-primary";
10808        var nextPing = "Paused";
10809        var lastPing = moment
10810          .utc(v.last_ping, "YYYY-MM-DD hh:mm[Z]")
10811          .local()
10812          .fromNow();
10813        break;
10814      default:
10815        var statusColor = "warning";
10816        var statusIcon = "ti-timer text-warning";
10817        var nextPing = "Waiting...";
10818        var lastPing = "n/a";
10819    }
10820    var tagPrimaryElem = "",
10821      tagSecondaryElem = "";
10822    if (array.options.tags && v.tags) {
10823      v.tags = v.tags.split(" ");
10824      $.each(v.tags, function (key, value) {
10825        if (isURL(value)) {
10826          v.tags = arrayRemove(v.tags, value);
10827        }
10828      });
10829      tagPrimaryElem =
10830        '<span class="pull-right mt-3 mr-2"><span class="label text-uppercase bg-' +
10831        statusColor.replace("animated-3 loop-animation flash", "") +
10832        ' label-rounded font-12">' +
10833        v.tags[0] +
10834        "</span></span>";
10835      tagSecondaryElem = "<h5>Tags: ";
10836      tagSecondaryElem += v.tags
10837        .map((t) => {
10838          return t;
10839        })
10840        .join(", ");
10841      tagSecondaryElem += "</h5>";
10842    }
10843    checks +=
10844      `
10845            <div class="col-xl-2 col-lg-3 col-md-4 col-sm-6 col-xs-12">
10846                <div class="card bg-inverse text-white mb-3 showMoreHealth mouse" data-id="` +
10847      i +
10848      `">
10849                    <div class="card-body bg-org-alt pt-1 pb-1">
10850                        <div class="d-flex no-block align-items-center">
10851                            <div class="left-health bg-` +
10852      statusColor +
10853      `"></div>
10854                            <div class="ml-1 w-100">
10855                                <span class="pull-right mt-3 mb-2"><i class="` +
10856      statusIcon +
10857      ` font-20"></i></span>
10858				` +
10859      tagPrimaryElem +
10860      `
10861                                <h3 class="d-flex no-block align-items-center mt-2 mb-2">` +
10862      hasIcon +
10863      v.name +
10864      `</h3>
10865                                <div class="clearfix"></div>
10866                                <div class="d-none showMoreHealthDiv-` +
10867      i +
10868      `"><h5>Last: ` +
10869      lastPing +
10870      `</h5><h5>Next: ` +
10871      nextPing +
10872      `</h5>` +
10873      v.desc +
10874      tagSecondaryElem +
10875      `</div>
10876                                <div class="clearfix"></div>
10877                            </div>
10878                        </div>
10879                    </div>
10880                </div>
10881            </div>
10882        `;
10883  });
10884  return checks;
10885}
10886function isURL(str) {
10887  const pattern = new RegExp(
10888    "^(https?:\\/\\/)?" + // protocol
10889      "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
10890      "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
10891      "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
10892      "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
10893      "(\\#[-a-z\\d_]*)?$",
10894    "i"
10895  ); // fragment locator
10896  return !!pattern.test(str);
10897}
10898function arrayRemove(arr, value) {
10899  return arr.filter(function (ele) {
10900    return ele != value;
10901  });
10902}
10903function buildAdGuardItem(array) {
10904  var stats = `
10905    <style>
10906    .bg-green {
10907        background-color: #00a65a !important;
10908    }
10909    
10910    .bg-aqua {
10911        background-color: #00c0ef!important;
10912    }
10913    
10914    .bg-yellow {
10915        background-color: #f39c12!important;
10916    }
10917    
10918    .bg-red {
10919        background-color: #dd4b39!important;
10920    }
10921    
10922    .adguard-stat {
10923        color: #fff !important;
10924    }
10925    
10926    .adguard-stat .card-body h3 {
10927        font-size: 38px;
10928        font-weight: 700;
10929    }
10930
10931    .adguard-stat .card-body i {
10932        font-size: 5em;
10933        float: right;
10934        color: #ffffff6b;
10935    }
10936
10937    .inline-block {
10938        display: inline-block;
10939    }
10940    </style>
10941    `;
10942  var length = Object.keys(array["data"]).length;
10943  var combine = array["options"]["combine"];
10944  var totalQueries = function (data) {
10945    var card = `
10946        <div class="col-lg-3 col-md-6 col-sm-6 col-xs-12">
10947            <div class="card text-white mb-3 adguard-stat bg-green">
10948                <div class="card-body">
10949                    <div class="inline-block">
10950                        <p class="d-inline mr-1">Total Queries</p>`;
10951    for (var key in data) {
10952      var e = data[key];
10953      if (length > 1 && !combine) {
10954        card += `<p class="d-inline text-muted">(` + key + `)</p>`;
10955      }
10956      card +=
10957        `<h3 data-toggle="tooltip" data-placement="right" title="` +
10958        key +
10959        `">` +
10960        e["num_dns_queries"].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") +
10961        `</h3>`;
10962    }
10963    card += `
10964                    </div>
10965                    <i class="fa fa-globe inline-block" aria-hidden="true"></i>
10966                </div>
10967            </div>
10968        </div>
10969        `;
10970    return card;
10971  };
10972  var totalBlocked = function (data) {
10973    var card = `
10974        <div class="col-lg-3 col-md-6 col-sm-6 col-xs-12">
10975            <div class="card bg-inverse text-white mb-3 adguard-stat bg-aqua">
10976                <div class="card-body">
10977                    <div class="inline-block">
10978                        <p class="d-inline mr-1">Queries Blocked</p>`;
10979    for (var key in data) {
10980      var e = data[key];
10981      if (length > 1 && !combine) {
10982        card += `<p class="d-inline text-muted">(${key})</p>`;
10983      }
10984      card += `<h3 data-toggle="tooltip" data-placement="right" title="${key}">${e[
10985        "num_blocked_filtering"
10986      ]
10987        .toString()
10988        .replace(/\B(?=(\d{3})+(?!\d))/g, ",")}</h3>`;
10989    }
10990    card += `
10991                    </div>
10992                    <i class="fa fa-hand-paper-o inline-block" aria-hidden="true"></i>
10993                </div>
10994            </div>
10995        </div>
10996        `;
10997    return card;
10998  };
10999  var avgProcessingTime = function (data) {
11000    var card = `
11001        <div class="col-lg-3 col-md-6 col-sm-6 col-xs-12">
11002            <div class="card bg-inverse text-white mb-3 adguard-stat bg-purple">
11003                <div class="card-body">
11004                    <div class="inline-block">
11005                        <p class="d-inline mr-1">Avg Processing Time</p>`;
11006    for (var key in data) {
11007      var e = data[key];
11008      if (length > 1 && !combine) {
11009        card += `<p class="d-inline text-muted">(${key})</p>`;
11010      }
11011      ms_time = parseFloat(e["avg_processing_time"]) * 1000;
11012      card += `<h3 data-toggle="tooltip" data-placement="right" title="${key}">${ms_time.toFixed(
11013        2
11014      )} ms</h3>`;
11015    }
11016    card += `
11017                    </div>
11018                    <i class="fa fa-group inline-block" aria-hidden="true"></i>
11019                </div>
11020            </div>
11021        </div>
11022        `;
11023    return card;
11024  };
11025  var domainsBlocked = function (data) {
11026    var card = `
11027        <div class="col-lg-3 col-md-6 col-sm-6 col-xs-12">
11028            <div class="card bg-inverse text-white mb-3 adguard-stat bg-red">
11029                <div class="card-body">
11030                    <div class="inline-block">
11031                        <p class="d-inline mr-1">Domains on Blocklist</p>`;
11032    for (var key in data) {
11033      var e = data[key];
11034      if (length > 1 && !combine) {
11035        card += `<p class="d-inline text-muted">(${key})</p>`;
11036      }
11037      var total_domains_blocked = 0;
11038      for (var key in e["filters"]) {
11039        total_domains_blocked += parseFloat(e["filters"][key]["rules_count"]);
11040      }
11041      card += `<h3 data-toggle="tooltip" data-placement="right" title="${key}">${total_domains_blocked
11042        .toString()
11043        .replace(/\B(?=(\d{3})+(?!\d))/g, ",")}</h3>`;
11044    }
11045    card += `
11046                    </div>
11047                    <i class="fa fa-list inline-block" aria-hidden="true"></i>
11048                </div>
11049            </div>
11050        </div>
11051        `;
11052    return card;
11053  };
11054  var domainsBlocked = function (data) {
11055    var card = `
11056        <div class="col-lg-3 col-md-6 col-sm-6 col-xs-12">
11057            <div class="card bg-inverse text-white mb-3 adguard-stat bg-red">
11058                <div class="card-body">
11059                    <div class="inline-block">
11060                        <p class="d-inline mr-1">Domains on Blocklist</p>`;
11061    for (var key in data) {
11062      var e = data[key];
11063      if (length > 1 && !combine) {
11064        card += `<p class="d-inline text-muted">(${key})</p>`;
11065      }
11066      var total_domains_blocked = 0;
11067      for (var key in e["filters"]) {
11068        total_domains_blocked += parseFloat(e["filters"][key]["rules_count"]);
11069      }
11070      total_domains_blocked += Object.keys(e["user_rules"]).length;
11071      card += `<h3 data-toggle="tooltip" data-placement="right" title="${key}">${total_domains_blocked
11072        .toString()
11073        .replace(/\B(?=(\d{3})+(?!\d))/g, ",")}</h3>`;
11074    }
11075    card += `
11076                    </div>
11077                    <i class="fa fa-list inline-block" aria-hidden="true"></i>
11078                </div>
11079            </div>
11080        </div>
11081        `;
11082    return card;
11083  };
11084  var percentBlocked = function (data) {
11085    var card = `
11086        <div class="col-lg-3 col-md-6 col-sm-6 col-xs-12">
11087            <div class="card bg-inverse text-white mb-3 adguard-stat bg-yellow">
11088                <div class="card-body">
11089                    <div class="inline-block">
11090                        <p class="d-inline mr-1">Percent Blocked</p>`;
11091    for (var key in data) {
11092      var e = data[key];
11093      if (typeof e["FTLnotrunning"] == "undefined") {
11094        if (length > 1 && !combine) {
11095          card += `<p class="d-inline text-muted">(${key})</p>`;
11096        }
11097        var percent =
11098          100 *
11099          (parseFloat(e["num_blocked_filtering"]) /
11100            parseFloat(e["num_dns_queries"]));
11101        card += `<h3 data-toggle="tooltip" data-placement="right" title="${key}">${percent.toFixed(
11102          2
11103        )}%</h3>`;
11104      }
11105    }
11106    card += `
11107                    </div>
11108                    <i class="fa fa-pie-chart inline-block" aria-hidden="true"></i>
11109                </div>
11110            </div>
11111        </div>
11112        `;
11113    return card;
11114  };
11115  if (combine) {
11116    stats += '<div class="row">';
11117    if (array["options"]["queries"]) {
11118      stats += totalQueries(array["data"]);
11119    }
11120    if (array["options"]["blocked_count"]) {
11121      stats += totalBlocked(array["data"]);
11122    }
11123    if (array["options"]["blocked_percent"]) {
11124      stats += percentBlocked(array["data"]);
11125    }
11126    if (array["options"]["processing_time"]) {
11127      stats += avgProcessingTime(array["data"]);
11128    }
11129    if (array["options"]["domain_count"]) {
11130      stats += domainsBlocked(array["filters"]);
11131    }
11132    stats += "</div>";
11133  } else {
11134    for (var key in array["data"]) {
11135      var data = array["data"][key];
11136      obj = {};
11137      obj[key] = data;
11138      stats += '<div class="row">';
11139      if (array["options"]["queries"]) {
11140        stats += totalQueries(array["data"]);
11141      }
11142      if (array["options"]["blocked_count"]) {
11143        stats += totalBlocked(array["data"]);
11144      }
11145      if (array["options"]["blocked_percent"]) {
11146        stats += percentBlocked(array["data"]);
11147      }
11148      if (array["options"]["processing_time"]) {
11149        stats += avgProcessingTime(array["data"]);
11150      }
11151      if (array["options"]["domain_count"]) {
11152        stats += domainsBlocked(array["filters"]);
11153      }
11154      stats += "</div>";
11155    }
11156  }
11157  return stats;
11158}
11159
11160function buildPiholeItem(array) {
11161  var stats = `
11162    <style>
11163    .bg-green {
11164        background-color: #00a65a !important;
11165    }
11166    
11167    .bg-aqua {
11168        background-color: #00c0ef!important;
11169    }
11170    
11171    .bg-yellow {
11172        background-color: #f39c12!important;
11173    }
11174    
11175    .bg-red {
11176        background-color: #dd4b39!important;
11177    }
11178    
11179    .pihole-stat {
11180        color: #fff !important;
11181    }
11182    
11183    .pihole-stat .card-body h3 {
11184        font-size: 38px;
11185        font-weight: 700;
11186    }
11187
11188    .pihole-stat .card-body i {
11189        font-size: 5em;
11190        float: right;
11191        color: #ffffff6b;
11192    }
11193
11194    .inline-block {
11195        display: inline-block;
11196    }
11197
11198    ul.multi-column {
11199        column-count: 1;
11200        column-gap: 2em;
11201    }
11202
11203    @media (min-width: 650px) {
11204        ul.multi-column {
11205            column-count: 3;
11206        }
11207    }
11208
11209    @media (min-width: 1200px) {
11210        ul.multi-column {
11211            column-count: 5;
11212        }
11213    }
11214    </style>
11215    `;
11216  var length = Object.keys(array["data"]).length;
11217  var combine = array["options"]["combine"];
11218  var totalQueries = function (data) {
11219    var card = `
11220        <div class="col-lg-4 col-md-6 col-sm-6 col-xs-12">
11221            <div class="card text-white mb-3 pihole-stat bg-green">
11222                <div class="card-body">
11223                    <div class="inline-block">
11224                        <p class="d-inline mr-1">Total queries (last 24 hours)</p>`;
11225    for (var key in data) {
11226      var e = data[key];
11227      if (typeof e["FTLnotrunning"] == "undefined") {
11228        if (length > 1 && !combine) {
11229          card += `<p class="d-inline text-muted">(` + key + `)</p>`;
11230        }
11231        let value = "Error";
11232        if (e.length == undefined) {
11233          value = e["sum_queries"]
11234            .toString()
11235            .replace(/\B(?=(\d{3})+(?!\d))/g, ",");
11236        }
11237        card +=
11238          `<h3 data-toggle="tooltip" data-placement="right" title="` +
11239          key +
11240          `">` +
11241          value +
11242          `</h3>`;
11243      }
11244    }
11245    card += `
11246                    </div>
11247                    <i class="fa fa-globe inline-block" aria-hidden="true"></i>
11248                </div>
11249            </div>
11250        </div>
11251        `;
11252    return card;
11253  };
11254  var totalBlocked = function (data) {
11255    var card = `
11256        <div class="col-lg-4 col-md-6 col-sm-6 col-xs-12">
11257            <div class="card bg-inverse text-white mb-3 pihole-stat bg-aqua">
11258                <div class="card-body">
11259                    <div class="inline-block">
11260                        <p class="d-inline mr-1">Queries Blocked (last 24 hours)</p>`;
11261    for (var key in data) {
11262      var e = data[key];
11263      if (typeof e["FTLnotrunning"] == "undefined") {
11264        if (length > 1 && !combine) {
11265          card += `<p class="d-inline text-muted">(${key})</p>`;
11266        }
11267        let value = "Error";
11268        if (e.length == undefined) {
11269          value = e["sum_blocked"]
11270            .toString()
11271            .replace(/\B(?=(\d{3})+(?!\d))/g, ",");
11272        }
11273        card +=
11274          `<h3 data-toggle="tooltip" data-placement="right" title="` +
11275          key +
11276          `">` +
11277          value +
11278          `</h3>`;
11279      }
11280    }
11281    card += `
11282                    </div>
11283                    <i class="fa fa-hand-paper-o inline-block" aria-hidden="true"></i>
11284                </div>
11285            </div>
11286        </div>
11287        `;
11288    return card;
11289  };
11290  var percentBlocked = function (data) {
11291    var card = `
11292        <div class="col-lg-4 col-md-6 col-sm-6 col-xs-12">
11293            <div class="card bg-inverse text-white mb-3 pihole-stat bg-yellow">
11294                <div class="card-body">
11295                    <div class="inline-block">
11296                        <p class="d-inline mr-1">Percent Blocked (last 24 hours)</p>`;
11297    for (var key in data) {
11298      var e = data[key];
11299      if (typeof e["FTLnotrunning"] == "undefined") {
11300        if (length > 1 && !combine) {
11301          card += `<p class="d-inline text-muted">(${key})</p>`;
11302        }
11303        let value = "Error";
11304        if (e.length == undefined) {
11305          value = e["percent_blocked"].toFixed(1);
11306        }
11307        card +=
11308          `<h3 data-toggle="tooltip" data-placement="right" title="` +
11309          key +
11310          `">` +
11311          value +
11312          `</h3>`;
11313      }
11314    }
11315    card += `
11316                    </div>
11317                    <i class="fa fa-pie-chart inline-block" aria-hidden="true"></i>
11318                </div>
11319            </div>
11320        </div>
11321        `;
11322    return card;
11323  };
11324  var domainsBlocked = function (data) {
11325    var card = `
11326        <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
11327            <div class="card bg-inverse text-white mb-3 pihole-stat bg-red">
11328                <div class="card-body">
11329                    <div class="inline-block">
11330                        <p class="d-inline mr-1">Domains on Blocklist (last 24 hours)</p>`;
11331    for (var key in data) {
11332      var e = data[key];
11333      if (typeof e["FTLnotrunning"] == "undefined") {
11334        if (length > 1 && !combine) {
11335          card += `<p class="d-inline text-muted">(${key})</p>`;
11336        }
11337        let value = "Error";
11338        value = e["domains_being_blocked"]
11339          .map(function (x) {
11340            return `<li>${x.toString()}</li>`;
11341          })
11342          .join("");
11343        card +=
11344          `<ul class="multi-column" data-toggle="tooltip" title="` +
11345          key +
11346          `">` +
11347          value +
11348          `</ul>`;
11349      }
11350    }
11351    card += `
11352                    </div>
11353                    <i class="fa fa-list inline-block" aria-hidden="true"></i>
11354                </div>
11355            </div>
11356        </div>
11357        `;
11358    return card;
11359  };
11360
11361  if (combine) {
11362    stats += '<div class="row">';
11363    stats += totalQueries(array["data"]);
11364    stats += totalBlocked(array["data"]);
11365    stats += percentBlocked(array["data"]);
11366    stats += domainsBlocked(array["data"]);
11367    stats += "</div>";
11368  } else {
11369    for (var key in array["data"]) {
11370      var data = array["data"][key];
11371      obj = {};
11372      obj[key] = data;
11373      stats += '<div class="row">';
11374      stats += totalQueries(obj);
11375      stats += totalBlocked(obj);
11376      stats += percentBlocked(obj);
11377      stats += domainsBlocked(obj);
11378      stats += "</div>";
11379    }
11380  }
11381  return stats;
11382}
11383function homepagePihole(timeout) {
11384  var timeout =
11385    typeof timeout !== "undefined"
11386      ? timeout
11387      : activeInfo.settings.homepage.refresh.homepagePiholeRefresh;
11388  organizrAPI2("GET", "api/v2/homepage/pihole/stats")
11389    .success(function (data) {
11390      try {
11391        let response = data.response;
11392        document.getElementById("homepageOrderPihole").innerHTML = "";
11393        if (response.data !== null) {
11394          buildPihole(response.data);
11395          $("#homepageOrderPihole").html(buildPihole(response.data));
11396        }
11397      } catch (e) {
11398        organizrCatchError(e, data);
11399      }
11400    })
11401    .fail(function (xhr) {
11402      OrganizrApiError(xhr);
11403    });
11404  let timeoutTitle = "PiHole-Homepage";
11405  if (typeof timeouts[timeoutTitle] !== "undefined") {
11406    clearTimeout(timeouts[timeoutTitle]);
11407  }
11408  timeouts[timeoutTitle] = setTimeout(function () {
11409    homepagePihole(timeout);
11410  }, timeout);
11411  delete timeout;
11412}
11413function homepageAdGuard(timeout) {
11414  var timeout =
11415    typeof timeout !== "undefined"
11416      ? timeout
11417      : activeInfo.settings.homepage.refresh.homepageAdGuardRefresh;
11418  organizrAPI2("GET", "api/v2/homepage/adguard/stats")
11419    .success(function (data) {
11420      try {
11421        let response = data.response;
11422        document.getElementById("homepageOrderAdGuard").innerHTML = "";
11423        if (response.data !== null) {
11424          buildAdGuard(response.data);
11425          $("#homepageOrderAdGuard").html(buildAdGuard(response.data));
11426        }
11427      } catch (e) {
11428        organizrCatchError(e, data);
11429      }
11430    })
11431    .fail(function (xhr) {
11432      OrganizrApiError(xhr);
11433    });
11434  let timeoutTitle = "AdGuard-Homepage";
11435  if (typeof timeouts[timeoutTitle] !== "undefined") {
11436    clearTimeout(timeouts[timeoutTitle]);
11437  }
11438  timeouts[timeoutTitle] = setTimeout(function () {
11439    homepageAdGuard(timeout);
11440  }, timeout);
11441  delete timeout;
11442}
11443function homepageHealthChecks(tags, timeout) {
11444  tags =
11445    typeof tags !== "undefined"
11446      ? tags
11447      : activeInfo.settings.homepage.options.healthChecksTags;
11448  if (tags == "") {
11449    var apiUrl = "api/v2/homepage/healthchecks";
11450  } else {
11451    var apiUrl = "api/v2/homepage/healthchecks/" + tags;
11452  }
11453  timeout =
11454    typeof timeout !== "undefined"
11455      ? timeout
11456      : activeInfo.settings.homepage.refresh.homepageHealthChecksRefresh;
11457  organizrAPI2("GET", apiUrl)
11458    .success(function (data) {
11459      try {
11460        var response = data.response;
11461        document.getElementById("homepageOrderhealthchecks").innerHTML = "";
11462        if (response.data !== null) {
11463          $("#homepageOrderhealthchecks").html(
11464            buildHealthChecks(response.data)
11465          );
11466        }
11467      } catch (e) {
11468        organizrCatchError(e, data);
11469      }
11470    })
11471    .fail(function (xhr) {
11472      OrganizrApiError(xhr);
11473    });
11474  let timeoutTitle = "HealthChecks-Homepage";
11475  if (typeof timeouts[timeoutTitle] !== "undefined") {
11476    clearTimeout(timeouts[timeoutTitle]);
11477  }
11478  timeouts[timeoutTitle] = setTimeout(function () {
11479    homepageHealthChecks(tags, timeout);
11480  }, timeout);
11481  delete timeout;
11482}
11483function homepageUnifi(timeout) {
11484  var timeout =
11485    typeof timeout !== "undefined"
11486      ? timeout
11487      : activeInfo.settings.homepage.refresh.homepageUnifiRefresh;
11488  organizrAPI2("GET", "api/v2/homepage/unifi/data")
11489    .success(function (data) {
11490      try {
11491        let response = data.response;
11492        document.getElementById("homepageOrderunifi").innerHTML = "";
11493        if (response.data !== null) {
11494          $("#homepageOrderunifi").html(buildUnifi(response.data));
11495        }
11496      } catch (e) {
11497        organizrCatchError(e, data);
11498      }
11499    })
11500    .fail(function (xhr) {
11501      OrganizrApiError(xhr);
11502    });
11503  var timeoutTitle = "Unifi-Homepage";
11504  if (typeof timeouts[timeoutTitle] !== "undefined") {
11505    clearTimeout(timeouts[timeoutTitle]);
11506  }
11507  timeouts[timeoutTitle] = setTimeout(function () {
11508    homepageUnifi(timeout);
11509  }, timeout);
11510  delete timeout;
11511}
11512function homepageDownloader(type, timeout) {
11513  var timeout =
11514    typeof timeout !== "undefined"
11515      ? timeout
11516      : activeInfo.settings.homepage.refresh.homepageDownloadRefresh;
11517  switch (type) {
11518    case "jdownloader":
11519      var action = "getJdownloader";
11520      break;
11521    case "sabnzbd":
11522      var action = "getSabnzbd";
11523      break;
11524    case "nzbget":
11525      var action = "getNzbget";
11526      break;
11527    case "transmission":
11528      var action = "getTransmission";
11529      break;
11530    case "sonarr":
11531      var action = "getSonarrQueue";
11532      break;
11533    case "radarr":
11534      var action = "getRadarrQueue";
11535      break;
11536    case "qBittorrent":
11537      var action = "getqBittorrent";
11538      break;
11539    case "deluge":
11540      var action = "getDeluge";
11541      break;
11542    case "rTorrent":
11543      var action = "getrTorrent";
11544      break;
11545    case "utorrent":
11546      var action = "getutorrent";
11547      break;
11548    default:
11549  }
11550  let lowerType = type.toLowerCase();
11551  organizrAPI2("GET", "api/v2/homepage/" + lowerType + "/queue")
11552    .success(function (data) {
11553      try {
11554        let response = data.response;
11555        if (response.data !== null) {
11556          buildDownloaderItem(response.data, type);
11557        }
11558      } catch (e) {
11559        organizrCatchError(e, data);
11560      }
11561    })
11562    .fail(function (xhr) {
11563      OrganizrApiError(xhr);
11564    });
11565  let timeoutTitle = type + "-Downloader-Homepage";
11566  if (typeof timeouts[timeoutTitle] !== "undefined") {
11567    clearTimeout(timeouts[timeoutTitle]);
11568  }
11569  timeouts[timeoutTitle] = setTimeout(function () {
11570    homepageDownloader(type, timeout);
11571  }, timeout);
11572  delete timeout;
11573}
11574function homepageStream(type, timeout) {
11575  var timeout =
11576    typeof timeout !== "undefined"
11577      ? timeout
11578      : activeInfo.settings.homepage.refresh.homepageStreamRefresh;
11579  organizrAPI2("GET", "api/v2/homepage/" + type + "/streams")
11580    .success(function (data) {
11581      try {
11582        let response = data.response;
11583        document.getElementById(
11584          "homepageOrder" + type + "nowplaying"
11585        ).innerHTML = "";
11586        $("#homepageOrder" + type + "nowplaying").html(
11587          buildStream(response.data, type)
11588        );
11589      } catch (e) {
11590        organizrCatchError(e, data);
11591      }
11592    })
11593    .fail(function (xhr) {
11594      OrganizrApiError(xhr);
11595    });
11596  let timeoutTitle = type + "-Stream-Homepage";
11597  if (typeof timeouts[timeoutTitle] !== "undefined") {
11598    clearTimeout(timeouts[timeoutTitle]);
11599  }
11600  timeouts[timeoutTitle] = setTimeout(function () {
11601    homepageStream(type, timeout);
11602  }, timeout);
11603  delete timeout;
11604}
11605function homepageRecent(type, timeout) {
11606  var timeout =
11607    typeof timeout !== "undefined"
11608      ? timeout
11609      : activeInfo.settings.homepage.refresh.homepageRecentRefresh;
11610  switch (type) {
11611    case "plex":
11612      var action = "getPlexRecent";
11613      break;
11614    case "emby":
11615    case "jellyfin":
11616      var action = "getEmbyRecent";
11617      break;
11618    default:
11619  }
11620  organizrAPI2("GET", "api/v2/homepage/" + type + "/recent")
11621    .success(function (data) {
11622      try {
11623        let response = data.response;
11624        document.getElementById("homepageOrder" + type + "recent").innerHTML =
11625          "";
11626        $("." + type + "-recent").trigger("destroy.owl.carousel");
11627        $("#homepageOrder" + type + "recent").html(
11628          buildRecent(response.data, type)
11629        );
11630        $(".recent-items").owlCarousel({
11631          nav: false,
11632          autoplay: false,
11633          dots: false,
11634          margin: 10,
11635          autoWidth: true,
11636          items: 4,
11637        });
11638      } catch (e) {
11639        organizrCatchError(e, data);
11640      }
11641    })
11642    .fail(function (xhr) {
11643      OrganizrApiError(xhr);
11644    });
11645  let timeoutTitle = type + "-Recent-Homepage";
11646  if (typeof timeouts[timeoutTitle] !== "undefined") {
11647    clearTimeout(timeouts[timeoutTitle]);
11648  }
11649  timeouts[timeoutTitle] = setTimeout(function () {
11650    homepageRecent(type, timeout);
11651  }, timeout);
11652  delete timeout;
11653}
11654function homepagePlaylist(type, timeout = 30000) {
11655  organizrAPI2("GET", "api/v2/homepage/" + type + "/playlists")
11656    .success(function (data) {
11657      try {
11658        let response = data.response;
11659        document.getElementById("homepageOrder" + type + "playlist").innerHTML =
11660          "";
11661        $("#homepageOrder" + type + "playlist").html(
11662          buildPlaylist(response.data, type)
11663        );
11664        $(".playlist-items").owlCarousel({
11665          nav: false,
11666          autoplay: false,
11667          dots: false,
11668          margin: 10,
11669          autoWidth: true,
11670          items: 4,
11671        });
11672      } catch (e) {
11673        organizrCatchError(e, data);
11674      }
11675    })
11676    .fail(function (xhr) {
11677      OrganizrApiError(xhr);
11678    });
11679}
11680function defaultRequestFilter(service) {
11681  switch (service) {
11682    case "ombi":
11683      var defaultFilter = {
11684        "request-filter-approved-ombi":
11685          activeInfo.settings.homepage.ombi.ombiDefaultFilterApproved,
11686        "request-filter-unapproved-ombi":
11687          activeInfo.settings.homepage.ombi.ombiDefaultFilterUnapproved,
11688        "request-filter-available-ombi":
11689          activeInfo.settings.homepage.ombi.ombiDefaultFilterAvailable,
11690        "request-filter-unavailable-ombi":
11691          activeInfo.settings.homepage.ombi.ombiDefaultFilterUnavailable,
11692        "request-filter-denied-ombi":
11693          activeInfo.settings.homepage.ombi.ombiDefaultFilterDenied,
11694      };
11695      $.each(defaultFilter, function (i, v) {
11696        if (v == false) {
11697          $("#" + i).click();
11698        }
11699      });
11700    case "overseerr":
11701      var defaultFilter = {
11702        "request-filter-approved-overseerr":
11703          activeInfo.settings.homepage.overseerr.overseerrDefaultFilterApproved,
11704        "request-filter-unapproved-overseerr":
11705          activeInfo.settings.homepage.overseerr
11706            .overseerrDefaultFilterUnapproved,
11707        "request-filter-available-overseerr":
11708          activeInfo.settings.homepage.overseerr
11709            .overseerrDefaultFilterAvailable,
11710        "request-filter-unavailable-overseerr":
11711          activeInfo.settings.homepage.overseerr
11712            .overseerrDefaultFilterUnavailable,
11713        "request-filter-denied-overseerr":
11714          activeInfo.settings.homepage.overseerr.overseerrDefaultFilterDenied,
11715      };
11716      $.each(defaultFilter, function (i, v) {
11717        if (v == false) {
11718          $("#" + i).click();
11719        }
11720      });
11721  }
11722}
11723function homepageRequests(service, timeout) {
11724  switch (service) {
11725    case "ombi":
11726      var apiUrl = "api/v2/homepage/ombi/requests";
11727      var div = "homepageOrderombi";
11728      var timeout =
11729        typeof timeout !== "undefined"
11730          ? timeout
11731          : activeInfo.settings.homepage.refresh.ombiRefresh;
11732      break;
11733    case "overseerr":
11734      var apiUrl = "api/v2/homepage/overseerr/requests";
11735      var div = "homepageOrderoverseerr";
11736      var timeout =
11737        typeof timeout !== "undefined"
11738          ? timeout
11739          : activeInfo.settings.homepage.refresh.overseerrRefresh;
11740      break;
11741    default:
11742      return false;
11743  }
11744  organizrAPI2("GET", apiUrl)
11745    .success(function (data) {
11746      try {
11747        let response = data.response;
11748        document.getElementById(div).innerHTML = "";
11749        if (response.data.content !== false) {
11750          $("#" + div).html(buildRequest(service, div, response.data));
11751        }
11752        $(".request-items-" + service).owlCarousel({
11753          nav: false,
11754          autoplay: false,
11755          dots: false,
11756          margin: 10,
11757          autoWidth: true,
11758          items: 4,
11759        });
11760        // Default Filter
11761        defaultRequestFilter(service);
11762      } catch (e) {
11763        organizrCatchError(e, data);
11764      }
11765    })
11766    .fail(function (xhr) {
11767      OrganizrApiError(xhr);
11768    });
11769  if (typeof timeouts[service + "-Requests-Homepage"] !== "undefined") {
11770    clearTimeout(timeouts[service + "-Requests-Homepage"]);
11771  }
11772  timeouts[service + "-Requests-Homepage"] = setTimeout(function () {
11773    homepageRequests(service, timeout);
11774  }, timeout);
11775  delete timeout;
11776}
11777function testAPIConnection(service, data = "") {
11778  messageSingle(
11779    "",
11780    " Testing now...",
11781    activeInfo.settings.notifications.position,
11782    "#FFF",
11783    "info",
11784    "60000"
11785  );
11786  organizrAPI2("POST", "api/v2/test/" + service, data)
11787    .success(function (data) {
11788      try {
11789        let response = data.response;
11790        messageSingle(
11791          "",
11792          " API Connection Success",
11793          activeInfo.settings.notifications.position,
11794          "#FFF",
11795          "success",
11796          "10000"
11797        );
11798      } catch (e) {
11799        organizrCatchError(e, data);
11800      }
11801    })
11802    .fail(function (xhr) {
11803      OrganizrApiError(xhr, "API Error");
11804    });
11805}
11806function getUnifiSite() {
11807  messageSingle(
11808    "",
11809    " Grabbing now...",
11810    activeInfo.settings.notifications.position,
11811    "#FFF",
11812    "info",
11813    "10000"
11814  );
11815  organizrAPI2("POST", "api/v2/test/unifi/site", {})
11816    .success(function (data) {
11817      try {
11818        var response = data.response;
11819        if (response.data !== false) {
11820          var sites = "";
11821          if (response.data.data) {
11822            $.each(response.data.data, function (i, v) {
11823              sites +=
11824                '<div class="form-group row"><div class="col-sm-12"><h4 class="mouse" onclick="unifiSiteApply(\'' +
11825                v.name +
11826                "')\">" +
11827                v.desc +
11828                "</h4></div></div>";
11829            });
11830          } else {
11831            //console.log('no');
11832          }
11833          var div =
11834            `
11835                <div class="row">
11836                    <div class="col-12">
11837                        <div class="card m-b-0">
11838                            <div class="form-horizontal">
11839                                <div class="card-body">
11840                                    <h4 class="card-title" lang="en">Choose Unifi Site</h4>
11841                                    ` +
11842            sites +
11843            `
11844                                </div>
11845                            </div>
11846                        </div>
11847                    </div>
11848                </div>
11849            `;
11850          swal({
11851            content: createElementFromHTML(div),
11852            buttons: false,
11853            className: "bg-org",
11854          });
11855        } else {
11856          messageSingle(
11857            "API Connection Failed",
11858            response.data,
11859            activeInfo.settings.notifications.position,
11860            "#FFF",
11861            "error",
11862            "10000"
11863          );
11864        }
11865      } catch (e) {
11866        organizrCatchError(e, data);
11867      }
11868    })
11869    .fail(function (xhr) {
11870      OrganizrApiError(xhr, "API Error");
11871    });
11872}
11873function unifiSiteApply(name) {
11874  $("#homepage-UniFi-form [name=unifiSiteName]").val(name);
11875  $("#homepage-UniFi-form [name=unifiSiteName]").change();
11876  swal.close();
11877  messageSingle(
11878    "",
11879    " Grabbed Site - Please Save Now",
11880    activeInfo.settings.notifications.position,
11881    "#FFF",
11882    "success",
11883    "10000"
11884  );
11885}
11886function homepageCalendar(timeout) {
11887  var timeout =
11888    typeof timeout !== "undefined"
11889      ? timeout
11890      : activeInfo.settings.homepage.refresh.calendarRefresh;
11891  if (activeInfo.settings.homepage.options.alternateHomepageHeaders) {
11892    $(".fc-toolbar").addClass("fc-alternate");
11893  }
11894  organizrAPI2("GET", "api/v2/homepage/calendar")
11895    .success(function (data) {
11896      try {
11897        let response = data.response;
11898        $("#calendar").fullCalendar("removeEvents");
11899        $("#calendar").fullCalendar("addEventSource", response.data.events);
11900        $("#calendar").fullCalendar("addEventSource", response.data.ical);
11901        $("#calendar").fullCalendar("today");
11902      } catch (e) {
11903        organizrCatchError(e, data);
11904      }
11905    })
11906    .fail(function (xhr) {
11907      OrganizrApiError(xhr);
11908    });
11909  if (typeof timeouts["calendar-Homepage"] !== "undefined") {
11910    clearTimeout(timeouts["calendar-Homepage"]);
11911  }
11912  timeouts["calendar-Homepage"] = setTimeout(function () {
11913    homepageCalendar(timeout);
11914  }, timeout);
11915  delete timeout;
11916}
11917function buildTautulliItem(array) {
11918  var cards = "";
11919  var homestats = array.homestats.data;
11920  var libstats = array.libstats;
11921  var options = array.options;
11922  var friendlyName = array.options.friendlyName;
11923  var buildLibraries = function (data) {
11924    var libs = data.data;
11925    var movies = [];
11926    var tv = [];
11927    var audio = [];
11928
11929    libs.forEach((e) => {
11930      switch (e["section_type"]) {
11931        case "movie":
11932          movies.push(e);
11933          break;
11934        case "show":
11935          tv.push(e);
11936          break;
11937        case "artist":
11938          audio.push(e);
11939          break;
11940        default:
11941          break;
11942      }
11943    });
11944
11945    movies = movies.sort((a, b) =>
11946      parseInt(a["count"]) > parseInt(b["count"]) ? -1 : 1
11947    );
11948    tv = tv.sort((a, b) =>
11949      parseInt(a["count"]) > parseInt(b["count"]) ? -1 : 1
11950    );
11951    audio = audio.sort((a, b) =>
11952      parseInt(a["count"]) > parseInt(b["count"]) ? -1 : 1
11953    );
11954
11955    var buildCard = function (type, data) {
11956      var extraField = null;
11957      var section_name = null;
11958      if (type == "movie") {
11959        extraField = "Movies";
11960        section_name = "Movie Libraries";
11961      } else if (type == "show") {
11962        extraField = "Shows/Seasons/Episodes";
11963        section_name = "TV Show Libraries";
11964      } else if (type == "artist") {
11965        extraField = "Artists/Albums/Tracks";
11966        section_name = "Music Libraries";
11967      }
11968      var cardTitle =
11969        '<th><span class="pull-left cardTitle">' +
11970        section_name.toUpperCase() +
11971        '</span><span class="pull-right cardCountType">' +
11972        extraField.toUpperCase() +
11973        "</th>";
11974      var card =
11975        `
11976            <div class="col-lg-4 col-md-6 col-sm-12 col-xs-12">
11977                <div class="card text-white mb-3 homepage-tautulli-card library-card">
11978                    <div class="card-body h-100 bg-org-alt">
11979                        <table class="h-100 w-100">
11980                            <tr>
11981                                <td rowspan='2' class="poster-td text-center"><img src="data/cache/tautulli-` +
11982        type +
11983        `.svg" class="lib-icon" alt="library icon"></td>
11984                                ${cardTitle}
11985                            </tr>
11986                            <tr>
11987                                <td>
11988                                    <div class="scrollable default-scroller">`;
11989      for (var i = 0; i < data.length; i++) {
11990        var rowType =
11991          i == 0
11992            ? "tautulliFirstItem"
11993            : i == data.length - 1
11994            ? "tautulliLastItem"
11995            : "";
11996        var rowValue = "";
11997        var firstDivCol = "";
11998        var secondDivCol = "";
11999        if (type == "movie") {
12000          rowValue = data[i]["count"];
12001          firstDivCol = "col-md-9";
12002          secondDivCol = "col-md-2";
12003        } else {
12004          rowValue =
12005            data[i]["count"] +
12006            '<span class="tautulliSeparator"> / </span>' +
12007            data[i]["parent_count"] +
12008            '<span class="tautulliSeparator"> / </span>' +
12009            data[i]["child_count"];
12010          firstDivCol = "col-md-5";
12011          secondDivCol = "col-md-6";
12012        }
12013        card += `
12014                                        <div class="cardListItem elip row w-100 p-r-0 m-0 ${rowType}">
12015                                            <div class="tautulliRank col-md-1 p-0">${
12016                                              i + 1
12017                                            }</div>
12018                                            <div class="${firstDivCol} p-0 text-left elip"> ${
12019          data[i]["section_name"]
12020        }</div>
12021                                            <div class="${secondDivCol} cardListCount text-right m-l-10 p-0">${rowValue}</div>
12022                                        </div>
12023                                        `;
12024      }
12025
12026      card += `
12027                                    </div>
12028                                </td>
12029                            </tr>
12030                        </table>
12031                    </div>
12032                </div>
12033            </div>`;
12034      return card;
12035    };
12036    var card = movies.length > 0 ? buildCard("movie", movies) : "";
12037    card += tv.length > 0 ? buildCard("show", tv) : "";
12038    card += audio.length > 0 ? buildCard("artist", audio) : "";
12039    return card;
12040  };
12041  var buildStats = function (data, stat, friendlyName = true) {
12042    var card = "";
12043    data.forEach((e) => {
12044      let classes = "";
12045      if (e["stat_id"] == stat && e["rows"].length > 0) {
12046        if (stat === "top_platforms") {
12047          classes = " platform-" + e["rows"][0]["platform_name"] + "-rgba";
12048        } else {
12049          classes = " bg-org-alt";
12050        }
12051        card += `
12052                <div class="col-xl-3 col-lg-4 col-md-6 col-sm-12 col-xs-12">
12053                    <div class="card text-white mb-3 homepage-tautulli-card">`;
12054        if (stat !== "top_users" && stat !== "top_platforms") {
12055          card +=
12056            `
12057                            <div class="bg-img-cont">
12058                                <img class="bg-img" src="` +
12059            e["rows"][0]["art"] +
12060            `" alt="background art">
12061                            </div>
12062                            `;
12063        }
12064        card +=
12065          `
12066                        <div class="card-body h-100` +
12067          classes +
12068          `">
12069                            <table class="h-100 w-100">
12070                                <tr>`;
12071        if (stat == "top_users") {
12072          card +=
12073            `<td rowspan="2" class="poster-td text-center"><img src="` +
12074            e["rows"][0]["user_thumb"] +
12075            `" class="poster avatar" alt="user avatar"></td>`;
12076        } else if (stat == "top_platforms") {
12077          card +=
12078            `<td rowspan="2" class="poster-td text-center"><img src="data/cache/tautulli-` +
12079            e["rows"][0]["platform_name"] +
12080            `.svg" class="poster" alt="platform icon"></td>`;
12081        } else {
12082          card +=
12083            `<td rowspan="2" class="poster-td"><img src="` +
12084            e["rows"][0]["thumb"] +
12085            `" class="poster" alt="movie poster"></td>`;
12086        }
12087        var extraField = null;
12088        if (e["stat_title"].includes("Popular")) {
12089          extraField = "users";
12090        } else if (
12091          e["stat_title"].includes("Watched") ||
12092          e["stat_title"].includes("Active")
12093        ) {
12094          extraField = "plays";
12095        }
12096        var cardTitle =
12097          '<th><span class="pull-left cardTitle">' +
12098          e["stat_title"].toUpperCase() +
12099          '</span><span class="pull-right cardCountType">' +
12100          extraField.toUpperCase() +
12101          "</th>";
12102        card +=
12103          cardTitle +
12104          `
12105                                </tr>
12106                                <tr>
12107                                    <td><div class="scrollable default-scroller">`;
12108        for (var i = 0; i < e["rows"].length; i++) {
12109          var item = e["rows"][i];
12110          var rowType =
12111            i == 0
12112              ? "tautulliFirstItem"
12113              : i == e["rows"].length - 1
12114              ? "tautulliLastItem"
12115              : "";
12116          var rowNameValue = "";
12117          var rowValue = "";
12118          if (stat == "top_users") {
12119            if (friendlyName) {
12120              rowNameValue = item["friendly_name"];
12121            } else {
12122              rowNameValue = item["user"];
12123            }
12124            rowValue = item["total_plays"];
12125          } else if (stat == "top_platforms") {
12126            rowNameValue = item["platform"];
12127            rowValue = item["total_plays"];
12128          } else if (extraField == "users") {
12129            rowNameValue = item["title"];
12130            rowValue = item["users_watched"];
12131          } else {
12132            rowNameValue = item["title"];
12133            rowValue = item["total_plays"];
12134          }
12135          card += `
12136                                            <div class="cardListItem elip row w-100 p-r-0 m-0 ${rowType}">
12137                                                <div class="tautulliRank col-md-1 p-0">${
12138                                                  i + 1
12139                                                }</div>
12140                                                <div class="col-md-9 p-0 text-left elip">${rowNameValue}</div>
12141                                                <div class="col-md-2 cardListCount text-right m-l-10 p-0">${rowValue}</div>
12142                                            </div>`;
12143        }
12144        card += `
12145                                    </div></td>
12146                                </tr>
12147                            </table>
12148                        </div>
12149                    </div>
12150                </div>`;
12151      } else {
12152        return "";
12153      }
12154    });
12155    return card;
12156  };
12157  cards += '<div class="row tautulliTop">';
12158  cards += options["libraries"] ? buildLibraries(libstats) : "";
12159  cards += options["popularMovies"]
12160    ? buildStats(homestats, "popular_movies")
12161    : "";
12162  cards += options["popularTV"] ? buildStats(homestats, "popular_tv") : "";
12163  cards += options["topMovies"] ? buildStats(homestats, "top_movies") : "";
12164  cards += options["topTV"] ? buildStats(homestats, "top_tv") : "";
12165  cards += options["topUsers"]
12166    ? buildStats(homestats, "top_users", friendlyName)
12167    : "";
12168  cards += options["topPlatforms"]
12169    ? buildStats(homestats, "top_platforms")
12170    : "";
12171  cards += "</div>";
12172  cards += '<div class="row tautulliLibraries">';
12173  cards += "</div>";
12174  return cards;
12175}
12176function buildTautulli(array) {
12177  if (array === false) {
12178    return "";
12179  }
12180  var html = `
12181    <div id="allTautulli">
12182		<div class="el-element-overlay row">`;
12183  if (array["options"]["title"]) {
12184    html +=
12185      `
12186            <div class="col-md-12">
12187                <h4 class="pull-left homepage-element-title"><span class="mouse" onclick="homepageTautulli()">` +
12188      activeInfo.settings.homepage.options.titles.tautulli +
12189      `</span> : </h4><h4 class="pull-left">&nbsp;</h4>
12190                <hr class="hidden-xs ml-2">
12191            </div>
12192            <div class="clearfix"></div>
12193        `;
12194  }
12195  html +=
12196    `
12197            <div class="tautulliCards col-sm-12 my-3">
12198                ` +
12199    buildTautulliItem(array) +
12200    `
12201			</div>
12202		</div>
12203	</div>
12204    `;
12205  return array ? html : "";
12206}
12207function homepageTautulli(timeout) {
12208  var timeout =
12209    typeof timeout !== "undefined"
12210      ? timeout
12211      : activeInfo.settings.homepage.refresh.homepageTautulliRefresh;
12212  organizrAPI2("GET", "api/v2/homepage/tautulli/data")
12213    .success(function (data) {
12214      try {
12215        let response = data.response;
12216        document.getElementById("homepageOrdertautulli").innerHTML = "";
12217        if (response.data !== null) {
12218          $("#homepageOrdertautulli").html(buildTautulli(response.data));
12219        }
12220      } catch (e) {
12221        organizrCatchError(e, data);
12222      }
12223    })
12224    .fail(function (xhr) {
12225      OrganizrApiError(xhr);
12226    });
12227  let timeoutTitle = "Tautulli-Homepage";
12228  if (typeof timeouts[timeoutTitle] !== "undefined") {
12229    clearTimeout(timeouts[timeoutTitle]);
12230  }
12231  timeouts[timeoutTitle] = setTimeout(function () {
12232    homepageTautulli(timeout);
12233  }, timeout);
12234  delete timeout;
12235}
12236function weatherIcon(code, daytime = true) {
12237  switch (code) {
12238    case 1:
12239    case 2:
12240      return daytime ? "wi-day-sunny" : "wi-night-clear";
12241    case 3:
12242    case 4:
12243    case 5:
12244    case 6:
12245    case 22:
12246      return daytime ? "wi-day-sunny-overcast" : "wi-night-alt-partly-cloudy";
12247    case 7:
12248    case 8:
12249    case 9:
12250      return daytime ? "wi-day-cloudy-high" : "wi-night-partly-cloudy";
12251    case 10:
12252    case 11:
12253    case 12:
12254      return daytime ? "wi-day-thunderstorm" : "wi-night-thunderstorm";
12255    case 13:
12256    case 14:
12257    case 15:
12258      return daytime ? "wi-day-haze" : "wi-night-cloudy-windy";
12259    case 16:
12260    case 17:
12261    case 18:
12262      return daytime ? "wi-day-fog" : "wi-night-fog";
12263    case 19:
12264    case 20:
12265    case 21:
12266      return daytime ? "wi-day-cloudy-high" : "wi-night-cloudy-high";
12267    case 23:
12268    case 25:
12269      return daytime ? "wi-day-rain" : "wi-night-rain";
12270    case 24:
12271    case 26:
12272      return daytime ? "wi-day-snow" : "wi-night-snow";
12273    case 27:
12274    case 28:
12275    case 30:
12276    case 31:
12277    case 33:
12278      return daytime ? "wi-day-rain-mix" : "wi-night-alt-rain-mix";
12279    case 29:
12280    case 32:
12281    case 34:
12282    case 35:
12283      return daytime
12284        ? "wi-day-snow-thunderstorm"
12285        : "wi-night-alt-snow-thunderstorm";
12286    default:
12287      return daytime ? "wi-day-sunny" : "wi-night-clear";
12288  }
12289}
12290function buildWeatherAndAir(array) {
12291  var returnData = "";
12292  if (typeof array.content === "undefined") {
12293    return "";
12294  }
12295  if (array.content.weather !== false) {
12296    if (array.content.weather.error === null) {
12297      let dates = {};
12298      $.each(array.content.weather.data, function (i, v) {
12299        let date = moment(v.datetime).format("YYYY-MM-DD");
12300        if (typeof dates[date] === "undefined") {
12301          dates[date] = v;
12302          dates[date]["temps"] = {
12303            high: v.temperature.value,
12304            low: v.temperature.value,
12305          };
12306        } else {
12307          if (moment(v.datetime).format("hh:mm a") == "12:00 pm") {
12308            dates[date]["icon_code"] = v.icon_code;
12309            dates[date]["is_day_time"] = v.is_day_time;
12310          }
12311          if (v.temperature.value > dates[date]["temps"]["high"]) {
12312            dates[date]["temps"]["high"] = v.temperature.value;
12313          }
12314          if (v.temperature.value < dates[date]["temps"]["low"]) {
12315            dates[date]["temps"]["low"] = v.temperature.value;
12316          }
12317        }
12318      });
12319      let weatherItems = '<div class="row">';
12320      let weatherItemsCount = 0;
12321      $.each(dates, function (i, v) {
12322        if (weatherItemsCount === 0) {
12323          weatherItems +=
12324            `
12325                    <div class="col-lg-4 col-sm-12 col-xs-12">
12326                        <div class="white-box">
12327                            <h3 class="box-title"><small class="pull-right m-t-10">Feels Like ` +
12328            Math.round(v.feels_like_temperature.value) +
12329            `°</small>` +
12330            moment(v.datetime).format("dddd") +
12331            `<br/><small class="text-uppercase elip">` +
12332            v.weather_text +
12333            `</small></h3>
12334                            <ul class="list-inline two-part" style="margin-top: -13px;">
12335                                <li><i class="wi ` +
12336            weatherIcon(v.icon_code, v.is_day_time) +
12337            ` text-info"></i></li>
12338                                <li class="text-right"><span class="counter">` +
12339            Math.round(v.temperature.value) +
12340            `<small><sup>°` +
12341            v.temperature.units +
12342            `</sup></small></span></li>
12343                            </ul>
12344                            <ul class="list-inline m-b-0">
12345                                <li class="pull-left w-50 hidden-xs"></li>
12346                                <li class="pull-right" style="width:75px"><small><i class="wi wi-strong-wind m-r-5 text-primary tooltip-primary" data-toggle="tooltip" data-placement="top" title="" data-original-title="Wind"></i>` +
12347            Math.round(v.wind.speed.value) +
12348            ` ` +
12349            v.wind.speed.units +
12350            `</small></li>
12351                                <li class="pull-right" style="width:75px"><small><i class="wi wi-barometer m-r-5 text-primary tooltip-primary" data-toggle="tooltip" data-placement="top" title="" data-original-title="Pressure"></i>` +
12352            Math.round(v.pressure.value) +
12353            ` ` +
12354            v.pressure.units +
12355            `</small></li>
12356                                <li class="pull-right" style="width:45px"><small><i class="wi wi-humidity m-r-5 text-primary tooltip-primary" data-toggle="tooltip" data-placement="top" title="" data-original-title="Humidity"></i>` +
12357            Math.round(v.relative_humidity) +
12358            `</small></li>
12359                                <li class="pull-right" style="width:45px"><small><i class="wi wi-raindrop m-r-5 text-primary tooltip-primary" data-toggle="tooltip" data-placement="top" title="" data-original-title="Dew Point"></i>` +
12360            Math.round(v.dew_point.value) +
12361            `°</small></li>
12362                                <div class="clearfix"></div>
12363                            </ul>
12364                        </div>
12365                    </div>
12366                    `;
12367        } else if (weatherItemsCount !== 5) {
12368          weatherItems +=
12369            `
12370                    <div class="col-lg-2 col-sm-3 col-xs-12">
12371                        <div class="white-box">
12372                            <h3 class="box-title">` +
12373            moment(v.datetime).format("dddd") +
12374            `</h3>
12375                            <ul class="list-inline two-part">
12376                                <li><i class="wi ` +
12377            weatherIcon(v.icon_code, v.is_day_time) +
12378            ` text-info"></i></li>
12379                                <li class="text-right"><span class="counter">` +
12380            Math.round(v.temps.high) +
12381            `<small><sup>°` +
12382            v.temperature.units +
12383            `</sup></small></span></li>
12384                            </ul>
12385                            <ul class="list-inline m-b-0">
12386                                <li class="pull-left w-100"><small class="text-uppercase elip">` +
12387            v.weather_text +
12388            `</small></li>
12389                                <div class="clearfix"></div>
12390                            </ul>
12391                        </div>
12392                    </div>
12393                    `;
12394        }
12395        weatherItemsCount++;
12396      });
12397      weatherItems += "</div>";
12398      returnData += weatherItems;
12399    }
12400  }
12401  if (array.content.air !== false) {
12402    if (array.content.air.error === null) {
12403      let airItems = '<div class="row">';
12404      let activeClasses = {
12405        poor: "",
12406        low: "",
12407        moderate: "",
12408        good: "",
12409        excellent: "",
12410        text: "",
12411      };
12412      if (array.content.air.data.indexes.baqi.aqi <= 20) {
12413        activeClasses["poor"] = "active";
12414        activeClasses["text"] = "text-poor-gradient";
12415      } else if (array.content.air.data.indexes.baqi.aqi <= 40) {
12416        activeClasses["low"] = "active";
12417        activeClasses["text"] = "text-low-gradient";
12418      } else if (array.content.air.data.indexes.baqi.aqi <= 60) {
12419        activeClasses["moderate"] = "active";
12420        activeClasses["text"] = "text-moderate-gradient";
12421      } else if (array.content.air.data.indexes.baqi.aqi <= 80) {
12422        activeClasses["good"] = "active";
12423        activeClasses["text"] = "text-good-gradient";
12424      } else if (array.content.air.data.indexes.baqi.aqi <= 100) {
12425        activeClasses["excellent"] = "active";
12426        activeClasses["text"] = "text-excellent-gradient";
12427      }
12428      airItems += `
12429            <div class="col-lg-4 col-sm-12 col-xs-12">
12430                <div class="white-box text-white">
12431                    <div class="aqi-scale-component-wrapper">
12432                        <div class="aqi__header">
12433                            <div class="aqi__value">
12434                                <div class="component-wrapper aqi-number ${
12435                                  activeClasses["text"]
12436                                }">${
12437        array.content.air.data.indexes.baqi.aqi
12438      }</div>
12439                            </div>
12440                            <div class="aqi__text"><h2 >AirQuality Index</h2></div>
12441                        </div>
12442                        <div class="aqi-scale m-t-40">
12443                            <div class="category">
12444                                <div class="chip ${activeClasses["poor"]}">
12445                                    <div class="chip__text text-white">Poor</div>
12446                                    <div class="chip__bar bg-poor-gradient"></div>
12447                                </div>
12448                                <div class="category__min-value text-white">0</div>
12449                                <div class="category__max-value text-white">20</div>
12450                            </div>
12451                            <div class="category">
12452                                <div class="chip ${activeClasses["low"]}">
12453                                    <div class="chip__text text-white">Low</div>
12454                                    <div class="chip__bar bg-low-gradient"></div>
12455                                </div>
12456                                <div class="category__max-value text-white">40</div>
12457                            </div>
12458                            <div class="category">
12459                                <div class="chip ${activeClasses["moderate"]}">
12460                                    <div class="chip__text text-white">Moderate</div>
12461                                    <div class="chip__bar bg-moderate-gradient"></div>
12462                                </div>
12463                                <div class="category__max-value text-white">60</div>
12464                            </div>
12465                            <div class="category">
12466                                <div class="chip ${activeClasses["good"]}">
12467                                    <div class="chip__text text-white">Good</div>
12468                                    <div class="chip__bar bg-good-gradient"></div>
12469                                </div>
12470                                <div class="category__max-value text-white">80</div>
12471                            </div>
12472                            <div class="category">
12473                                <div class="chip ${activeClasses["excellent"]}">
12474                                    <div class="chip__text text-white">Excellent</div>
12475                                    <div class="chip__bar bg-excellent-gradient"></div>
12476                                </div>
12477                                <div class="category__max-value text-white">100</div>
12478                            </div>
12479                        </div>
12480                    </div>
12481                </div>
12482            </div>
12483            ${buildPollutant(array.content.air.data.pollutants)}
12484            ${buildHealthRecommendation(
12485              array.content.air.data.health_recommendations
12486            )}
12487            `;
12488      airItems += "</div>";
12489      returnData += airItems;
12490    }
12491  }
12492  if (array.content.pollen !== false) {
12493    if (array.content.pollen.error === null) {
12494    }
12495  }
12496  return returnData;
12497}
12498function buildHealthRecommendation(array) {
12499  var healthHeader = "";
12500  var healthSection = "";
12501  $.each(array, function (i, v) {
12502    var title = i
12503      .toString()
12504      .replace("_", " ")
12505      .toLowerCase()
12506      .split(" ")
12507      .map((word) => word.charAt(0).toUpperCase() + word.substring(1))
12508      .join(" ");
12509    switch (i) {
12510      case "general_population":
12511        var icon = "fa fa-group";
12512        break;
12513      case "elderly":
12514        var icon = "ti ti-wheelchair";
12515        break;
12516      case "lung_diseases":
12517        var icon = "mdi mdi-spray";
12518        break;
12519      case "heart_diseases":
12520        var icon = "mdi mdi-heart-pulse";
12521        break;
12522      case "active":
12523        var icon = "mdi mdi-run-fast";
12524        break;
12525      case "pregnant_women":
12526        var icon = "mdi mdi-human-pregnant";
12527        break;
12528      case "children":
12529        var icon = "fa fa-child";
12530        break;
12531      default:
12532        var icon = "";
12533    }
12534    healthHeader +=
12535      '<li><a href="#section-health-' +
12536      i +
12537      '" class="sticon ' +
12538      icon +
12539      '"></a></li>';
12540    healthSection += `
12541            <section id="section-pollutant-${i}" class="" >
12542                <h5 class="m-t-0">${title}</h5>
12543                <span>${v}</span>
12544            </section>
12545        `;
12546  });
12547  var html = `
12548    <div class="col-lg-4 hidden-xs hidden-sm">
12549        <div class="white-box text-white p-0">
12550            <!-- Tabstyle start -->
12551            <section class="">
12552                <div class="sttabs sttabs-main-weather-health-div tabs-style-iconbox">
12553                    <nav>
12554                        <ul>${healthHeader}</ul>
12555                    </nav>
12556                    <div class="content-wrap health-and-pollutant-section default-scroller">${healthSection}</div>
12557                    <!-- /content -->
12558                </div>
12559                <!-- /tabs -->
12560            </section>
12561            <!-- Tabstyle start -->
12562        </div>
12563    </div>
12564    <script>
12565        (function() {
12566            [].slice.call(document.querySelectorAll('.sttabs-main-weather-health-div')).forEach(function(el) {
12567                new CBPFWTabs(el);
12568            });
12569        })();
12570    </script>`;
12571  return html;
12572}
12573function buildPollutant(array) {
12574  var pollutantHeader = "";
12575  var pollutantSection = "";
12576  $.each(array, function (i, v) {
12577    pollutantHeader +=
12578      '<li><a href="#section-pollutant-' +
12579      i +
12580      '" class="sticon"><strong>' +
12581      v.display_name +
12582      '</strong><br/><small class="elip">' +
12583      v.concentration.value +
12584      " " +
12585      v.concentration.units +
12586      "</small></a></li>";
12587    pollutantSection += `
12588            <section id="section-pollutant-${i}">
12589                <h5 class="m-t-0">${v.full_name}</h5>
12590                <h6>Sources</h6>
12591                <span>${v.sources_and_effects.sources}</span>
12592                <hr>
12593                <h6>Effects</h6>
12594                <span>${v.sources_and_effects.effects}</span>
12595            </section>
12596        `;
12597  });
12598  var html = `
12599    <div class="col-lg-4 hidden-xs hidden-sm">
12600        <div class="white-box text-white p-0">
12601            <!-- Tabstyle start -->
12602            <section class="">
12603                <div class="sttabs sttabs-main-weather-pollutant-div tabs-style-iconbox">
12604                    <nav>
12605                        <ul>${pollutantHeader}</ul>
12606                    </nav>
12607                    <div class="content-wrap health-and-pollutant-section default-scroller">${pollutantSection}</div>
12608                    <!-- /content -->
12609                </div>
12610                <!-- /tabs -->
12611            </section>
12612            <!-- Tabstyle start -->
12613        </div>
12614    </div>
12615    <script>
12616        (function() {
12617            [].slice.call(document.querySelectorAll('.sttabs-main-weather-pollutant-div')).forEach(function(el) {
12618                new CBPFWTabs(el);
12619            });
12620        })();
12621    </script>`;
12622  return html;
12623}
12624function homepageWeatherAndAir(timeout) {
12625  var timeout =
12626    typeof timeout !== "undefined"
12627      ? timeout
12628      : activeInfo.settings.homepage.refresh.homepageWeatherAndAirRefresh;
12629  organizrAPI2("GET", "api/v2/homepage/weather/data")
12630    .success(function (data) {
12631      try {
12632        let response = data.response;
12633        if (response.data !== null) {
12634          document.getElementById("homepageOrderWeatherAndAir").innerHTML = "";
12635          $("#homepageOrderWeatherAndAir").html(
12636            buildWeatherAndAir(response.data)
12637          );
12638        }
12639      } catch (e) {
12640        organizrCatchError(e, data);
12641      }
12642    })
12643    .fail(function (xhr) {
12644      OrganizrApiError(xhr);
12645    });
12646  let timeoutTitle = "WeatherAndAir-Homepage";
12647  if (typeof timeouts[timeoutTitle] !== "undefined") {
12648    clearTimeout(timeouts[timeoutTitle]);
12649  }
12650  timeouts[timeoutTitle] = setTimeout(function () {
12651    homepageWeatherAndAir(timeout);
12652  }, timeout);
12653  delete timeout;
12654}
12655function buildMonitorrItem(array) {
12656  var cards = "";
12657  var options = array["options"];
12658  var services = array["services"];
12659  var tabName = "";
12660
12661  var buildCard = function (name, data) {
12662    if (data.status == true) {
12663      var statusColor = "success";
12664      var imageText = "fa fa-check-circle text-success";
12665    } else if (data.status == "unresponsive") {
12666      var statusColor = "warning animated-3 loop-animation flash";
12667      var imageText = "fa fa-times-circle text-warning";
12668    } else {
12669      var statusColor = "danger animated-3 loop-animation flash";
12670      var imageText = "fa fa-times-circle text-danger";
12671    }
12672    if (typeof data.link !== "undefined" && data.link.includes("#")) {
12673      tabName = data.link.substring(data.link.indexOf("#") + 1);
12674      console.log(tabName);
12675      // Need to rework tabActions
12676      monitorrLink =
12677        '<a href="javascript:void(0)" onclick="tabActions(event,\'' +
12678        tabName +
12679        "',1)\">";
12680    } else if (typeof data.link !== "undefined") {
12681      monitorrLink = '<a href="' + data.link + '" target="_blank">';
12682    }
12683    if (options["compact"]) {
12684      var card =
12685        `
12686            <div class="col-xl-2 col-lg-3 col-md-4 col-sm-6 col-xs-12">
12687                <div class="card bg-inverse text-white mb-3 monitorr-card">
12688                    <div class="card-body bg-org-alt pt-1 pb-1">
12689                        <div class="d-flex no-block align-items-center">
12690                            <div class="left-health bg-` +
12691        statusColor +
12692        `"></div>
12693                            <div class="ml-1 w-100">
12694                                <i class="` +
12695        imageText +
12696        ` font-20 pull-right mt-3 mb-2"></i>
12697                                `;
12698      if (typeof data.link !== "undefined") {
12699        card += monitorrLink;
12700      }
12701      card +=
12702        `<h3 class="d-flex no-block align-items-center mt-2 mb-2"><img class="lazyload loginTitle" src="` +
12703        data.image +
12704        `">&nbsp;` +
12705        name +
12706        `</h3>
12707                                `;
12708      if (typeof data.link !== "undefined") {
12709        card += `</a>`;
12710      }
12711      card += `<div class="clearfix"></div>
12712                            </div>
12713                        </div>
12714                    </div>
12715                </div>
12716            </div>`;
12717    } else {
12718      var card = `
12719            <div class="col-lg-2 col-md-3 col-sm-4 col-xs-6">
12720                <div class="card bg-inverse text-white mb-3 monitorr-card">
12721                    <div class="card-body bg-org-alt text-center">
12722                        `;
12723      if (typeof data.link !== "undefined") {
12724        card += `<a href="` + data.link + `" target="_blank">`;
12725      }
12726      card +=
12727        `<div class="d-block">
12728                            <h3 class="mt-0 mb-3">` +
12729        name +
12730        `</h3>
12731                            <img class="monitorrImage" src="` +
12732        data.image +
12733        `" alt="service icon">
12734                        </div>
12735                        <div class="d-inline-block mt-4 py-2 px-4 badge indicator bg-` +
12736        statusColor +
12737        `">
12738                            <p class="mb-0">`;
12739      if (data.status == true) {
12740        card += "ONLINE";
12741      } else if (data.status == "unresponsive") {
12742        card += "UNRESPONSIVE";
12743      } else {
12744        card += "OFFLINE";
12745      }
12746      card += `</p>
12747                        </div>
12748                        `;
12749      if (typeof data.link !== "undefined") {
12750        card += `</a>`;
12751      }
12752      card += `</div>
12753                </div>
12754            </div>
12755            `;
12756    }
12757    return card;
12758  };
12759  for (var key in services) {
12760    cards += buildCard(key, services[key]);
12761  }
12762  return cards;
12763}
12764function buildMonitorr(array) {
12765  if (array === false) {
12766    return "";
12767  }
12768  if (array.error != undefined) {
12769    organizrConsole("Monitorr Function", array.error, "error");
12770  } else {
12771    var services =
12772      typeof array.services !== "undefined"
12773        ? Object.keys(array.services).length
12774        : false;
12775    var html = `
12776        <div id="allMonitorr">
12777            <div class="el-element-overlay row">`;
12778    if (array["options"]["titleToggle"]) {
12779      html +=
12780        `
12781                <div class="col-md-12">
12782                    <h4 class="pull-left homepage-element-title"><span lang="en">` +
12783        array["options"]["title"] +
12784        `</span> : </h4><h4 class="pull-left">&nbsp;<span class="label label-info m-l-20 checkbox-circle good-monitorr-services mouse" onclick="homepageMonitorr()">` +
12785        services +
12786        `</span></h4></h4>
12787                    <hr class="hidden-xs ml-2">
12788                </div>
12789                <div class="clearfix"></div>
12790            `;
12791    }
12792    html +=
12793      `
12794                <div class="monitorrCards">
12795                    ` +
12796      buildMonitorrItem(array) +
12797      `
12798                </div>
12799            </div>
12800        </div>
12801        <div class="clearfix"></div>
12802        `;
12803  }
12804  return array ? html : "";
12805}
12806function homepageMonitorr(timeout) {
12807  var timeout =
12808    typeof timeout !== "undefined"
12809      ? timeout
12810      : activeInfo.settings.homepage.refresh.homepagePiholeRefresh;
12811  organizrAPI2("GET", "api/v2/homepage/monitorr/data")
12812    .success(function (data) {
12813      try {
12814        let response = data.response;
12815        document.getElementById("homepageOrderMonitorr").innerHTML = "";
12816        if (response.data !== null) {
12817          buildMonitorr(response.data);
12818          $("#homepageOrderMonitorr").html(buildMonitorr(response.data));
12819        }
12820      } catch (e) {
12821        organizrCatchError(e, data);
12822      }
12823    })
12824    .fail(function (xhr) {
12825      OrganizrApiError(xhr);
12826    });
12827  let timeoutTitle = "Monitorr-Homepage";
12828  if (typeof timeouts[timeoutTitle] !== "undefined") {
12829    clearTimeout(timeouts[timeoutTitle]);
12830  }
12831  timeouts[timeoutTitle] = setTimeout(function () {
12832    homepageMonitorr(timeout);
12833  }, timeout);
12834  delete timeout;
12835}
12836function buildUptimeKumaItem(array) {
12837  var cards = "";
12838  var options = array["options"];
12839  var services = array["data"];
12840  var tabName = "";
12841
12842  var buildCard = function (name, data) {
12843    if (data.status == true) {
12844      var statusColor = "success";
12845      var imageText = "fa fa-check-circle text-success";
12846    } else {
12847      var statusColor = "danger animated-3 loop-animation flash";
12848      var imageText = "fa fa-times-circle text-danger";
12849    }
12850    tabName = data.name;
12851    kumaLink =
12852      '<a href="javascript:void(0)" onclick="tabActions(event,\'' +
12853      tabName +
12854      "',1)\">";
12855    if (options["compact"]) {
12856      var card =
12857        `
12858            <div class="col-xl-2 col-lg-3 col-md-4 col-sm-6 col-xs-12">
12859                <div class="card bg-inverse text-white mb-3 monitorr-card">
12860                    <div class="card-body bg-org-alt pt-1 pb-1">
12861                        <div class="d-flex no-block align-items-center">
12862                            <div class="left-health bg-` +
12863        statusColor +
12864        `"></div>
12865                            <div class="ml-1 w-100">
12866                                <i class="` +
12867        imageText +
12868        ` font-20 pull-right mt-3 mb-2"></i>
12869                                `;
12870      if (typeof data.url !== "undefined") {
12871        card += kumaLink;
12872      }
12873      card +=
12874        `<h3 class="d-flex no-block align-items-center mt-2 mb-2"><img class="lazyload loginTitle">&nbsp;` +
12875        data.name;
12876      if (data.latency != null && options.showLatency) {
12877        card +=
12878          `<span class="ml-3 font-12 align-self-center text-dark">` +
12879          data.latency +
12880          `ms</span></h3>`;
12881      }
12882      card += `</h3>`;
12883      if (typeof data.url !== "undefined") {
12884        card += `</a>`;
12885      }
12886      card += `<div class="clearfix"></div>
12887                            </div>
12888                        </div>
12889                    </div>
12890                </div>
12891            </div>`;
12892    } else {
12893      var card = `
12894            <div class="col-lg-2 col-md-3 col-sm-4 col-xs-6">
12895                <div class="card bg-inverse text-white mb-3 monitorr-card">
12896                    <div class="card-body bg-org-alt text-center">
12897                        `;
12898      if (typeof data.url !== "undefined") {
12899        card += `<a href="` + data.url + `" target="_blank">`;
12900      }
12901      card +=
12902        `<div class="d-block">
12903                            <h3 class="mt-0 mb-2">` +
12904        data.name +
12905        `</h3>`;
12906
12907      if (data.latency != null && options.showLatency) {
12908        card += `<p class="text-dark mb-0">` + data.latency + `ms</p>`;
12909      }
12910
12911      card +=
12912        `</div>
12913                        <div class="d-inline-block mt-4 py-2 px-4 badge indicator bg-` +
12914        statusColor +
12915        `">
12916                            <p class="mb-0">`;
12917      if (data.status == true) {
12918        card += "ONLINE";
12919      } else {
12920        card += "OFFLINE";
12921      }
12922      card += `</p>
12923                        </div>
12924                        `;
12925      if (typeof data.url !== "undefined") {
12926        card += `</a>`;
12927      }
12928      card += `</div>
12929                </div>
12930            </div>
12931            `;
12932    }
12933    return card;
12934  };
12935  for (var key in services) {
12936    cards += buildCard(key, services[key]);
12937  }
12938  return cards;
12939}
12940function buildUptimeKuma(array) {
12941  if (array === false) {
12942    return "";
12943  }
12944  if (array.error != undefined) {
12945    organizrConsole("Uptime Kuma Function", array.error, "error");
12946  } else {
12947    var html = `
12948        <div id="allUptimeKuma">
12949            <div class="el-element-overlay row">`;
12950    if (array["options"]["titleToggle"]) {
12951      html +=
12952        `
12953                <div class="col-md-12">
12954                    <h4 class="pull-left homepage-element-title"><span lang="en">` +
12955        array["options"]["title"] +
12956        `</span> : </h4>
12957                    <hr class="hidden-xs ml-2">
12958                </div>
12959                <div class="clearfix"></div>
12960            `;
12961    }
12962    html +=
12963      `
12964                <div class="uptimeKumaCards">
12965                    ` +
12966      buildUptimeKumaItem(array) +
12967      `
12968                </div>
12969            </div>
12970        </div>
12971        <div class="clearfix"></div>
12972        `;
12973  }
12974  return array ? html : "";
12975}
12976function homepageUptimeKuma(timeout) {
12977  var timeout =
12978    typeof timeout !== "undefined"
12979      ? timeout
12980      : activeInfo.settings.homepage.refresh.homepageUptimeKumaRefresh;
12981  organizrAPI2("GET", "api/v2/homepage/kuma/data")
12982    .success(function (data) {
12983      try {
12984        let response = data.response;
12985        document.getElementById("homepageOrderUptimeKuma").innerHTML = "";
12986        if (response.data !== null) {
12987          buildUptimeKuma(response.data);
12988          $("#homepageOrderUptimeKuma").html(buildUptimeKuma(response.data));
12989        }
12990      } catch (e) {
12991        console.log(e);
12992        organizrCatchError(e, data);
12993      }
12994    })
12995    .fail(function (xhr) {
12996      OrganizrApiError(xhr);
12997    });
12998  let timeoutTitle = "UptimeKuma-Homepage";
12999  if (typeof timeouts[timeoutTitle] !== "undefined") {
13000    clearTimeout(timeouts[timeoutTitle]);
13001  }
13002  timeouts[timeoutTitle] = setTimeout(function () {
13003    homepageUptimeKuma(timeout);
13004  }, timeout);
13005  delete timeout;
13006}
13007function buildPromPageItem(array) {
13008  var cards = "";
13009  var options = array["options"];
13010  var services = array["data"];
13011  var tabName = "";
13012  console.log(options);
13013
13014  var buildCard = function (name, data) {
13015    if (data.status == true) {
13016      var statusColor = "success";
13017      var imageText = "fa fa-check-circle text-success";
13018    } else {
13019      var statusColor = "danger animated-3 loop-animation flash";
13020      var imageText = "fa fa-times-circle text-danger";
13021    }
13022    tabName = data.name;
13023    if (options["compact"]) {
13024      var card =
13025        `
13026            <div class="col-xl-2 col-lg-3 col-md-4 col-sm-6 col-xs-12">
13027                <div class="card bg-inverse text-white mb-3 monitorr-card">
13028                    <div class="card-body bg-org-alt pt-1 pb-1">
13029                        <div class="d-flex no-block align-items-center">
13030                            <div class="left-health bg-` +
13031        statusColor +
13032        `"></div>
13033                            <div class="ml-1 w-100">
13034                                <i class="` +
13035        imageText +
13036        ` font-20 pull-right mt-3 mb-2"></i>
13037                                `;
13038      card +=
13039        `<h3 class="d-flex no-block align-items-center mt-2 mb-2"><img class="lazyload loginTitle">&nbsp;` +
13040        data.name;
13041      if (data.uptime != null && options.showUptime) {
13042        card +=
13043          `<span class="ml-3 font-12 align-self-center text-dark">` +
13044          Math.round(data.uptime * 100) / 100 +
13045          `%</span></h3>`;
13046      }
13047      card += `</h3>`;
13048      card += `<div class="clearfix"></div>
13049                            </div>
13050                        </div>
13051                    </div>
13052                </div>
13053            </div>`;
13054    } else {
13055      var card = `
13056            <div class="col-lg-2 col-md-3 col-sm-4 col-xs-6">
13057                <div class="card bg-inverse text-white mb-3 monitorr-card">
13058                    <div class="card-body bg-org-alt text-center">
13059                        `;
13060      card +=
13061        `<div class="d-block">
13062                            <h3 class="mt-0 mb-2">` +
13063        data.name +
13064        `</h3>`;
13065
13066      if (data.uptime != null && options.showUptime) {
13067        card +=
13068          `<p class="text-dark mb-0">` +
13069          Math.round(data.uptime * 100) / 100 +
13070          `%</p>`;
13071      }
13072
13073      card +=
13074        `</div>
13075                        <div class="d-inline-block mt-4 py-2 px-4 badge indicator bg-` +
13076        statusColor +
13077        `">
13078                            <p class="mb-0">`;
13079      if (data.status == true) {
13080        card += "UP";
13081      } else {
13082        card += "DOWN";
13083      }
13084      card += `</p>
13085                        </div>
13086                        `;
13087      card += `</div>
13088                </div>
13089            </div>
13090            `;
13091    }
13092    return card;
13093  };
13094  for (var key in services) {
13095    cards += buildCard(key, services[key]);
13096  }
13097  return cards;
13098}
13099function buildPromPage(array) {
13100  if (array === false) {
13101    return "";
13102  }
13103  if (array.error != undefined) {
13104    organizrConsole("PromPage Function", array.error, "error");
13105  } else {
13106    var html = `
13107        <div id="allPromPage">
13108            <div class="el-element-overlay row">`;
13109    if (array["options"]["titleToggle"]) {
13110      html +=
13111        `
13112                <div class="col-md-12">
13113                    <h4 class="pull-left homepage-element-title"><span lang="en">` +
13114        array["options"]["title"] +
13115        `</span> : </h4>
13116                    <hr class="hidden-xs ml-2">
13117                </div>
13118                <div class="clearfix"></div>
13119            `;
13120    }
13121    html +=
13122      `
13123                <div class="promPageCards">
13124                    ` +
13125      buildPromPageItem(array) +
13126      `
13127                </div>
13128            </div>
13129        </div>
13130        <div class="clearfix"></div>
13131        `;
13132  }
13133  return array ? html : "";
13134}
13135function homepagePromPage(timeout) {
13136  var timeout =
13137    typeof timeout !== "undefined"
13138      ? timeout
13139      : activeInfo.settings.homepage.refresh.homepagePromPageRefresh;
13140  organizrAPI2("GET", "api/v2/homepage/prompage/data")
13141    .success(function (data) {
13142      try {
13143        let response = data.response;
13144        document.getElementById("homepageOrderPromPage").innerHTML = "";
13145        if (response.data !== null) {
13146          buildUptimeKuma(response.data);
13147          $("#homepageOrderPromPage").html(buildPromPage(response.data));
13148        }
13149      } catch (e) {
13150        console.log(e);
13151        organizrCatchError(e, data);
13152      }
13153    })
13154    .fail(function (xhr) {
13155      OrganizrApiError(xhr);
13156    });
13157  let timeoutTitle = "PromPage-Homepage";
13158  if (typeof timeouts[timeoutTitle] !== "undefined") {
13159    clearTimeout(timeouts[timeoutTitle]);
13160  }
13161  timeouts[timeoutTitle] = setTimeout(function () {
13162    homepagePromPage(timeout);
13163  }, timeout);
13164  delete timeout;
13165}
13166function homepageSpeedtest(timeout) {
13167  var timeout =
13168    typeof timeout !== "undefined"
13169      ? timeout
13170      : activeInfo.settings.homepage.refresh.homepageSpeedtestRefresh;
13171  organizrAPI2("GET", "api/v2/homepage/speedtest/data")
13172    .success(function (data) {
13173      try {
13174        let response = data.response;
13175        document.getElementById("homepageOrderSpeedtest").innerHTML = "";
13176        if (response.data !== null) {
13177          $("#homepageOrderSpeedtest").html(buildSpeedtest(response.data));
13178        }
13179      } catch (e) {
13180        organizrCatchError(e, data);
13181      }
13182    })
13183    .fail(function (xhr) {
13184      OrganizrApiError(xhr);
13185    });
13186  let timeoutTitle = "Speedtest-Homepage";
13187  if (typeof timeouts[timeoutTitle] !== "undefined") {
13188    clearTimeout(timeouts[timeoutTitle]);
13189  }
13190  timeouts[timeoutTitle] = setTimeout(function () {
13191    homepageSpeedtest(timeout);
13192  }, timeout);
13193  delete timeout;
13194}
13195function buildSpeedtest(array) {
13196  if (array === false) {
13197    return "";
13198  }
13199  var html = `
13200    <style>
13201    .shadow-sm {
13202        -webkit-box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.075) !important;
13203        box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.075) !important;
13204    }
13205    .speedtest-card {
13206        background-color: #2d2c2c;
13207    }
13208    .speedtest-card .text-success {
13209        color: #07db71 !important;
13210    }
13211    .speedtest-card .text-warning {
13212        color: #fca503 !important;
13213    }
13214    .speedtest-card .text-primary {
13215        color: #3e95cd !important;
13216    }
13217    .speedtest-card span.icon {
13218        font-size: 2em;
13219    }
13220    .speedtest-card h5 {
13221    }
13222
13223    .speedtest-card h4,
13224    .speedtest-card h3 {
13225        font-weight: 450;
13226        line-height: 1.2;
13227    }
13228
13229    .speedtest-card .text-muted,
13230    .speedtest-card h5 {
13231        color: #9e9e9e !important;
13232    }
13233    </style>
13234    `;
13235  var current = array.data.current;
13236  var average = array.data.average;
13237  var maximum = array.data.maximum;
13238  var minimum = array.data.minimum;
13239  var options = array.options;
13240
13241  html += `
13242    <div id="allSpeedtest">
13243    `;
13244  if (options.titleToggle) {
13245    html +=
13246      `
13247        <div class="row">
13248            <div class="col-sm-12">
13249                <h4 class="pull-left homepage-element-title"><span lang="en">` +
13250      array["options"]["title"] +
13251      ` : </h4>
13252            </div>
13253        </div>
13254        `;
13255  }
13256  html +=
13257    `
13258        <div class="row">
13259            <div class="my-2 col-lg-4 col-md-4 col-sm-12">
13260                <div class="card speedtest-card shadow-sm mb-3">
13261                    <div class="card-body">
13262                        <div class="d-flex align-items-center justify-content-between">
13263                            <h4>Ping</h4>
13264                            <span class="ti-pulse icon text-success" />
13265                        </div>
13266                        <div class="text-truncate">
13267                            <h3 class="d-inline">` +
13268    parseFloat(current.ping).toFixed(1) +
13269    `</h3>
13270                            <p class="d-inline ml-1 text-white">ms (current)</p>
13271                        </div>`;
13272  if (average != undefined) {
13273    html +=
13274      `
13275                        <div class="text-truncate text-muted">
13276                            <h5 class="d-inline">` +
13277      parseFloat(average.ping).toFixed(1) +
13278      `</h5>
13279                            <p class="d-inline ml-1">ms (average)</p>
13280                        </div>
13281        `;
13282  }
13283  if (maximum != undefined) {
13284    html +=
13285      `
13286                        <div class="text-truncate text-muted">
13287                            <h5 class="d-inline">` +
13288      parseFloat(maximum.ping).toFixed(1) +
13289      `</h5>
13290                            <p class="d-inline ml-1">ms (maximum)</p>
13291                        </div>
13292        `;
13293  }
13294  if (minimum != undefined) {
13295    html +=
13296      `
13297                        <div class="text-truncate text-muted">
13298                            <h5 class="d-inline">` +
13299      parseFloat(minimum.ping).toFixed(1) +
13300      `</h5>
13301                            <p class="d-inline ml-1">ms (minimum)</p>
13302                        </div>
13303        `;
13304  }
13305  html +=
13306    `       </div>
13307                </div>
13308            </div>
13309            <div class="my-2 col-lg-4 col-md-4 col-sm-12">
13310                <div class="card speedtest-card shadow-sm mb-3">
13311                    <div class="card-body">
13312                        <div class="d-flex align-items-center justify-content-between">
13313                            <h4>Download</h4>
13314                            <span class="ti-download icon text-warning" />
13315                        </div>
13316                        <div class="text-truncate">
13317                            <h3 class="d-inline">` +
13318    parseFloat(current.download).toFixed(1) +
13319    `</h3>
13320                            <p class="d-inline ml-1 text-white">Mbit/s (current)</p>
13321                        </div>`;
13322  if (average != undefined) {
13323    html +=
13324      `
13325                        <div class="text-truncate text-muted">
13326                            <h5 class="d-inline">` +
13327      parseFloat(average.download).toFixed(1) +
13328      `</h5>
13329                            <p class="d-inline ml-1">Mbit/s (average)</p>
13330                        </div>
13331            `;
13332  }
13333  if (maximum != undefined) {
13334    html +=
13335      `
13336                        <div class="text-truncate text-muted">
13337                            <h5 class="d-inline">` +
13338      parseFloat(maximum.download).toFixed(1) +
13339      `</h5>
13340                            <p class="d-inline ml-1">Mbit/s (maximum)</p>
13341                        </div>
13342        `;
13343  }
13344  if (minimum != undefined) {
13345    html +=
13346      `
13347                        <div class="text-truncate text-muted">
13348                            <h5 class="d-inline">` +
13349      parseFloat(minimum.download).toFixed(1) +
13350      `</h5>
13351                            <p class="d-inline ml-1">Mbit/s (minimum)</p>
13352                        </div>
13353        `;
13354  }
13355  html +=
13356    `       </div>
13357                </div>
13358            </div>
13359            <div class="my-2 col-lg-4 col-md-4 col-sm-12">
13360                <div class="card speedtest-card shadow-sm mb-3">
13361                    <div class="card-body">
13362                        <div class="d-flex align-items-center justify-content-between">
13363                            <h4>Upload</h4>
13364                            <span class="ti-upload icon text-primary" />
13365                        </div>
13366                        <div class="text-truncate">
13367                            <h3 class="d-inline">` +
13368    parseFloat(current.upload).toFixed(1) +
13369    `</h3>
13370                            <p class="d-inline ml-1 text-white">Mbit/s (current)</p>
13371                        </div>`;
13372  if (average != undefined) {
13373    html +=
13374      `
13375                        <div class="text-truncate text-muted">
13376                            <h5 class="d-inline">` +
13377      parseFloat(average.upload).toFixed(1) +
13378      `</h5>
13379                            <p class="d-inline ml-1">Mbit/s (average)</p>
13380                        </div>
13381            `;
13382  }
13383  if (maximum != undefined) {
13384    html +=
13385      `
13386                        <div class="text-truncate text-muted">
13387                            <h5 class="d-inline">` +
13388      parseFloat(maximum.upload).toFixed(1) +
13389      `</h5>
13390                            <p class="d-inline ml-1">Mbit/s (maximum)</p>
13391                        </div>
13392        `;
13393  }
13394  if (minimum != undefined) {
13395    html +=
13396      `
13397                        <div class="text-truncate text-muted">
13398                            <h5 class="d-inline">` +
13399      parseFloat(minimum.upload).toFixed(1) +
13400      `</h5>
13401                            <p class="d-inline ml-1">Mbit/s (minimum)</p>
13402                        </div>
13403        `;
13404  }
13405  html += `       </div>
13406                </div>
13407            </div>
13408        </div>
13409    </div>
13410    `;
13411
13412  return array ? html : "";
13413}
13414function buildNetdataItem(array) {
13415  var html = `
13416    <style>
13417    .all-netdata .easyPieChart-value {
13418        position: absolute;
13419        top: 77px;
13420        width: 100%;
13421        text-align: center;
13422        left: 0;
13423        font-size: 24.4625px;
13424        font-weight: normal;
13425    }
13426    .all-netdata .easyPieChart-title {
13427        position: absolute;
13428        width: 100%;
13429        text-align: center;
13430        left: 0;
13431        font-weight: bold;
13432    }
13433    .all-netdata .easyPieChart-units {
13434        position: absolute;
13435        top: 118px;
13436        width: 100%;
13437        text-align: center;
13438        left: 0;
13439        font-size: 15px;
13440        font-weight: normal;
13441    }
13442
13443    .all-netdata .gauge-chart .gauge-value {
13444        position: relative;
13445        width: 100%;
13446        text-align: center;
13447        top: 30px;
13448        color: #dcdcdc;
13449        font-weight: bold;
13450        left: 0;
13451        font-size: 26px;
13452    }
13453
13454    .all-netdata .gauge-chart .gauge-title {
13455        position: relative;
13456        width: 100%;
13457        text-align: center;
13458        top: -10px;
13459        //color: #fff;
13460        font-weight: bold;
13461        left: 0;
13462        font-size: 15px;
13463    }
13464
13465    .all-netdata .chart-lg .gauge-chart .gauge-value {
13466        top: 70px;
13467        font-size: 26px;
13468    }
13469
13470    .all-netdata .chart-lg .gauge-chart .gauge-title {
13471        top: 45px;
13472        font-size: 15px;
13473    }
13474
13475    .all-netdata .chart-md .gauge-chart .gauge-value {
13476        top: 65px;
13477        font-size: 26px;
13478    }
13479
13480    .all-netdata .chart-md .gauge-chart .gauge-title {
13481        top: 45px;
13482        font-size: 15px;
13483    }
13484
13485    .all-netdata .chart-sm .gauge-chart .gauge-value {
13486        top: 65px;
13487        font-size: 26px;
13488    }
13489
13490    .all-netdata .chart-sm .gauge-chart .gauge-title {
13491        top: 45px;
13492        font-size: 15px;
13493    }
13494
13495    .all-netdata .chart-lg,
13496    .all-netdata .chart-md,
13497    .all-netdata .chart-sm {
13498        display: inline-block;
13499        margin: 15px;
13500    }
13501
13502    .all-netdata .chart-lg,
13503    .all-netdata .chart-lg .chart {
13504        height: 180px;
13505        width: 180px;
13506    }
13507
13508    .all-netdata .chart-md,
13509    .all-netdata .chart-md .chart {
13510        height: 160px;
13511        width: 160px;
13512    }
13513
13514    .all-netdata .chart-sm,
13515    .all-netdata .chart-sm .chart {
13516        height: 140px;
13517        width: 140px;
13518    }
13519
13520    .all-netdata .chart-lg .gauge-chart,
13521    .all-netdata .gauge-cont.chart-lg {
13522        //height: 300px;
13523        width: 300px;
13524    }
13525
13526    .all-netdata .chart-md .gauge-chart,
13527    .all-netdata .gauge-cont.chart-md {
13528        //height: 275px;
13529        width: 275px;
13530    }
13531
13532    .all-netdata .chart-sm .gauge-chart,
13533    .all-netdata .gauge-cont.chart-sm {
13534        //height: 250px;
13535        width: 250px;
13536    }
13537
13538    .all-netdata .chart-lg .easyPieChart-title {
13539        top: 37px;
13540        font-size: 15px;
13541    }
13542
13543    .all-netdata .chart-md .easyPieChart-title {
13544        top: 33px;
13545        font-size: 13.5px;
13546    }
13547
13548    .all-netdata .chart-sm .easyPieChart-title {
13549        top: 30px;
13550        font-size: 12px;
13551    }
13552
13553    .all-netdata .chart-lg .easyPieChart-value {
13554        top: 75px;
13555        font-size: 24.4625px;
13556    }
13557
13558    .all-netdata .chart-md .easyPieChart-value {
13559        top: 65px;
13560        font-size: 24.4625px;
13561    }
13562
13563    .all-netdata .chart-sm .easyPieChart-value {
13564        top: 55px;
13565        font-size: 24.4625px;
13566    }
13567
13568    .all-netdata .chart-lg .easyPieChart-units {
13569        top: 130px;
13570        font-size: 15px;
13571    }
13572
13573    .all-netdata .chart-md .easyPieChart-units {
13574        top: 108px;
13575        font-size: 15px;
13576    }
13577
13578    .all-netdata .chart-sm .easyPieChart-units {
13579        top: 95px;
13580        font-size: 15px;
13581    }
13582    </style>
13583    `;
13584
13585  var buildEasyPieChart = function (e, i, size, easySize, display) {
13586    return (
13587      `
13588        <div class="chart-` +
13589      size +
13590      ` my-3 text-center ` +
13591      display +
13592      `">
13593            <div class="chart" id="easyPieChart` +
13594      (i + 1) +
13595      `" data-percent="` +
13596      e.percent +
13597      `">
13598                <span class="easyPieChart-title">` +
13599      e.title +
13600      `</span>
13601                <span class="easyPieChart-value" id="easyPieChart` +
13602      (i + 1) +
13603      `Value">` +
13604      parseFloat(e.value).toFixed(1) +
13605      `</span>
13606                <span class="easyPieChart-units" id="easyPieChart` +
13607      (i + 1) +
13608      `Units">` +
13609      e.units +
13610      `</span>
13611            </div>
13612        </div>
13613        <script>
13614        $(function() {
13615            var opts = {
13616                size: ` +
13617      easySize +
13618      `,
13619                lineWidth: 7,
13620                scaleColor: false,
13621                barColor: '#` +
13622      e.colour +
13623      `',
13624                trackColor: '#636363',
13625            };
13626            if(` +
13627      e.percent +
13628      ` == 0) {
13629                opts.lineCap = 'butt';
13630            }
13631            $('#easyPieChart` +
13632      (i + 1) +
13633      `').easyPieChart(opts);
13634        });
13635        </script>
13636        `
13637    );
13638  };
13639
13640  var buildGaugeChart = function (e, i, size, easySize, display) {
13641    switch (size) {
13642      case "lg":
13643        easySize = 300;
13644        break;
13645      case "sm":
13646        easySize = 275;
13647        break;
13648      case "md":
13649      default:
13650        easySize = 250;
13651        break;
13652    }
13653    return (
13654      `
13655        <div class="mx-0 gauge-cont chart-` +
13656      size +
13657      ` my-3 text-center ` +
13658      display +
13659      `">
13660            <div class="gauge-chart text-center">
13661                <span class="gauge-title d-block" id="gaugeChart` +
13662      (i + 1) +
13663      `Title">` +
13664      e.title +
13665      `</span>
13666                <span class="gauge-value d-block" id="gaugeChart` +
13667      (i + 1) +
13668      `Value">` +
13669      parseFloat(e.value).toFixed(1) +
13670      `</span>
13671                <canvas id="gaugeChart` +
13672      (i + 1) +
13673      `" style="width: 100%"></canvas>
13674            </div>
13675        </div>
13676        <script>
13677        $(function() {
13678            var opts = {
13679                angle: 0.14, // The span of the gauge arc
13680                lineWidth: 0.54, // The line thickness
13681                radiusScale: 1, // Relative radius
13682                pointer: {
13683                    length: 0.77, // // Relative to gauge radius
13684                    strokeWidth: 0.075, // The thickness
13685                    color: '#A1A1A1' // Fill color
13686                },
13687                limitMax: false,     // If false, max value increases automatically if value > maxValue
13688                limitMin: false,     // If true, the min value of the gauge will be fixed
13689                colorStart: '#` +
13690      e.colour +
13691      `',   // Colors
13692                colorStop: '#` +
13693      e.colour +
13694      `',    // just experiment with them
13695                strokeColor: '#636363',  // to see which ones work best for you
13696                generateGradient: true,
13697                highDpiSupport: true,     // High resolution support
13698            
13699            };
13700            var target = document.getElementById('gaugeChart` +
13701      (i + 1) +
13702      `'); // your canvas element
13703            var gauge = new Gauge(target).setOptions(opts); // create sexy gauge!
13704            gauge.maxValue = ` +
13705      e.max +
13706      `; // set max gauge value
13707            gauge.setMinValue(0);  // Prefer setter over gauge.minValue = 0
13708            gauge.animationSpeed = 8; // set animation speed (32 is default value)
13709            gauge.set(` +
13710      e.percent +
13711      `); // set actual value
13712            window.netdata[` +
13713      (i + 1) +
13714      `] = gauge
13715        });
13716        </script>
13717        `
13718    );
13719  };
13720
13721  array.forEach((e, i) => {
13722    var size = e.size;
13723    var easySize;
13724    if (size == "") {
13725      size = "md";
13726    }
13727    switch (size) {
13728      case "lg":
13729        easySize = 180;
13730        break;
13731      case "sm":
13732        easySize = 140;
13733        break;
13734      case "md":
13735      default:
13736        easySize = 160;
13737        break;
13738    }
13739
13740    var display = " ";
13741    if (e.lg) {
13742      display += " d-xl-inline-block d-lg-inline-block";
13743    } else {
13744      display += " d-xl-none d-lg-none d-none";
13745    }
13746    if (e.md) {
13747      display += " d-md-inline-block";
13748    } else {
13749      display += " d-md-none d-none";
13750    }
13751    if (e.sm) {
13752      display += " d-sm-inline-block d-xs-inline-block";
13753    } else {
13754      display += " d-sm-none d-xs-none d-none";
13755    }
13756    display += " ";
13757
13758    if (e.error) {
13759      organizrConsole(
13760        "Netdata Function",
13761        "(Chart " + (i + 1) + "): " + e.error,
13762        "error"
13763      );
13764    } else if (e.chart == "easypiechart") {
13765      html += buildEasyPieChart(e, i, size, easySize, display);
13766    } else if (e.chart == "gauge") {
13767      html += buildGaugeChart(e, i, size, easySize, display);
13768    }
13769  });
13770
13771  return html;
13772}
13773function buildNetdata(array) {
13774  var data = array.data;
13775  if (array === false) {
13776    return "";
13777  }
13778  window.netdata = [];
13779
13780  var html = `
13781    <style>
13782    .clearfix {
13783        *zoom: 1;
13784      }
13785      .all-netdata .clearfix:before,
13786      .all-netdata .clearfix:after {
13787        display: table;
13788        content: "";
13789      }
13790      .all-netdata .clearfix:after {
13791        clear: both;
13792      }
13793      
13794      .all-netdata .easyPieChart {
13795          position: relative;
13796          text-align: center;
13797      }
13798      
13799      .all-netdata .easyPieChart canvas {
13800          position: absolute;
13801          top: 0;
13802          left: 0;
13803      }
13804      
13805      .all-netdata .chart {
13806          float: left;
13807          //margin: 10px;
13808      }
13809      
13810      .all-netdata .percentage,
13811      .all-netdata .label {
13812          text-align: center;
13813          color: #333;
13814          font-weight: 100;
13815          font-size: 1.2em;
13816          margin-bottom: 0.3em;
13817      }
13818      
13819      .all-netdata .credits {
13820          padding-top: 0.5em;
13821          clear: both;
13822          color: #999;
13823      }
13824      
13825      .all-netdata .credits a {
13826          color: #333;
13827      }
13828      
13829      .all-netdata .dark {
13830          background: #333;
13831      }
13832      
13833      .all-netdata .dark .percentage-light,
13834      .all-netdata .dark .label {
13835          text-align: center;
13836          color: #999;
13837          font-weight: 100;
13838          font-size: 1.2em;
13839          margin-bottom: 0.3em;
13840      }
13841      
13842      
13843      .all-netdata .button {
13844        -webkit-box-shadow: inset 0 0 1px #000, inset 0 1px 0 1px rgba(255,255,255,0.2), 0 1px 1px -1px rgba(0, 0, 0, .5);
13845        -moz-box-shadow: inset 0 0 1px #000, inset 0 1px 0 1px rgba(255,255,255,0.2), 0 1px 1px -1px rgba(0, 0, 0, .5);
13846        box-shadow: inset 0 0 1px #000, inset 0 1px 0 1px rgba(255,255,255,0.2), 0 1px 1px -1px rgba(0, 0, 0, .5);
13847        -webkit-border-radius: 3px;
13848        -moz-border-radius: 3px;
13849        border-radius: 3px;
13850        padding: 6px 20px;
13851        font-weight: bold;
13852        text-transform: uppercase;
13853        display: block;
13854        margin: 0 auto 2em;
13855        max-width: 200px;
13856        text-align: center;
13857        background-color: #5c5c5c;
13858        background-image: -moz-linear-gradient(top, #666666, #4d4d4d);
13859        background-image: -ms-linear-gradient(top, #666666, #4d4d4d);
13860        background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#666666), to(#4d4d4d));
13861        background-image: -webkit-linear-gradient(top, #666666, #4d4d4d);
13862        background-image: -o-linear-gradient(top, #666666, #4d4d4d);
13863        background-image: linear-gradient(top, #666666, #4d4d4d);
13864        background-repeat: repeat-x;
13865        filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#666666', endColorstr='#4d4d4d', GradientType=0);
13866        color: #ffffff;
13867        text-shadow: 0 1px 1px #333333;
13868      }
13869      .all-netdata .button:hover {
13870        color: #ffffff;
13871        text-decoration: none;
13872        background-color: #616161;
13873        background-image: -moz-linear-gradient(top, #6b6b6b, #525252);
13874        background-image: -ms-linear-gradient(top, #6b6b6b, #525252);
13875        background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#6b6b6b), to(#525252));
13876        background-image: -webkit-linear-gradient(top, #6b6b6b, #525252);
13877        background-image: -o-linear-gradient(top, #6b6b6b, #525252);
13878        background-image: linear-gradient(top, #6b6b6b, #525252);
13879        background-repeat: repeat-x;
13880        filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#6b6b6b', endColorstr='#525252', GradientType=0);
13881      }
13882      .all-netdata .button:active {
13883        background-color: #575757;
13884        background-image: -moz-linear-gradient(top, #616161, #474747);
13885        background-image: -ms-linear-gradient(top, #616161, #474747);
13886        background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#616161), to(#474747));
13887        background-image: -webkit-linear-gradient(top, #616161, #474747);
13888        background-image: -o-linear-gradient(top, #616161, #474747);
13889        background-image: linear-gradient(top, #616161, #474747);
13890        background-repeat: repeat-x;
13891        filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#616161', endColorstr='#474747', GradientType=0);
13892        -webkit-transform: translate(0, 1px);
13893        -moz-transform: translate(0, 1px);
13894        -ms-transform: translate(0, 1px);
13895        -o-transform: translate(0, 1px);
13896        transform: translate(0, 1px);
13897      }
13898      .all-netdata .button:disabled {
13899        background-color: #dddddd;
13900        background-image: -moz-linear-gradient(top, #e7e7e7, #cdcdcd);
13901        background-image: -ms-linear-gradient(top, #e7e7e7, #cdcdcd);
13902        background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#e7e7e7), to(#cdcdcd));
13903        background-image: -webkit-linear-gradient(top, #e7e7e7, #cdcdcd);
13904        background-image: -o-linear-gradient(top, #e7e7e7, #cdcdcd);
13905        background-image: linear-gradient(top, #e7e7e7, #cdcdcd);
13906        background-repeat: repeat-x;
13907        filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#e7e7e7', endColorstr='#cdcdcd', GradientType=0);
13908        color: #939393;
13909        text-shadow: 0 1px 1px #fff;
13910      }
13911    </style>
13912    `;
13913
13914  html += `
13915    <div class="row m-b-30">
13916        
13917            <div class="d-block text-center all-netdata">
13918    `;
13919  html += buildNetdataItem(data);
13920  html += `
13921            </div>
13922        
13923    </div>`;
13924
13925  return array ? html : "";
13926}
13927function homepageNetdata(timeout) {
13928  var timeout =
13929    typeof timeout !== "undefined"
13930      ? timeout
13931      : activeInfo.settings.homepage.refresh.homepageNetdataRefresh;
13932  organizrAPI2("GET", "api/v2/homepage/netdata/data")
13933    .success(function (data) {
13934      try {
13935        let response = data.response;
13936        if (!tryUpdateNetdata(response.data.data)) {
13937          document.getElementById("homepageOrderNetdata").innerHTML = "";
13938          if (response.data !== null) {
13939            $("#homepageOrderNetdata").html(buildNetdata(response.data));
13940          }
13941        }
13942      } catch (e) {
13943        organizrCatchError(e, data);
13944      }
13945    })
13946    .fail(function (xhr) {
13947      OrganizrApiError(xhr);
13948    });
13949  var timeoutTitle = "Netdata-Homepage";
13950  if (typeof timeouts[timeoutTitle] !== "undefined") {
13951    clearTimeout(timeouts[timeoutTitle]);
13952  }
13953  timeouts[timeoutTitle] = setTimeout(function () {
13954    homepageNetdata(timeout);
13955  }, timeout);
13956  delete timeout;
13957}
13958function tryUpdateNetdata(array) {
13959  var existing = false;
13960  array.forEach((e, i) => {
13961    var id = i + 1;
13962    if (e.chart == "easypiechart") {
13963      if ($("#easyPieChart" + id).length) {
13964        $("#easyPieChart" + id)
13965          .data("easyPieChart")
13966          .update(e.percent);
13967        $("#easyPieChart" + id + "Value").html(parseFloat(e.value).toFixed(1));
13968        existing = true;
13969      }
13970    } else if (e.chart == "gauge") {
13971      if (window.netdata) {
13972        if (window.netdata[i + 1]) {
13973          window.netdata[i + 1].set(e.percent); // set actual value
13974          $("#gaugeChart" + (i + 1) + "Value").html(
13975            parseFloat(e.value).toFixed(1)
13976          );
13977          existing = true;
13978        }
13979      } else {
13980        existing = false;
13981      }
13982    } else {
13983      existing = false;
13984    }
13985  });
13986  return existing;
13987}
13988function homepageJackett() {
13989  if (activeInfo.settings.homepage.options.alternateHomepageHeaders) {
13990    var header = `
13991		<div class="col-md-12">
13992			<h2 class="text-white m-0 pull-left text-uppercase"><img class="lazyload homepageImageTitle" data-src="plugins/images/tabs/jackett.png"> &nbsp; <span lang="en">Jackett</span>&nbsp;</h2>
13993			<hr class="hidden-xs"><div class="clearfix"></div>
13994		</div>
13995		<div class="clearfix"></div>
13996		<script>$('.jackett-panel').removeClass('panel panel-default');</script>
13997		`;
13998  } else {
13999    var header = `
14000		<div class="panel-heading bg-info p-t-10 p-b-10">
14001			<span class="pull-left m-t-5 text-white"><img class="lazyload homepageImageTitle" data-src="plugins/images/tabs/jackett.png" > &nbsp; <span lang="en">Jackett</span></span>
14002			<div class="clearfix"></div>
14003		</div>
14004		`;
14005  }
14006  let html =
14007    `
14008	<div id="jackettSearch" class="row">
14009		<div class="col-lg-12">
14010			<div class="jackett-panel panel panel-default">
14011				` +
14012    header +
14013    `
14014				<div class="panel-wrapper p-b-0 collapse in">
14015					<div class="white-box">
14016	                    <h3 class="box-title m-b-0" lang="en">Search</h3>
14017	                    
14018	                    <form onsubmit="searchJackett();return false;">
14019	                        <div class="input-group m-b-30">
14020	                        	<span class="input-group-btn hidden">
14021									<button type="button" class="btn waves-effect waves-light btn-primary clearJackett" onclick="clearJackett();"><i class="fa fa-eraser"></i></button>
14022								</span>
14023	                            <input id="jackett-search-query" class="form-control" placeholder="Search for..." lang="en">
14024	                            <span class="input-group-btn">
14025									<button type="submit" class="btn waves-effect waves-light btn-info"><i class="fa fa-search"></i></button>
14026								</span>
14027	                        </div>
14028	
14029	                    </form>
14030	                    
14031	                    <div class="jackettDataTable hidden">
14032        					<h3 class="box-title m-b-0" lang="en">Results</h3>
14033					        <div class="table-responsive">
14034					            <table id="jackettDataTable" class="table table-striped">
14035					                <thead>
14036					                    <tr>
14037					                        <th lang="en">Date</th>
14038					                        <th lang="en">Tracker</th>
14039					                        <th lang="en">Name</th>
14040					                        <th lang="en">Size</th>
14041					                        <th lang="en">Files</th>
14042					                        <th lang="en">Grabs</th>
14043					                        <th lang="en">Seeds</th>
14044					                        <th lang="en">Leechers</th>
14045					                        <th lang="en">Download</th>
14046					                    </tr>
14047					                </thead>
14048					                <tbody></tbody>
14049					            </table>
14050					        </div>
14051    					</div>
14052	                </div>
14053					
14054				</div>
14055			</div>
14056		</div>
14057	</div>
14058	`;
14059  $("#homepageOrderJackett").html(html);
14060}
14061function clearJackett() {
14062  $("#jackett-search-query").val("");
14063  $(".clearJackett").parent().addClass("hidden");
14064  $("#jackettDataTable").DataTable().destroy();
14065  $(".jackettDataTable").addClass("hidden");
14066}
14067function searchJackett() {
14068  let query = $("#jackett-search-query").val();
14069  if (query !== "") {
14070    $(".jackettDataTable").removeClass("hidden");
14071    //ajaxloader('#jackettSearch .panel-wrapper', 'in');
14072    ajaxblocker(".jackett-panel .white-box", "in", "Searching...");
14073  } else {
14074    return false;
14075  }
14076  $.fn.dataTable.ext.errMode = "none";
14077  $("#jackettDataTable").DataTable().destroy();
14078  let preferBlackholeDownload =
14079    activeInfo.settings.homepage.jackett.homepageJackettBackholeDownload;
14080  let jackettTable = $("#jackettDataTable")
14081    .on("error.dt", function (e, settings, techNote, message) {
14082      console.log("An error has been reported by DataTables: ", message);
14083      $("#jackettDataTable").DataTable().destroy();
14084      ajaxblocker(".jackett-panel .white-box");
14085      $(".clearJackett").parent().removeClass("hidden");
14086      window.message(
14087        "Jackett Connection Error",
14088        "",
14089        activeInfo.settings.notifications.position,
14090        "#FFF",
14091        "error",
14092        "5000"
14093      );
14094    })
14095    .DataTable({
14096      ajax: {
14097        url: "api/v2/homepage/jackett/" + query,
14098        dataSrc: function (json) {
14099          return json.response.data.content.Results;
14100        },
14101      },
14102      columns: [
14103        {
14104          data: "PublishDate",
14105          render: function (data, type, row) {
14106            if (type === "display" || type === "filter") {
14107              var m = moment.tz(data, activeInfo.timezone);
14108              return moment.utc(m, "YYYY-MM-DD hh:mm[Z]").local().fromNow();
14109            }
14110            return data;
14111          },
14112        },
14113        { data: "Tracker" },
14114        {
14115          data: "Title",
14116          render: function (data, type, row) {
14117            if (row.Details !== null) {
14118              return (
14119                '<a href="' + row.Details + '" target="_blank">' + data + "</a>"
14120              );
14121            } else {
14122              return data;
14123            }
14124          },
14125        },
14126        {
14127          data: "Size",
14128          render: function (data, type, row) {
14129            if (type === "display" || type === "filter") {
14130              return humanFileSize(data, false);
14131            }
14132            return humanFileSize(data, false);
14133          },
14134        },
14135        { data: "Files" },
14136        { data: "Grabs" },
14137        { data: "Seeders" },
14138        { data: "Peers" },
14139        {
14140          data: "MagnetUri",
14141          render: function (data, type, row) {
14142            if (type === "display" || type === "filter") {
14143              if (
14144                preferBlackholeDownload === true &&
14145                row.BlackholeLink !== null
14146              ) {
14147                return (
14148                  "<a onclick=\"jackettDownload('" +
14149                  row.BlackholeLink +
14150                  '\');return false;" href="#"><i class="fa fa-cloud-download"></i></a>'
14151                );
14152              } else if (data !== null) {
14153                return (
14154                  '<a href="' +
14155                  data +
14156                  '" target="_blank"><i class="fa fa-magnet"></i></a>'
14157                );
14158              } else if (row.Details !== null) {
14159                return (
14160                  '<a href="' +
14161                  row.Details +
14162                  '" target="_blank"><i class="fa fa-cloud-download"></i></a>'
14163                );
14164              } else if (row.Guid !== null) {
14165                return (
14166                  '<a href="' +
14167                  row.Guid +
14168                  '" target="_blank"><i class="fa fa-cloud-download"></i></a>'
14169                );
14170              } else if (row.Link !== null) {
14171                return (
14172                  '<a href="' +
14173                  row.Link +
14174                  '" target="_blank"><i class="fa fa-download"></i></a>'
14175                );
14176              } else {
14177                return "No Download Link";
14178              }
14179            }
14180            return data;
14181          },
14182        },
14183      ],
14184      order: [[0, "desc"]],
14185      initComplete: function (settings, json) {
14186        //ajaxloader();
14187        ajaxblocker(".jackett-panel .white-box");
14188        $(".clearJackett").parent().removeClass("hidden");
14189      },
14190    });
14191}
14192function jackettDownload(url) {
14193  let blackholeLink = url.substring(url.indexOf("/bh/"));
14194  var post = {
14195    url: blackholeLink,
14196  };
14197  organizrAPI2("POST", "api/v2/homepage/jackett/download/", post, true)
14198    .success(function () {
14199      message(
14200        "Torrent downloaded",
14201        "",
14202        activeInfo.settings.notifications.position,
14203        "#FFF",
14204        "success",
14205        "5000"
14206      );
14207    })
14208    .fail(function (xhr) {
14209      OrganizrApiError(xhr, "Error downloading torrent");
14210    });
14211}
14212function homepageProwlarr() {
14213  if (activeInfo.settings.homepage.options.alternateHomepageHeaders) {
14214    var header = `
14215		<div class="col-md-12">
14216			<h2 class="text-white m-0 pull-left text-uppercase"><img class="lazyload homepageImageTitle" data-src="plugins/images/tabs/prowlarr.png"> &nbsp; <span lang="en">Prowlarr</span>&nbsp;</h2>
14217			<hr class="hidden-xs"><div class="clearfix"></div>
14218		</div>
14219		<div class="clearfix"></div>
14220		<script>$('.prowlarr-panel').removeClass('panel panel-default');</script>
14221		`;
14222  } else {
14223    var header = `
14224		<div class="panel-heading bg-info p-t-10 p-b-10">
14225			<span class="pull-left m-t-5 text-white"><img class="lazyload homepageImageTitle" data-src="plugins/images/tabs/prowlarr.png" > &nbsp; <span lang="en">Prowlarr</span></span>
14226			<div class="clearfix"></div>
14227		</div>
14228		`;
14229  }
14230  let html =
14231    `
14232	<div id="prowlarrSearch" class="row">
14233		<div class="col-lg-12">
14234			<div class="prowlarr-panel panel panel-default">
14235				` +
14236    header +
14237    `
14238				<div class="panel-wrapper p-b-0 collapse in">
14239					<div class="white-box">
14240	                    <h3 class="box-title m-b-0" lang="en">Search</h3>
14241	                    
14242	                    <form onsubmit="searchProwlarr();return false;">
14243	                        <div class="input-group m-b-30">
14244	                        	<span class="input-group-btn hidden">
14245									<button type="button" class="btn waves-effect waves-light btn-primary clearProwlarr" onclick="clearProwlarr();"><i class="fa fa-eraser"></i></button>
14246								</span>
14247	                            <input id="prowlarr-search-query" class="form-control" placeholder="Search for..." lang="en">
14248	                            <span class="input-group-btn">
14249									<button type="submit" class="btn waves-effect waves-light btn-info"><i class="fa fa-search"></i></button>
14250								</span>
14251	                        </div>
14252	
14253	                    </form>
14254	                    
14255	                    <div class="prowlarrDataTable hidden">
14256        					<h3 class="box-title m-b-0" lang="en">Results</h3>
14257					        <div class="table-responsive">
14258					            <table id="prowlarrDataTable" class="table table-striped">
14259					                <thead>
14260					                    <tr>
14261					                        <th lang="en">Date</th>
14262					                        <th lang="en">Indexer</th>
14263					                        <th lang="en">Title</th>
14264					                        <th lang="en">Size</th>
14265					                        <th lang="en">Grabs</th>
14266					                        <th lang="en">Seeders</th>
14267					                        <th lang="en">Leechers</th>
14268					                        <th lang="en">Download</th>
14269					                    </tr>
14270					                </thead>
14271					                <tbody></tbody>
14272					            </table>
14273					        </div>
14274    					</div>
14275	                </div>
14276					
14277				</div>
14278			</div>
14279		</div>
14280	</div>
14281	`;
14282  $("#homepageOrderProwlarr").html(html);
14283}
14284function clearProwlarr() {
14285  $("#prowlarr-search-query").val("");
14286  $(".clearProwlarr").parent().addClass("hidden");
14287  $("#prowlarrDataTable").DataTable().destroy();
14288  $(".prowlarrDataTable").addClass("hidden");
14289}
14290function searchProwlarr() {
14291  let query = $("#prowlarr-search-query").val();
14292  if (query !== "") {
14293    $(".prowlarrDataTable").removeClass("hidden");
14294    ajaxloader("#prowlarrSearch .panel-wrapper", "in");
14295  } else {
14296    return false;
14297  }
14298  $.fn.dataTable.ext.errMode = "none";
14299  $("#prowlarrDataTable").DataTable().destroy();
14300  let preferBlackholeDownload =
14301    activeInfo.settings.homepage.prowlarr.homepageProwlarrBackholeDownload;
14302  let prowlarrTable = $("#prowlarrDataTable")
14303    .on("error.dt", function (e, settings, techNote, message) {
14304      console.log("An error has been reported by DataTables: ", message);
14305    })
14306    .DataTable({
14307      ajax: {
14308        url: "api/v2/homepage/prowlarr/" + query,
14309        dataSrc: function (json) {
14310          return json.response.data.content;
14311        },
14312      },
14313      columns: [
14314        {
14315          data: "publishDate",
14316          render: function (data, type, row) {
14317            if (type === "display" || type === "filter") {
14318              var m = moment.tz(data, activeInfo.timezone);
14319              return moment.utc(m, "YYYY-MM-DD hh:mm[Z]").local().fromNow();
14320            }
14321            return data;
14322          },
14323        },
14324        { data: "indexer" },
14325        {
14326          data: "title",
14327          render: function (data, type, row) {
14328            if (row.Details !== null) {
14329              return (
14330                '<a href="' +
14331                row["infoUrl"] +
14332                '" target="_blank">' +
14333                data +
14334                "</a>"
14335              );
14336            } else {
14337              return data;
14338            }
14339          },
14340        },
14341        {
14342          data: "size",
14343          render: function (data) {
14344            return humanFileSize(data, false);
14345          },
14346        },
14347        { data: "grabs" },
14348        { data: "seeders" },
14349        { data: "leechers" },
14350        {
14351          data: "downloadUrl",
14352          render: function (data, type, row) {
14353            if (type === "display" || type === "filter") {
14354              if (data !== null) {
14355                if (preferBlackholeDownload === true && row.guid !== null) {
14356                  return (
14357                    "<a onclick=\"prowlarrDownload('" +
14358                    row.guid +
14359                    "," +
14360                    row.indexerId +
14361                    '\');return false;" href="#"><i class="fa fa-cloud-download"></i></a>'
14362                  );
14363                } else {
14364                  return (
14365                    '<a href="' +
14366                    data +
14367                    '" target="_blank"><i class="fa fa-download"></i></a>'
14368                  );
14369                }
14370              } else {
14371                return "No Download Link";
14372              }
14373            }
14374            return data;
14375          },
14376        },
14377      ],
14378      order: [[5, "desc"]],
14379      initComplete: function (settings, json) {
14380        ajaxloader();
14381        //ajaxblocker('.prowlarr-panel .white-box');
14382        $(".clearProwlarr").parent().removeClass("hidden");
14383      },
14384    });
14385}
14386function prowlarrDownload(url) {
14387  const args = url.split(",");
14388  var post = {
14389    guid: args[0],
14390    indexerId: args[1],
14391  };
14392  organizrAPI2("POST", "api/v2/homepage/prowlarr/download/", post, true)
14393    .success(function () {
14394      message(
14395        "Torrent downloaded",
14396        "",
14397        activeInfo.settings.notifications.position,
14398        "#FFF",
14399        "success",
14400        "5000"
14401      );
14402    })
14403    .fail(function (xhr) {
14404      OrganizrApiError(xhr, "Error downloading torrent");
14405    });
14406}
14407function homepageOctoprint(timeout) {
14408  var timeout =
14409    typeof timeout !== "undefined"
14410      ? timeout
14411      : activeInfo.settings.homepage.refresh.homepageOctoprintRefresh;
14412  organizrAPI2("GET", "api/v2/homepage/octoprint/data")
14413    .success(function (data) {
14414      try {
14415        let response = data.response;
14416        document.getElementById("homepageOrderOctoprint").innerHTML = "";
14417        if (response.data !== null) {
14418          $("#homepageOrderOctoprint").html(buildOctoprint(response.data));
14419        }
14420      } catch (e) {
14421        organizrCatchError(e, data);
14422      }
14423    })
14424    .fail(function (xhr) {
14425      OrganizrApiError(xhr);
14426    });
14427  let timeoutTitle = "Octoprint-Homepage";
14428  if (typeof timeouts[timeoutTitle] !== "undefined") {
14429    clearTimeout(timeouts[timeoutTitle]);
14430  }
14431  timeouts[timeoutTitle] = setTimeout(function () {
14432    homepageOctoprint(timeout);
14433  }, timeout);
14434  delete timeout;
14435}
14436function buildOctoprint(array) {
14437  var menu = `<ul class="nav customtab nav-tabs pull-right" role="tablist">`;
14438  var headerAlt = "";
14439  var header = "";
14440  var content = "";
14441  var webcamUrl = "";
14442  var webcamHtml = "";
14443  var css = `
14444	<style>
14445	.octoprint-webcam {
14446		max-height: 400px;
14447		max-width: 100%;
14448		float: right;
14449	}
14450	.octoprint-block {
14451		margin-left: 0px;
14452		margin-right: 0px;
14453	}
14454	.octoprint-button-spacer {
14455		padding-right: 46px;
14456	}
14457	</style>
14458	`;
14459  menu += `
14460		<li role="presentation" class="active" ><a href="" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="true" onclick="homepageOctoprint();"><span class="visible-xs"><i class="ti-download"></i></span><span class="hidden-xs">REFRESH</span></a></li>
14461		`;
14462  menu += "</ul>";
14463  if (activeInfo.settings.homepage.options.alternateHomepageHeaders) {
14464    var headerAlt =
14465      `
14466		<div class="col-md-12">
14467			<h2 class="text-white m-0 pull-left text-uppercase"><img class="lazyload homepageImageTitle" data-src="plugins/images/tabs/octoprint.png">  &nbsp; </h2>
14468			` +
14469      menu +
14470      `
14471			<hr class="hidden-xs"><div class="clearfix"></div>
14472		</div>
14473		<div class="clearfix"></div>
14474		`;
14475  } else {
14476    var header =
14477      `
14478		<div class="white-box bg-info m-b-0 p-b-0 p-t-10 mailbox-widget">
14479			<h2 class="text-white m-0 pull-left text-uppercase"><img class="lazyload homepageImageTitle" data-src="plugins/images/tabs/octoprint.png">  &nbsp; </h2>
14480			` +
14481      menu +
14482      `
14483			<div class="clearfix"></div>
14484		</div>
14485		`;
14486  }
14487  content = "<p>State: " + array.data.job.state + "</p>";
14488  if (array.data.job.state == "Printing") {
14489    content += "<p>File: " + array.data.job.job.file.display + "</p>";
14490    content +=
14491      "<p>Progress: " +
14492      parseFloat(array.data.job.progress.completion).toFixed(0) +
14493      "%</p>";
14494    content +=
14495      "<p>Approx. Total Print Time: " +
14496      octoprintFormatTime(array.data.job.job.estimatedPrintTime) +
14497      "</p>";
14498    content +=
14499      "<p>Print Time Left: " +
14500      octoprintFormatTime(array.data.job.progress.printTimeLeft) +
14501      "</p>";
14502  }
14503  if (array.data.settings.webcam.webcamEnabled) {
14504    webcamUrl = array.data.settings.webcam.streamUrl;
14505    if (webcamUrl[0] == "/") {
14506      webcamUrl = array.data.url + webcamUrl;
14507    }
14508  }
14509  if (webcamUrl) {
14510    var webcamHtml =
14511      `<div class="col-lg-4"><img class="octoprint-webcam" src="` +
14512      webcamUrl +
14513      `"></div>`;
14514  }
14515  return (
14516    css +
14517    `
14518	<div class="row">
14519		` +
14520    headerAlt +
14521    `
14522		<div class="col-lg-12">
14523			` +
14524    header +
14525    `
14526			<div class="row octoprint-block white-box">
14527				<div class="col-lg-8 text-white">
14528						<div class="tab-content m-t-0">` +
14529    content +
14530    `</div>
14531				</div>
14532				` +
14533    webcamHtml +
14534    `
14535			</div>
14536		</div>
14537	</div>
14538	`
14539  );
14540}
14541function octoprintFormatTime(seconds) {
14542  var format = "";
14543  var days = Math.floor(moment.duration(seconds, "seconds").asDays());
14544  var hours = Math.floor(moment.duration(seconds, "seconds").asHours());
14545  var minutes = moment.duration(seconds, "seconds").minutes();
14546  var seconds = moment.duration(seconds, "seconds").seconds();
14547  if (days > 0) {
14548    format += days + " " + octoprintPluralize("day", days) + " ";
14549  }
14550  if (hours > 0) {
14551    format += hours + " " + octoprintPluralize("hour", hours) + " ";
14552  }
14553  if (minutes > 0) {
14554    format += minutes + " " + octoprintPluralize("minute", minutes) + " ";
14555  }
14556  if (seconds > 0) {
14557    format += seconds + " " + octoprintPluralize("second", seconds) + " ";
14558  }
14559  return format;
14560}
14561
14562function octoprintPluralize(s, n) {
14563  if (n > 1) {
14564    return s + "s";
14565  }
14566  return s;
14567}
14568function pad(n, width, z) {
14569  z = z || "0";
14570  n = n + "";
14571  return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
14572}
14573// Thanks Swifty!
14574function PopupCenter(url, title, w, h) {
14575  // Fixes dual-screen position                         Most browsers      Firefox
14576  var dualScreenLeft =
14577    window.screenLeft != undefined ? window.screenLeft : window.screenX;
14578  var dualScreenTop =
14579    window.screenTop != undefined ? window.screenTop : window.screenY;
14580  var width = window.innerWidth
14581    ? window.innerWidth
14582    : document.documentElement.clientWidth
14583    ? document.documentElement.clientWidth
14584    : screen.width;
14585  var height = window.innerHeight
14586    ? window.innerHeight
14587    : document.documentElement.clientHeight
14588    ? document.documentElement.clientHeight
14589    : screen.height;
14590  var left = width / 2 - w / 2 + dualScreenLeft;
14591  var top = height / 2 - h / 2 + dualScreenTop;
14592  var newWindow = window.open(
14593    url,
14594    title,
14595    "scrollbars=yes, width=" +
14596      w +
14597      ", height=" +
14598      h +
14599      ", top=" +
14600      top +
14601      ", left=" +
14602      left
14603  );
14604  // Puts focus on the newWindow
14605  if (window.focus) {
14606    newWindow.focus();
14607  }
14608  return newWindow;
14609}
14610function getPlexHeaders() {
14611  let plexTitle =
14612    activeInfo.appearance.title == ""
14613      ? "Organizr"
14614      : cleanClass(activeInfo.appearance.title);
14615  return {
14616    Accept: "application/json",
14617    "X-Plex-Product": plexTitle,
14618    "X-Plex-Version": "2.0",
14619    "X-Plex-Client-Identifier": activeInfo.settings.misc.uuid,
14620    "X-Plex-Model": "Plex OAuth",
14621    "X-Plex-Platform": activeInfo.osName,
14622    "X-Plex-Platform-Version": activeInfo.osVersion,
14623    "X-Plex-Device": activeInfo.browserName,
14624    "X-Plex-Device-Name": activeInfo.browserVersion,
14625    "X-Plex-Device-Screen-Resolution":
14626      window.screen.width + "x" + window.screen.height,
14627    "X-Plex-Language": "en",
14628  };
14629}
14630var plex_oauth_window = null;
14631const plex_oauth_loader =
14632  "<style>" +
14633  ".login-loader-container {" +
14634  'font-family: "Open Sans", Arial, sans-serif;' +
14635  "position: absolute;" +
14636  "top: 0;" +
14637  "right: 0;" +
14638  "bottom: 0;" +
14639  "left: 0;" +
14640  "}" +
14641  ".login-loader-message {" +
14642  "color: #282A2D;" +
14643  "text-align: center;" +
14644  "position: absolute;" +
14645  "left: 50%;" +
14646  "top: 25%;" +
14647  "transform: translate(-50%, -50%);" +
14648  "}" +
14649  ".login-loader {" +
14650  "border: 5px solid #ccc;" +
14651  "-webkit-animation: spin 1s linear infinite;" +
14652  "animation: spin 1s linear infinite;" +
14653  "border-top: 5px solid #282A2D;" +
14654  "border-radius: 50%;" +
14655  "width: 50px;" +
14656  "height: 50px;" +
14657  "position: relative;" +
14658  "left: calc(50% - 25px);" +
14659  "}" +
14660  "@keyframes spin {" +
14661  "0% { transform: rotate(0deg); }" +
14662  "100% { transform: rotate(360deg); }" +
14663  "}" +
14664  "</style>" +
14665  '<div class="login-loader-container">' +
14666  '<div class="login-loader-message">' +
14667  '<div class="login-loader"></div>' +
14668  "<br>" +
14669  "Redirecting to the login page..." +
14670  "</div>" +
14671  "</div>";
14672function closePlexOAuthWindow() {
14673  if (plex_oauth_window) {
14674    plex_oauth_window.close();
14675  }
14676}
14677getPlexOAuthPin = function () {
14678  var x_plex_headers = getPlexHeaders();
14679  var deferred = $.Deferred();
14680  $.ajax({
14681    url: "https://plex.tv/api/v2/pins?strong=true",
14682    type: "POST",
14683    headers: x_plex_headers,
14684    success: function (data) {
14685      deferred.resolve({ pin: data.id, code: data.code });
14686    },
14687    error: function () {
14688      closePlexOAuthWindow();
14689      deferred.reject();
14690    },
14691  });
14692  return deferred;
14693};
14694var polling = null;
14695function PlexOAuth(
14696  successCallback,
14697  errorCallback,
14698  maxRetryCallback,
14699  pollingCallback,
14700  preFunction,
14701  clientID = null
14702) {
14703  if (typeof preFunction === "function") {
14704    preFunction();
14705  }
14706  closePlexOAuthWindow();
14707  plex_oauth_window = PopupCenter("", "Plex-OAuth", 600, 700);
14708  $(plex_oauth_window.document.body).html(plex_oauth_loader);
14709  getPlexOAuthPin().then(
14710    function (data) {
14711      var x_plex_headers = getPlexHeaders();
14712      const pin = data.pin;
14713      const code = data.code;
14714      var oauth_params = {
14715        clientID: x_plex_headers["X-Plex-Client-Identifier"],
14716        "context[device][product]": x_plex_headers["X-Plex-Product"],
14717        "context[device][version]": x_plex_headers["X-Plex-Version"],
14718        "context[device][platform]": x_plex_headers["X-Plex-Platform"],
14719        "context[device][platformVersion]":
14720          x_plex_headers["X-Plex-Platform-Version"],
14721        "context[device][device]": x_plex_headers["X-Plex-Device"],
14722        "context[device][deviceName]": x_plex_headers["X-Plex-Device-Name"],
14723        "context[device][model]": x_plex_headers["X-Plex-Model"],
14724        "context[device][screenResolution]":
14725          x_plex_headers["X-Plex-Device-Screen-Resolution"],
14726        "context[device][layout]": "desktop",
14727        code: code,
14728      };
14729      plex_oauth_window.location =
14730        "https://app.plex.tv/auth/#!?" + encodeData(oauth_params);
14731      polling = pin;
14732      let maxPollCount = 120;
14733      (function poll() {
14734        maxPollCount--;
14735        $.ajax({
14736          url: "https://plex.tv/api/v2/pins/" + pin,
14737          type: "GET",
14738          headers: x_plex_headers,
14739          success: function (data) {
14740            if (data.authToken) {
14741              polling = null;
14742              closePlexOAuthWindow();
14743              if (typeof successCallback === "function") {
14744                successCallback("plex", data.authToken, clientID);
14745              }
14746            }
14747          },
14748          complete: function () {
14749            if (maxPollCount <= 0) {
14750              closePlexOAuthWindow();
14751              if (typeof maxRetryCallback === "function") {
14752                maxRetryCallback();
14753              }
14754            } else if (polling === pin) {
14755              setTimeout(function () {
14756                poll();
14757              }, 1000);
14758              if (typeof pollingCallback === "function") {
14759                pollingCallback(maxPollCount);
14760              }
14761            }
14762          },
14763          timeout: 1000,
14764        });
14765      })();
14766    },
14767    function () {
14768      closePlexOAuthWindow();
14769      if (typeof errorCallback === "function") {
14770        errorCallback();
14771      }
14772    }
14773  );
14774}
14775function openOAuth(provider) {
14776  // will actually fix this later
14777  closePlexOAuthWindow();
14778  plex_oauth_window = PopupCenter("", "OAuth", 600, 700);
14779  $(plex_oauth_window.document.body).html(plex_oauth_loader);
14780  plex_oauth_window.location = "api/v2/oauth/trakt";
14781}
14782function encodeData(data) {
14783  return Object.keys(data)
14784    .map(function (key) {
14785      return [key, data[key]].map(encodeURIComponent).join("=");
14786    })
14787    .join("&");
14788}
14789function oAuthSuccess(type, token, id = null) {
14790  switch (type) {
14791    case "plex":
14792      if (id) {
14793        $(id).val(token);
14794        $(id).change();
14795        messageSingle(
14796          "",
14797          window.lang.translate("Grabbed Token - Please Save"),
14798          activeInfo.settings.notifications.position,
14799          "#FFF",
14800          "success",
14801          "5000"
14802        );
14803      } else {
14804        $("#oAuth-Input").val(token);
14805        $("#oAuthType-Input").val(type);
14806        $("#login-username-Input").addClass("hidden");
14807        $("#login-password-Input").addClass("hidden");
14808        $("#oAuth-div").removeClass("hidden");
14809        $(".login-button").first().trigger("click");
14810      }
14811      break;
14812    default:
14813      break;
14814  }
14815}
14816function oAuthError() {
14817  messageSingle(
14818    "",
14819    window.lang.translate("Error Connecting to oAuth Provider"),
14820    activeInfo.settings.notifications.position,
14821    "#FFF",
14822    "error",
14823    "5000"
14824  );
14825}
14826function oAuthMaxRetry() {
14827  messageSingle(
14828    "",
14829    window.lang.translate("Max Retry Error Connecting to oAuth Provider"),
14830    activeInfo.settings.notifications.position,
14831    "#FFF",
14832    "error",
14833    "5000"
14834  );
14835}
14836function oAuthStart(type) {
14837  switch (type) {
14838    case "plex":
14839      PlexOAuth(oAuthSuccess, oAuthError, oAuthMaxRetry, null, null);
14840      break;
14841    default:
14842      break;
14843  }
14844}
14845function oidcStart(provider) {
14846  // Redirect in same window to OIDC provider
14847  window.location.href =
14848    "api/v2/oidc/" + encodeURIComponent(provider) + "/authorize";
14849}
14850function testOIDCConnection(provider) {
14851  organizrAPI2(
14852    "GET",
14853    "api/v2/oidc/" + encodeURIComponent(provider) + "/test",
14854    ""
14855  )
14856    .success(function (data) {
14857      if (data.response.result === "success") {
14858        messageSingle(
14859          "OIDC Test",
14860          data.response.message || "Connection successful",
14861          activeInfo.settings.notifications.position,
14862          "#FFF",
14863          "success",
14864          "5000"
14865        );
14866      } else {
14867        messageSingle(
14868          "OIDC Test",
14869          data.response.message || "Connection failed",
14870          activeInfo.settings.notifications.position,
14871          "#FFF",
14872          "error",
14873          "5000"
14874        );
14875      }
14876    })
14877    .fail(function (xhr) {
14878      var msg = "Connection failed";
14879      try {
14880        var resp = JSON.parse(xhr.responseText);
14881        msg = resp.response.message || msg;
14882      } catch (e) {}
14883      messageSingle(
14884        "OIDC Test",
14885        msg,
14886        activeInfo.settings.notifications.position,
14887        "#FFF",
14888        "error",
14889        "5000"
14890      );
14891    });
14892}
14893function clearAJAX(id = "all") {
14894  if (id == "all") {
14895    $.each(timeouts, function (i, v) {
14896      clearTimeout(timeouts[i]);
14897    });
14898  } else if (id == "homepage") {
14899    $.each(timeouts, function (i, v) {
14900      if (i.indexOf("-Homepage") > 0) {
14901        clearTimeout(timeouts[i]);
14902      }
14903    });
14904  } else {
14905    clearTimeout(timeouts[id]);
14906  }
14907}
14908//Generate API
14909function generateCode() {
14910  var code = "";
14911  var possible = "abcdefghijklmnopqrstuvwxyz0123456789";
14912  for (var i = 0; i < 20; i++)
14913    code += possible.charAt(Math.floor(Math.random() * possible.length));
14914  return code;
14915}
14916// uppercase word
14917function toUpper(str) {
14918  return str
14919    .toLowerCase()
14920    .split(" ")
14921    .map(function (word) {
14922      return word[0].toUpperCase() + word.substr(1);
14923    })
14924    .join(" ");
14925}
14926// human filesize
14927function humanFileSize(bytes, si) {
14928  var thresh = si ? 1000 : 1024;
14929  if (Math.abs(bytes) < thresh) {
14930    return bytes + " B";
14931  }
14932  var units = si
14933    ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
14934    : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
14935  var u = -1;
14936  do {
14937    bytes /= thresh;
14938    ++u;
14939  } while (Math.abs(bytes) >= thresh && u < units.length - 1);
14940  return bytes.toFixed(1) + " " + units[u];
14941}
14942//youtube search
14943function youtubeSearch(searchQuery) {
14944  return $.ajax({
14945    url: "api/v2/homepage/youtube/" + searchQuery,
14946  });
14947}
14948function youtubeCheck(title, link) {
14949  youtubeSearch(title)
14950    .success(function (data) {
14951      var response = data.response;
14952      if (response.data) {
14953        inlineLoad();
14954        var id = response.data.items["0"].id.videoId;
14955        var div =
14956          `
14957		<div id="player-` +
14958          link +
14959          `" data-plyr-provider="youtube" data-plyr-embed-id="` +
14960          id +
14961          `"
14962		></div>
14963		<div class="clearfix"></div>
14964		`;
14965        $(".youtube-div").html(div);
14966        $("." + link).trigger("click");
14967        player = new Plyr("#player-" + link);
14968      }
14969    })
14970    .fail(function (xhr) {
14971      OrganizrApiError(xhr, "YouTube API Error");
14972      // Fallback: open YouTube search in a new tab/window
14973      var q = "";
14974      try {
14975        q = decodeURIComponent(title);
14976      } catch (e1) {
14977        try {
14978          q = unescape(title);
14979        } catch (e2) {
14980          q = title;
14981        }
14982      }
14983      var url =
14984        "https://www.youtube.com/results?search_query=" +
14985        encodeURIComponent(q + " trailer");
14986      window.open(url, "_blank");
14987    });
14988}
14989function requestSearch(title, page = 1) {
14990  return $.ajax({
14991    url:
14992      "https://api.themoviedb.org/3/search/multi?api_key=83cf4ee97bb728eeaf9d4a54e64356a1&language=" +
14993      activeInfo.language +
14994      "&query=" +
14995      title +
14996      "&page=" +
14997      page +
14998      "&include_adult=false",
14999  });
15000}
15001function requestSearchList(list, page = 1) {
15002  var url = "";
15003  switch (list) {
15004    case "top-movie":
15005      url =
15006        "https://api.themoviedb.org/3/movie/top_rated?api_key=83cf4ee97bb728eeaf9d4a54e64356a1&language=" +
15007        activeInfo.language +
15008        "&region=US&page=" +
15009        page;
15010      break;
15011    case "pop-movie":
15012      url =
15013        "https://api.themoviedb.org/3/movie/popular?api_key=83cf4ee97bb728eeaf9d4a54e64356a1&language=" +
15014        activeInfo.language +
15015        "&region=US&page=" +
15016        page;
15017      break;
15018    case "up-movie":
15019      url =
15020        "https://api.themoviedb.org/3/movie/upcoming?api_key=83cf4ee97bb728eeaf9d4a54e64356a1&language=" +
15021        activeInfo.language +
15022        "&region=US&page=" +
15023        page;
15024      break;
15025    case "theatre-movie":
15026      url =
15027        "https://api.themoviedb.org/3/movie/now_playing?api_key=83cf4ee97bb728eeaf9d4a54e64356a1&language=" +
15028        activeInfo.language +
15029        "&region=US&page=" +
15030        page;
15031      break;
15032    case "top-tv":
15033      url =
15034        "https://api.themoviedb.org/3/tv/top_rated?api_key=83cf4ee97bb728eeaf9d4a54e64356a1&language=" +
15035        activeInfo.language +
15036        "&region=US&page=" +
15037        page;
15038      break;
15039    case "pop-tv":
15040      url =
15041        "https://api.themoviedb.org/3/tv/popular?api_key=83cf4ee97bb728eeaf9d4a54e64356a1&language=" +
15042        activeInfo.language +
15043        "&region=US&page=" +
15044        page;
15045      break;
15046    case "today-tv":
15047      url =
15048        "https://api.themoviedb.org/3/tv/airing_today?api_key=83cf4ee97bb728eeaf9d4a54e64356a1&language=" +
15049        activeInfo.language +
15050        "&region=US&page=" +
15051        page;
15052      break;
15053    case "org-mod":
15054      url =
15055        "https://api.themoviedb.org/4/list/64438?api_key=83cf4ee97bb728eeaf9d4a54e64356a1&language=" +
15056        activeInfo.language +
15057        "&page=" +
15058        page;
15059      break;
15060    default:
15061  }
15062  return $.ajax({
15063    url: url,
15064  });
15065}
15066function requestNewID(id) {
15067  return $.ajax({
15068    url:
15069      "https://api.themoviedb.org/3/tv/" +
15070      id +
15071      "/external_ids?api_key=83cf4ee97bb728eeaf9d4a54e64356a1&language=en-US",
15072  });
15073}
15074function getTmdbImages(id, type) {
15075  return $.ajax({
15076    url: `https://api.themoviedb.org/3/${type}/${id}/images?api_key=83cf4ee97bb728eeaf9d4a54e64356a1`,
15077  });
15078}
15079function inlineLoad() {
15080  $(".inline-popups").magnificPopup({
15081    removalDelay: 500, //delay removal by X to allow out-animation
15082    closeOnBgClick: true,
15083    //closeOnContentClick: true,
15084    callbacks: {
15085      beforeOpen: function () {
15086        this.st.mainClass = this.st.el.attr("data-effect");
15087        this.st.focus = "#request-input";
15088      },
15089      close: function () {
15090        if (typeof player !== "undefined") {
15091          player.destroy();
15092        }
15093      },
15094    },
15095    midClick: true, // allow opening popup on middle mouse click. Always set it to true if you don't provide alternative source.
15096  });
15097}
15098//Import Users
15099function importUsers(type) {
15100  $(".importUsersButton").attr("disabled", true);
15101  messageSingle(
15102    "",
15103    window.lang.translate("Importing Users"),
15104    activeInfo.settings.notifications.position,
15105    "#FFF",
15106    "success",
15107    "5000"
15108  );
15109  organizrAPI2("POST", "api/v2/users/import/" + type, { type: type })
15110    .success(function (data) {
15111      try {
15112        var response = data.response;
15113        message(
15114          "User Import",
15115          response.message,
15116          activeInfo.settings.notifications.position,
15117          "#FFF",
15118          "success",
15119          "5000"
15120        );
15121        $(".importUsersButton").attr("disabled", false);
15122      } catch (e) {
15123        organizrCatchError(e, data);
15124      }
15125    })
15126    .fail(function (xhr) {
15127      OrganizrApiError(xhr, "Import Error");
15128    });
15129}
15130//Settings change auth
15131function changeAuth() {
15132  var type = $("#authSelect").val();
15133  var service = $("#authBackendSelect").val();
15134  switch (service) {
15135    case "plex":
15136      $(".switchAuth").parent().parent().parent().hide();
15137      $(".backendAuth").parent().parent().parent().show();
15138      $(".plexAuth").parent().parent().parent().show();
15139      break;
15140    case "emby_local":
15141    case "emby_connect":
15142    case "emby_all":
15143      $(".switchAuth").parent().parent().parent().hide();
15144      $(".backendAuth").parent().parent().parent().show();
15145      $(".embyAuth").parent().parent().parent().show();
15146      break;
15147    case "jellyfin":
15148      $(".switchAuth").parent().parent().parent().hide();
15149      $(".backendAuth").parent().parent().parent().show();
15150      $(".jellyfinAuth").parent().parent().parent().show();
15151      break;
15152    case "ftp":
15153      $(".switchAuth").parent().parent().parent().hide();
15154      $(".backendAuth").parent().parent().parent().show();
15155      $(".ftpAuth").parent().parent().parent().show();
15156      break;
15157    case "ldap":
15158      $(".switchAuth").parent().parent().parent().hide();
15159      $(".backendAuth").parent().parent().parent().show();
15160      $(".ldapAuth").parent().parent().parent().show();
15161      break;
15162    default:
15163      $(".switchAuth").parent().parent().parent().hide();
15164      $(".backendAuth").parent().parent().parent().show();
15165  }
15166  if (type == "internal") {
15167    $(".switchAuth").parent().parent().parent().hide();
15168  }
15169}
15170function organizrSpecialSettings(array) {
15171  //media search
15172  if (
15173    array.settings.homepage.search.enabled == true &&
15174    typeof array.settings.homepage.search.type !== "undefined"
15175  ) {
15176    var htmlDOM = `
15177		<li class=""><a class="waves-effect waves-light inline-popups" href="#mediaSearch-area" data-effect="mfp-zoom-out"> <i class="ti-search"></i></a></li>
15178		`;
15179    var searchBoxResults =
15180      `
15181		<div id="mediaSearch-area" class="white-popup mfp-with-anim mfp-hide">
15182			<div class="col-md-8 col-md-offset-2">
15183				<div class="white-box m-b-0 resultBox-outside">
15184					<div class="form-group m-b-0">
15185
15186							<input id="mediaSearchQuery" data-server="` +
15187      array.settings.homepage.search.type +
15188      `" lang="en" placeholder="Search My Media" type="text" class="form-control inline-focus">
15189
15190						<div class="clearfix"></div>
15191					</div>
15192					<div class="row el-element-overlay mediaSearch-div resultBox-inside"></div>
15193				</div>
15194			</div>
15195		</div>
15196		`;
15197    $(htmlDOM).prependTo(".navbar-right");
15198    $(searchBoxResults).appendTo($(".organizr-area"));
15199  }
15200}
15201function checkLocalForwardStatus(array) {
15202  if (
15203    array.settings.login.enableLocalAddressForward == true &&
15204    typeof array.settings.login.enableLocalAddressForward !== "undefined"
15205  ) {
15206    if (
15207      array.settings.login.wanDomain !== "" &&
15208      array.settings.login.localAddress !== ""
15209    ) {
15210      organizrConsole("Organizr Function", "Local Login Enabled");
15211      organizrConsole("Organizr Function", "Local Login Testing...");
15212      let remoteSite = array.settings.login.wanDomain;
15213      let localSite = array.settings.login.localAddress;
15214      try {
15215        let currentURL = decodeURI(window.location.href);
15216        let currentSite = window.location.host;
15217        if (
15218          activeInfo.settings.user.local &&
15219          currentSite.indexOf(remoteSite) !== -1 &&
15220          currentURL.indexOf("override") === -1
15221        ) {
15222          organizrConsole(
15223            "Organizr Function",
15224            "Local Login Status: Local | Forwarding Now"
15225          );
15226          window.location = localSite;
15227        } else {
15228          organizrConsole(
15229            "Organizr Function",
15230            "Local Login Status: Not Local or Override was set - Ignoring Forward Request"
15231          );
15232        }
15233      } catch (e) {
15234        console.error(e);
15235      }
15236    }
15237  }
15238}
15239function forceSearch(term) {
15240  $.magnificPopup.close();
15241  let tabInfo = findTab("api/v2/page/homepage", "access_url");
15242  if (tabInformation[tabInfo.id]["loaded"]) {
15243    if (tabInformation[tabInfo.id]["active"]) {
15244      setTimeout(function () {
15245        $("#newRequestButton").trigger("click");
15246        $("#request-input").val(term);
15247        doneTyping();
15248      }, 500);
15249    } else {
15250      tabActions("click", tabInfo.id);
15251      setTimeout(function () {
15252        $("#newRequestButton").trigger("click");
15253        $("#request-input").val(term);
15254        doneTyping();
15255      }, 1000);
15256    }
15257  } else {
15258    tabActions("click", tabInfo.id);
15259    setTimeout(function () {
15260      $("#newRequestButton").trigger("click");
15261      $("#request-input").val(term);
15262      doneTyping();
15263    }, 3000);
15264  }
15265}
15266function splitPoster(str) {
15267  var words = str.split(" ");
15268  var newWord = "";
15269  $.each(words, function (i, v) {
15270    newWord += v + "<br/>";
15271  });
15272  return newWord;
15273}
15274function buildMediaResults(array, source, term) {
15275  if (array.content.length == 0) {
15276    var none =
15277      '<h2 class="text-center" lang="en">No Results for:</h2><h3 class="text-center" lang="en">' +
15278      term +
15279      "</h3>";
15280    none +=
15281      activeInfo.settings.homepage.ombi.enabled == true ||
15282      activeInfo.settings.homepage.overseerr.enabled == true
15283        ? `<button onclick="forceSearch('` +
15284          term +
15285          `')" class="btn btn-block btn-info" lang="en">Would you like to Request it?</button>`
15286        : "";
15287    return none;
15288  }
15289  var results = "";
15290  var tv = 0;
15291  var movie = 0;
15292  var music = 0;
15293  var total = 0;
15294  $.each(array.content, function (i, v) {
15295    total = total + 1;
15296    tv = v.type == "tv" ? tv + 1 : tv;
15297    movie = v.type == "movie" ? movie + 1 : movie;
15298    music = v.type == "music" ? music + 1 : music;
15299    var bg = v.imageURL;
15300    var top = v.title;
15301    var bottom = v.metadata.originallyAvailableAt;
15302    results +=
15303      `
15304        <div id="` +
15305      v.uid +
15306      `-metadata-div" class="white-popup mfp-with-anim mfp-hide">
15307            <div class="col-md-8 col-md-offset-2 ` +
15308      v.uid +
15309      `-metadata-info"></div>
15310        </div>
15311        <a class="inline-popups ` +
15312      v.uid +
15313      ` hidden" href="#` +
15314      v.uid +
15315      `-metadata-div" data-effect="mfp-zoom-out"></a>
15316
15317        <div class="col-lg-3 col-md-4 col-sm-6 col-xs-12 m-t-20 request-result-item request-result-` +
15318      v.type +
15319      ` metadata-get mouse" data-source="` +
15320      source +
15321      `" data-key="` +
15322      v.metadataKey +
15323      `" data-uid="` +
15324      v.uid +
15325      `">
15326            <div class="white-box m-b-10">
15327                <div class="el-card-item p-b-0">
15328                    <div class="el-card-avatar el-overlay-1 m-b-5"> <img class="lazyload resultImages" data-src="` +
15329      bg +
15330      `"></div>
15331                    <div class="el-card-content bg-org">
15332                        <h3 class="box-title elip">` +
15333      top +
15334      `</h3> <small>` +
15335      bottom +
15336      `</small>
15337                        <br>
15338                    </div>
15339                </div>
15340            </div>
15341        </div>
15342        `;
15343  });
15344  //requests setup?
15345  if (
15346    activeInfo.settings.homepage.ombi.enabled == true ||
15347    activeInfo.settings.homepage.overseerr.enabled == true
15348  ) {
15349    results +=
15350      `
15351		<div class="col-lg-3 col-md-4 col-sm-6 col-xs-12 m-t-20 request-result-item request-result-movie mouse"  onclick="forceSearch('` +
15352      term +
15353      `')">
15354			<div class="white-box m-b-10">
15355				<div class="el-card-item p-b-0">
15356					<div class="el-card-avatar el-overlay-1 m-b-5"> <img class="lazyload resultImages mouse" data-src="plugins/images/homepage/no-request.png">
15357						<div class="customPoster">
15358							<a href="javascript:void(0);">` +
15359      splitPoster(term) +
15360      `</a>
15361						</div>
15362					</div>
15363					<div class="el-card-content bg-org">
15364						<h3 class="box-title elip">` +
15365      term +
15366      `</h3> <small lang="en">Request Me!</small>
15367						<br>
15368					</div>
15369				</div>
15370			</div>
15371		</div>
15372		`;
15373  }
15374  var buttons =
15375    `
15376    <div class="button-box p-20 text-center p-b-0">
15377        <button class="btn btn-inverse waves-effect waves-light filter-request-result" data-filter="request-result-all"><span>` +
15378    total +
15379    `</span> <i class="fa fa-th-large m-l-5 fa-fw"></i></button>
15380        <button class="btn btn-primary waves-effect waves-light filter-request-result" data-filter="request-result-movie"><span>` +
15381    movie +
15382    `</span> <i class="fa fa-film m-l-5 fa-fw"></i></button>
15383        <button class="btn btn-info waves-effect waves-light filter-request-result" data-filter="request-result-tv"><span>` +
15384    tv +
15385    `</span> <i class="fa fa-tv m-l-5 fa-fw"></i></button>
15386        <button class="btn btn-info waves-effect waves-light filter-request-result" data-filter="request-result-music"><span>` +
15387    music +
15388    `</span> <i class="fa fa-music m-l-5 fa-fw"></i></button>
15389    </div>
15390    `;
15391  results = '<div class="media-results">' + results + "</div>";
15392  return buttons + results;
15393}
15394function getPingList(arrayItems) {
15395  var pingList = [];
15396  var timeout =
15397    activeInfo.user.groupID <= 1
15398      ? activeInfo.settings.homepage.refresh.adminPingRefresh
15399      : activeInfo.settings.homepage.refresh.otherPingRefresh;
15400  if (
15401    Array.isArray(arrayItems["data"]["tabs"]) &&
15402    arrayItems["data"]["tabs"].length > 0
15403  ) {
15404    $.each(arrayItems["data"]["tabs"], function (i, v) {
15405      if (v.ping && v.ping_url !== null) {
15406        pingList.push(v.ping_url);
15407      }
15408    });
15409  }
15410  return pingList.length > 0 ? pingUpdate(pingList, timeout) : false;
15411}
15412function pingUpdateItem(ping) {
15413  if (activeInfo.user.groupID > activeInfo.settings.ping.auth) {
15414    return false;
15415  }
15416  organizrAPI2("GET", "api/v2/ping/" + ping)
15417    .success(function (data) {
15418      try {
15419        var response = data.response;
15420      } catch (e) {
15421        organizrCatchError(e, data);
15422      }
15423      var i = ping;
15424      var v = response.data;
15425      var elm = $(".menu-" + cleanClass(i) + "-ping");
15426      var elmMs = $(".menu-" + cleanClass(i) + "-ping-ms");
15427      var catElm = elm
15428        .parent()
15429        .parent()
15430        .parent()
15431        .parent()
15432        .children("a")
15433        .find(".menu-category-ping");
15434      var error =
15435        '<div class="ping"><span class="heartbit"></span><span class="point"></span></div>';
15436      var success = "";
15437      var badCount = 0;
15438      var goodCount = 0;
15439      var previousState =
15440        elm.attr("data-previous-state") == ""
15441          ? ""
15442          : elm.attr("data-previous-state");
15443      var tabName = elm.attr("data-tab-name");
15444      var status = v == null ? "down" : "up";
15445      var ms = v == null ? "down" : v + "ms";
15446      var sendMessage =
15447        previousState !== status &&
15448        previousState !== "" &&
15449        activeInfo.user.groupID <= activeInfo.settings.ping.authMessage
15450          ? true
15451          : false;
15452      var audioDown = sendMessage
15453        ? new Audio(activeInfo.settings.ping.offlineSound)
15454        : "";
15455      var audioUp = sendMessage
15456        ? new Audio(activeInfo.settings.ping.onlineSound)
15457        : "";
15458      elm.attr("data-previous-state", status);
15459      let listing = elm
15460        .parent()
15461        .parent()
15462        .parent()
15463        .parent()
15464        .children("a")
15465        .find(".menu-category-ping")
15466        .parent()
15467        .parent()
15468        .find("li")
15469        .find("div[class$='-ping']");
15470      $.each(listing, function (i, v) {
15471        let state = $(v).attr("data-previous-state");
15472        if (state == "up") {
15473          goodCount = goodCount + 1;
15474        } else if (state == "down") {
15475          badCount = badCount + 1;
15476        }
15477      });
15478      if (catElm.length > 0) {
15479        catElm.attr("data-bad", badCount);
15480        catElm.attr("data-good", goodCount);
15481        if (badCount == 0) {
15482          catElm.html(success);
15483        }
15484      }
15485      if (
15486        activeInfo.user.groupID <= activeInfo.settings.ping.authMs &&
15487        activeInfo.settings.ping.ms
15488      ) {
15489        elmMs.removeClass("hidden").html(ms);
15490      }
15491      switch (status) {
15492        case "down":
15493          elm.html(error);
15494          catElm.html(error);
15495          elm.parent().find("img").addClass("grayscale");
15496          var msg = sendMessage
15497            ? message(
15498                tabName,
15499                "Server Down",
15500                activeInfo.settings.notifications.position,
15501                "#FFF",
15502                "error",
15503                "600000"
15504              )
15505            : "";
15506          var audio =
15507            sendMessage && activeInfo.settings.ping.statusSounds
15508              ? audioDown.play()
15509              : "";
15510          break;
15511        default:
15512          elm.html(success);
15513          elm.parent().find("img").removeClass("grayscale");
15514          var msg = sendMessage
15515            ? message(
15516                tabName,
15517                "Server Back Online",
15518                activeInfo.settings.notifications.position,
15519                "#FFF",
15520                "success",
15521                "600000"
15522              )
15523            : "";
15524          var audio =
15525            sendMessage && activeInfo.settings.ping.statusSounds
15526              ? audioUp.play()
15527              : "";
15528      }
15529    })
15530    .fail(function (xhr) {
15531      OrganizrApiError(xhr);
15532    });
15533}
15534function pingUpdate(pingList, timeout) {
15535  $.each(pingList, function (i, v) {
15536    pingUpdateItem(v);
15537  });
15538  var timeoutTitle = "ping";
15539  if (typeof timeouts[timeoutTitle] !== "undefined") {
15540    clearTimeout(timeouts[timeoutTitle]);
15541  }
15542  timeouts[timeoutTitle] = setTimeout(function () {
15543    pingUpdate(pingList, timeout);
15544  }, timeout);
15545}
15546function include(filename) {
15547  var type = filename.split(".").pop();
15548  switch (type) {
15549    case "js":
15550      var body = document.getElementsByTagName("body")[0];
15551      var script = document.createElement("script");
15552      script.src = filename;
15553      script.type = "text/javascript";
15554      body.appendChild(script);
15555      break;
15556    case "css":
15557      var head = document.getElementById("style");
15558      var script = document.createElement("link");
15559      script.href = filename;
15560      script.type = "text/css";
15561      script.rel = "stylesheet";
15562      head.appendChild(script);
15563      break;
15564    default:
15565      return false;
15566  }
15567  return false;
15568}
15569function defineNotification() {
15570  var bb =
15571    typeof activeInfo !== "undefined"
15572      ? activeInfo.settings.notifications.backbone
15573      : "izi";
15574  switch (bb) {
15575    case "toastr":
15576      include("plugins/bower_components/toast-master/css/jquery.toast.css");
15577      include("plugins/bower_components/toast-master/js/jquery.toast.js");
15578      window.notificationFunction = "$.toast";
15579      break;
15580    case "izi":
15581      include("plugins/bower_components/iziToast/css/iziToast.min.css");
15582      include("plugins/bower_components/iziToast/js/iziToast.min.js");
15583      window.notificationFunction = "iziToast";
15584      break;
15585    case "alertify":
15586      include("plugins/bower_components/alertify/alertify.min.css");
15587      include("plugins/bower_components/alertify/default.min.css");
15588      include("plugins/bower_components/alertify/alertify.min.js");
15589      window.notificationFunction = "alertify";
15590      break;
15591    case "noty":
15592      include("plugins/bower_components/noty/noty.min.js");
15593      include("plugins/bower_components/noty/mo.min.js");
15594      include("plugins/bower_components/noty/noty.css");
15595      include("plugins/bower_components/noty/mint.css");
15596      window.notificationFunction = "Noty";
15597      break;
15598    default:
15599      return false;
15600  }
15601  window.notificationsReady = true;
15602}
15603function messagePositions() {
15604  return {
15605    br: {
15606      toastr: "bottom-right",
15607      alertify: "bottom-right",
15608      izi: "bottomRight",
15609      noty: "bottomRight",
15610    },
15611    bl: {
15612      toastr: "bottom-left",
15613      alertify: "bottom-left",
15614      izi: "bottomLeft",
15615      noty: "bottomLeft",
15616    },
15617    bc: {
15618      toastr: "bottom-center",
15619      alertify: "bottom-center",
15620      izi: "bottomCenter",
15621      noty: "bottomCenter",
15622    },
15623    tr: {
15624      toastr: "top-right",
15625      alertify: "top-right",
15626      izi: "topRight",
15627      noty: "topRight",
15628    },
15629    tl: {
15630      toastr: "top-left",
15631      alertify: "top-left",
15632      izi: "topLeft",
15633      noty: "topLeft",
15634    },
15635    tc: {
15636      toastr: "top-center",
15637      alertify: "top-center",
15638      izi: "topCenter",
15639      noty: "topCenter",
15640    },
15641    c: {
15642      toastr: "center",
15643      alertify: "bottom-center",
15644      izi: "center",
15645      noty: "center",
15646    },
15647  };
15648}
15649function message(
15650  heading,
15651  text,
15652  position,
15653  color,
15654  icon,
15655  timeout,
15656  single = false
15657) {
15658  let bb =
15659    typeof activeInfo !== "undefined"
15660      ? activeInfo.settings.notifications.backbone
15661      : "izi";
15662  let activePosition =
15663    typeof activeInfo !== "undefined"
15664      ? activeInfo.settings.notifications.position
15665      : "bc";
15666  position = typeof position !== "undefined" ? position : activePosition;
15667  text = typeof text !== "undefined" ? text : "";
15668  color = typeof color !== "undefined" ? color : "#FFF";
15669  icon = typeof icon !== "undefined" ? icon : "info";
15670  timeout = typeof timeout !== "undefined" ? timeout : 10000;
15671  switch (bb) {
15672    case "toastr":
15673      var ready = eval(notificationFunction) !== undefined ? true : false;
15674      break;
15675    case "izi":
15676    case "alertify":
15677    case "noty":
15678      try {
15679        var ready =
15680          typeof eval(notificationFunction) !== undefined ? true : false;
15681      } catch (e) {
15682        if (e instanceof SyntaxError) {
15683          setTimeout(function () {
15684            message(heading, text, position, color, icon, timeout, single);
15685          }, 100);
15686        }
15687      }
15688      break;
15689    default:
15690      var ready = false;
15691  }
15692  if (notificationsReady && ready) {
15693    oldPosition = position;
15694    position = messagePositions()[position][bb];
15695    if (typeof activeInfo === "undefined") {
15696      setTimeout(function () {
15697        message(heading, text, oldPosition, color, icon, timeout, single);
15698      }, 100);
15699      return false;
15700    }
15701    if (single) {
15702      switch (bb) {
15703        case "toastr":
15704          $.toast().reset("all");
15705          break;
15706        case "izi":
15707          iziToast.destroy();
15708          break;
15709        case "alertify":
15710          alertify.dismissAll();
15711          break;
15712        case "noty":
15713          Noty.closeAll();
15714          break;
15715        default:
15716          return false;
15717      }
15718    }
15719    switch (bb) {
15720      case "toastr":
15721        $.toast({
15722          heading: heading,
15723          text: text,
15724          position: position,
15725          loaderBg: color,
15726          icon: icon,
15727          hideAfter: timeout,
15728          stack: 6,
15729          showHideTransition: "slide",
15730        });
15731        break;
15732      case "izi":
15733        switch (icon) {
15734          case "success":
15735            var msg = {
15736              icon: "mdi mdi-check-circle-outline",
15737            };
15738            break;
15739          case "info":
15740            var msg = {
15741              icon: "mdi mdi-information-outline",
15742            };
15743            break;
15744          case "error":
15745            var msg = {
15746              icon: "mdi mdi-close-circle-outline",
15747            };
15748            break;
15749          case "warning":
15750            var msg = {
15751              icon: "mdi mdi-alert-circle-outline",
15752            };
15753            break;
15754          case "update":
15755            var msg = {
15756              icon: "mdi mdi-webpack",
15757            };
15758            break;
15759          default:
15760            var msg = {
15761              icon: "mdi mdi-alert-circle-outline",
15762            };
15763        }
15764        iziToast.show({
15765          close: true,
15766          progressBar: true,
15767          progressBarEasing: "ease",
15768          class: icon + "-notify",
15769          title: heading,
15770          message: text,
15771          position: position,
15772          timeout: timeout,
15773          layout: 2,
15774          transitionIn: "flipInX",
15775          transitionOut: "flipOutX",
15776          balloon: false,
15777          icon: msg["icon"],
15778        });
15779        break;
15780      case "alertify":
15781        var msgFull = heading !== "" ? heading + "<br/>" + text : text;
15782        timeout = timeout / 1000;
15783        alertify.set("notifier", "position", position);
15784        alertify.notify(msgFull, icon + "-alertify", timeout);
15785        break;
15786      case "noty":
15787        if (typeof mojs == "undefined") {
15788          setTimeout(function () {
15789            message(heading, text, oldPosition, color, icon, timeout);
15790          }, 100);
15791          return false;
15792        }
15793        var msgFull = heading !== "" ? heading + "<br/>" + text : text;
15794        new Noty({
15795          type: icon + "-noty",
15796          layout: position,
15797          text: msgFull,
15798          progressBar: true,
15799          timeout: timeout,
15800          animation: {
15801            open: function (promise) {
15802              var n = this;
15803              var Timeline = new mojs.Timeline();
15804              var body = new mojs.Html({
15805                el: n.barDom,
15806                x: { 500: 0, delay: 0, duration: 500, easing: "elastic.out" },
15807                isForce3d: true,
15808                onComplete: function () {
15809                  promise(function (resolve) {
15810                    resolve();
15811                  });
15812                },
15813              });
15814
15815              var parent = new mojs.Shape({
15816                parent: n.barDom,
15817                width: 200,
15818                height: n.barDom.getBoundingClientRect().height,
15819                radius: 0,
15820                x: { [150]: -150 },
15821                duration: 1.2 * 500,
15822                isShowStart: true,
15823              });
15824
15825              n.barDom.style["overflow"] = "visible";
15826              parent.el.style["overflow"] = "hidden";
15827
15828              var burst = new mojs.Burst({
15829                parent: parent.el,
15830                count: 10,
15831                top: n.barDom.getBoundingClientRect().height + 75,
15832                degree: 90,
15833                radius: 75,
15834                angle: { [-90]: 40 },
15835                children: {
15836                  fill: "#EBD761",
15837                  delay: "stagger(500, -50)",
15838                  radius: "rand(8, 25)",
15839                  direction: -1,
15840                  isSwirl: true,
15841                },
15842              });
15843
15844              var fadeBurst = new mojs.Burst({
15845                parent: parent.el,
15846                count: 2,
15847                degree: 0,
15848                angle: 75,
15849                radius: { 0: 100 },
15850                top: "90%",
15851                children: {
15852                  fill: "#EBD761",
15853                  pathScale: [0.65, 1],
15854                  radius: "rand(12, 15)",
15855                  direction: [-1, 1],
15856                  delay: 0.8 * 500,
15857                  isSwirl: true,
15858                },
15859              });
15860
15861              Timeline.add(body, burst, fadeBurst, parent);
15862              Timeline.play();
15863            },
15864            close: function (promise) {
15865              var n = this;
15866              new mojs.Html({
15867                el: n.barDom,
15868                x: { 0: 500, delay: 10, duration: 500, easing: "cubic.out" },
15869                skewY: { 0: 10, delay: 10, duration: 500, easing: "cubic.out" },
15870                isForce3d: true,
15871                onComplete: function () {
15872                  promise(function (resolve) {
15873                    resolve();
15874                  });
15875                },
15876              }).play();
15877            },
15878          },
15879        }).show();
15880        break;
15881      default:
15882        organizrConsole("Organizr Function", "Message case not setup");
15883    }
15884  } else {
15885    setTimeout(function () {
15886      message(heading, text, position, color, icon, timeout, single);
15887    }, 100);
15888  }
15889}
15890function messageSingle(heading, text, position, color, icon, timeout) {
15891  let activePosition =
15892    typeof activeInfo !== "undefined"
15893      ? activeInfo.settings.notifications.position
15894      : "bc";
15895  position = typeof position !== "undefined" ? position : activePosition;
15896  text = typeof text !== "undefined" ? text : "";
15897  color = typeof color !== "undefined" ? color : "#FFF";
15898  icon = typeof icon !== "undefined" ? icon : "info";
15899  timeout = typeof timeout !== "undefined" ? timeout : 10000;
15900  message(heading, text, position, color, icon, timeout, true);
15901}
15902
15903function blockDev(e) {
15904  var evtobj = window.event ? event : e;
15905  if (evtobj.keyCode == 73 && evtobj.shiftKey && evtobj.ctrlKey) {
15906    evtobj.preventDefault();
15907  }
15908}
15909function authDebugCheck() {
15910  if (activeInfo.settings.misc.authDebug == true) {
15911    message(
15912      "REMINDER",
15913      "Auth Debug is still enabled",
15914      activeInfo.settings.notifications.position,
15915      "#FFF",
15916      "warning",
15917      "20000"
15918    );
15919  }
15920}
15921function lock() {
15922  if (activeInfo.settings.user.oAuthLogin == true) {
15923    message(
15924      "Lock Disabled",
15925      "Lock function disabled if logged in via oAuth",
15926      activeInfo.settings.notifications.position,
15927      "#FFF",
15928      "warning",
15929      "5000"
15930    );
15931    return false;
15932  }
15933  organizrAPI2("POST", "api/v2/users/lock", "")
15934    .success(function (data) {
15935      try {
15936        let html = data.response;
15937        location.reload();
15938      } catch (e) {
15939        organizrCatchError(e, data);
15940      }
15941    })
15942    .fail(function (xhr) {
15943      OrganizrApiError(xhr, "Lock Error");
15944    });
15945}
15946function openSettings() {
15947  let tabInfo = findTab("api/v2/page/settings", "access_url");
15948  tabActions("click", tabInfo.id);
15949}
15950function openHomepage() {
15951  let tabInfo = findTab("api/v2/page/homepage", "access_url");
15952  tabActions("click", tabInfo.id);
15953}
15954function toggleFullScreenIcon() {
15955  $(".fullscreen-icon")
15956    .toggleClass("ti-fullscreen")
15957    .toggleClass("mdi mdi-fullscreen-exit");
15958}
15959function toggleFullScreen() {
15960  toggleFullScreenIcon();
15961  if (
15962    !document.fullscreenElement && // alternative standard method
15963    !document.mozFullScreenElement &&
15964    !document.webkitFullscreenElement &&
15965    !document.msFullscreenElement
15966  ) {
15967    // current working methods
15968    if (document.documentElement.requestFullscreen) {
15969      document.documentElement.requestFullscreen();
15970    } else if (document.documentElement.msRequestFullscreen) {
15971      document.documentElement.msRequestFullscreen();
15972    } else if (document.documentElement.mozRequestFullScreen) {
15973      document.documentElement.mozRequestFullScreen();
15974    } else if (document.documentElement.webkitRequestFullscreen) {
15975      document.documentElement.webkitRequestFullscreen(
15976        Element.ALLOW_KEYBOARD_INPUT
15977      );
15978    }
15979  } else {
15980    if (document.exitFullscreen) {
15981      document.exitFullscreen();
15982    } else if (document.msExitFullscreen) {
15983      document.msExitFullscreen();
15984    } else if (document.mozCancelFullScreen) {
15985      document.mozCancelFullScreen();
15986    } else if (document.webkitExitFullscreen) {
15987      document.webkitExitFullscreen();
15988    }
15989  }
15990}
15991function orgErrorCode(code) {
15992  switch (code) {
15993    case "upgrading":
15994      window.location.href = "./plugins/static/upgrade.html";
15995    default:
15996  }
15997}
15998function clickPath(type, path = null) {
15999  switch (type) {
16000    case "c":
16001    case "custom":
16002      if (path !== null) {
16003        if (typeof path == "object") {
16004          $.each(path, function (i, v) {
16005            $(v).trigger("click");
16006          });
16007        } else {
16008          $(path).trigger("click");
16009        }
16010      } else {
16011        return null;
16012      }
16013      break;
16014    case "update":
16015      $("#settings-main-system-settings-anchor").trigger("click");
16016      $("#settings-settings-updates-anchor").trigger("click");
16017      break;
16018    case "sso":
16019      $("#settings-main-system-settings-anchor").trigger("click");
16020      $("#settings-settings-sso-anchor").trigger("click");
16021      break;
16022    default:
16023      return null;
16024  }
16025}
16026function toggleWritableFolders() {
16027  $(".folders-writable").toggleClass("hidden");
16028}
16029function getAllTabNames() {
16030  var allTabs = $(".tabEditor");
16031  var tabList = [];
16032  $.each(allTabs, function (i, v) {
16033    tabList[i] = v.getAttribute("data-name").toLowerCase();
16034  });
16035  return tabList;
16036}
16037function checkIfTabNameExists(tabName) {
16038  if (getAllTabNames().indexOf(tabName.toLowerCase()) == -1) {
16039    return false;
16040  } else {
16041    return true;
16042  }
16043}
16044function getLatestBlackberryThemes() {
16045  return $.ajax({
16046    url: "https://api.github.com/repos/Archmonger/Blackberry-Themes/contents/Themes",
16047  });
16048}
16049function getBlackberryTheme(theme) {
16050  return $.ajax({
16051    url:
16052      "https://api.github.com/repos/Archmonger/Blackberry-Themes/contents/Themes/" +
16053      theme +
16054      "/Icons",
16055  });
16056}
16057function showBlackberryThemes(target) {
16058  getLatestBlackberryThemes()
16059    .success(function (data) {
16060      try {
16061        let themes = "";
16062        $.each(data, function (i, v) {
16063          if (v.name !== "Beta") {
16064            themes += `<a href="javascript:selectBlackberryTheme('${v.name}','${target}');" class="list-group-item"><span><img class="themeIcon pull-right" src="https://raw.githubusercontent.com/Archmonger/Blackberry-Themes/master/Themes/${v.name}/Icons/preview.png"></span>${v.name}</a>`;
16065          }
16066        });
16067        themes = `<div class="list-group">${themes}</div>`;
16068        let html = `
16069			<div class="panel">
16070				<div class="bg-org2">
16071					<div class="panel-heading">Choose a Theme</div>
16072					<div class="panel-body text-left">${themes}</div>
16073				</div>
16074			</div>
16075			`;
16076        swal({
16077          content: createElementFromHTML(html),
16078          button: "Close",
16079          className: "orgErrorAlert",
16080          dangerMode: true,
16081        });
16082      } catch (e) {
16083        organizrCatchError(e, data);
16084      }
16085    })
16086    .fail(function (xhr) {
16087      OrganizrApiError(xhr);
16088    });
16089}
16090function selectBlackberryTheme(theme, target) {
16091  getBlackberryTheme(theme)
16092    .success(function (data) {
16093      try {
16094        let icons = "";
16095        $.each(data, function (i, v) {
16096          v.name = v.name.split(".")[0];
16097          v.name = cleanClass(v.name);
16098          icons += `<a href="#" onclick="javascript:swal.close();$('#${target}').val('${v.download_url}')"><img alt="${v.name}" data-toggle="tooltip" data-placement="top" title="" data-original-title="${v.name}"src="${v.download_url}" ></a>`;
16099        });
16100        icons = `<div id="gallery-content-center">${icons}</div>`;
16101        let html = `
16102			<div class="panel">
16103				<div class="bg-org2">
16104					<div class="panel-heading">Choose an Icon</div>
16105					<div class="panel-body text-left">${icons}</div>
16106				</div>
16107			</div>
16108			`;
16109        swal({
16110          content: createElementFromHTML(html),
16111          buttons: {
16112            back: {
16113              text: "Back To Themes",
16114              value: "back",
16115              dangerMode: true,
16116              className: "bg-org-alt",
16117            },
16118          },
16119          className: "orgErrorAlert",
16120          dangerMode: true,
16121        }).then((value) => {
16122          switch (value) {
16123            case "back":
16124              showBlackberryThemes();
16125              break;
16126          }
16127        });
16128      } catch (e) {
16129        organizrCatchError(e, data);
16130      }
16131    })
16132    .fail(function (xhr) {
16133      OrganizrApiError(xhr);
16134    });
16135}
16136function orgErrorAlert(error) {
16137  let showError = false;
16138  if (typeof activeInfo === "undefined") {
16139    showError = true;
16140  } else {
16141    if (activeInfo.settings.misc.debugErrors) {
16142      showError = true;
16143    }
16144  }
16145  if (showError) {
16146    let div = `
16147	    <div class="panel">
16148            <div class="bg-org2">
16149                <div class="panel-heading">ERROR</div>
16150                <div class="panel-body text-left">${error}</div>
16151            </div>
16152        </div>
16153	    `;
16154    swal({
16155      content: createElementFromHTML(div),
16156      button: "OK",
16157      className: "orgErrorAlert",
16158      dangerMode: true,
16159    });
16160  }
16161}
16162function toggleDebug() {
16163  var div = `
16164	<div class="white-box m-0">
16165	    <div class="steamline">
16166	        <div class="sl-item">
16167	            <div class="sl-left bg-success"><i class="mdi mdi-code-tags"></i></div>
16168	            <div class="sl-right">
16169	                <div class="form-group">
16170	                    <div id="" class="input-group">
16171	                        <input id="debug-input" lang="en" placeholder="Input Command" type="text"
16172	                               class="form-control inline-focus">
16173	                        <div class="input-group-btn">
16174	                            <button type="button"
16175	                                    class="btn waves-effect waves-light btn-info dropdown-toggle"
16176	                                    data-toggle="dropdown" aria-expanded="false"><span lang="en">Commands</span>
16177	                                <span class="caret"></span></button>
16178	                            <ul class="dropdown-menu dropdown-menu-right">
16179	                                <li><a onclick="orgDebugList('activeInfo.settings.sso');"
16180	                                       href="javascript:void(0)"
16181	                                       lang="en">SSO</a></li>
16182	                                <li><a onclick="orgDebugList('activeInfo.settings.sso.plex');"
16183	                                       href="javascript:void(0)"
16184	                                       lang="en">Plex SSO</a></li>
16185	                                <li><a onclick="orgDebugList('activeInfo.settings.sso.tautulli');"
16186	                                       href="javascript:void(0)"
16187	                                       lang="en">Tautulli SSO</a></li>
16188	                                <li><a onclick="orgDebugList('activeInfo.settings.sso.overseerr');"
16189	                                       href="javascript:void(0)"
16190	                                       lang="en">
16190Overseerr SSO</a></li>
16191	                                <li><a onclick="orgDebugList('activeInfo.settings.sso.petio');"
16192	                                       href="javascript:void(0)"
16193	                                       lang="en">Petio SSO</a></li>
16194	                                <li><a onclick="orgDebugList('activeInfo.settings.sso.ombi');"
16195	                                       href="javascript:void(0)"
16196	                                       lang="en">Ombi SSO</a></li>
16197	                                <li><a onclick="orgDebugList('activeInfo.settings.sso.jellyfin');"
16198	                                       href="javascript:void(0)"
16199	                                       lang="en">Jellyfin SSO</a></li>
16200	                                <li><a onclick="orgDebugList('activeInfo.settings.sso.komga');"
16201	                                       href="javascript:void(0)"
16202	                                       lang="en">Komga SSO</a></li>
16203	                                <li><a onclick="orgDebugList('activeInfo.settings.sso.misc');"
16204	                                       href="javascript:void(0)"
16205	                                       lang="en">Misc SSO</a></li>
16206	                                <li><a onclick="orgDebugList('activeInfo.settings.misc.schema');"
16207	                                       href="javascript:void(0)"
16208	                                       lang="en">DB Schema</a></li>
16209	                            </ul>
16210	                        </div>
16211	                    </div>
16212	                    <div class="clearfix"></div>
16213	                </div>
16214	            </div>
16215	        </div>
16216	        <div id="debugPreInfoBox" class="sl-item text-left">
16217	            <div class="sl-left bg-info"><i class="mdi mdi-package-variant-closed"></i></div>
16218	            <div class="sl-right">
16219	                <div>
16220	                    <span lang="en">Organizr Information:</span>&nbsp;
16221	                </div>
16222	                <div id="debugPreInfo" class="desc"></div>
16223	            </div>
16224	        </div>
16225	        <div id="debugResultsBox" class="sl-item hidden text-left">
16226	            <div class="sl-left bg-info"><i class="mdi mdi-receipt"></i></div>
16227	            <div class="sl-right">
16228	                <div><span lang="en">Results For cmd:</span>&nbsp;<span class="cmdName"></span>
16229	                </div>
16230	                <div id="debugResults" class="desc"></div>
16231	            </div>
16232	        </div>
16233	    </div>
16234	</div>
16235	`;
16236  swal({
16237    content: createElementFromHTML(div),
16238    button: "OK",
16239    className: "orgErrorAlert",
16240  });
16241  getDebugPreInfo();
16242}
16243function toggleCalendarFilter() {
16244  var div = `
16245	<div id="calendar-filter-modal" class="panel panel-inverse">
16246        <div class="panel-heading"><span class="text-uppercase" lang="en">Filter Calendar</span></div>
16247        <div class="panel-wrapper collapse in" aria-expanded="true">
16248            <div class="panel-body">
16249	            <div class="row">
16250                    <div class="col-md-12">
16251                        <label class="control-label" lang="en">Choose Media Type</label>
16252                        <select class="form-control form-white" data-placeholder="Choose media type" id="choose-calender-filter">
16253                            <option value="all" lang="en">All</option>
16254                            <option value="tv" lang="en">TV</option>
16255                            <option value="film" lang="en">Movie</option>
16256                            <option value="music" lang="en">Music</option>
16257                        </select>
16258                    </div>
16259                    <div class="col-md-12">
16260                        <label class="control-label" lang="en">Choose Media Status</label>
16261                        <select class="form-control form-white" data-placeholder="Choose media status" id="choose-calender-filter-status">
16262                            <option value="all" lang="en">All</option>
16263                            <option value="text-success" lang="en">Downloaded</option>
16264                            <option value="text-info" lang="en">Unaired</option>
16265                            <option value="text-danger" lang="en">Missing</option>
16266                            <option value="text-primary animated flash" lang="en">Premier</option>
16267                        </select>
16268                    </div>
16269                </div>
16270            </div>
16271        </div>
16272	</div>
16273	`;
16274  swal({
16275    content: createElementFromHTML(div),
16276    className: "bg-org",
16277    button: false,
16278  });
16279}
16280function closeOrgError() {
16281  $("#main-org-error-container").removeClass("show");
16282  $("#main-org-error").html("");
16283}
16284function isJSON(data) {
16285  if (typeof data != "string") {
16286    data = JSON.stringify(data);
16287  }
16288  try {
16289    JSON.parse(data);
16290    return true;
16291  } catch (e) {
16292    return false;
16293  }
16294}
16295function createElementFromHTML(htmlString) {
16296  var div = document.createElement("div");
16297  div.innerHTML = htmlString.trim();
16298  return div.firstChild;
16299}
16300function addCoordinatesToInput(latitude, longitude) {
16301  $("#homepage-Weather-Air-form [name=homepageWeatherAndAirLatitude]")
16302    .val(latitude)
16303    .change();
16304  $("#homepage-Weather-Air-form [name=homepageWeatherAndAirLongitude]")
16305    .val(longitude)
16306    .change();
16307  swal.close();
16308  message(
16309    "Coordinates Added",
16310    "Please Save",
16311    activeInfo.settings.notifications.position,
16312    "#FFF",
16313    "success",
16314    "10000"
16315  );
16316}
16317function searchCoordinatesAPI(query) {
16318  messageSingle(
16319    "Submitting Query",
16320    "",
16321    activeInfo.settings.notifications.position,
16322    "#FFF",
16323    "info",
16324    "5000"
16325  );
16326  organizrAPI2("POST", "api/v2/homepage/weather/coordinates", { query: query })
16327    .success(function (data) {
16328      try {
16329        let html = data.response;
16330        if (html.data.type == "FeatureCollection") {
16331          var entries = "";
16332          $.each(html.data.features, function (i, v) {
16333            entries +=
16334              '<li class="text-left"><i class="fa fa-caret-right text-info"></i><span class="mouse" onclick="addCoordinatesToInput(\'' +
16335              v.center[1] +
16336              "','" +
16337              v.center[0] +
16338              "')\">" +
16339              v.place_name +
16340              "</span></li>";
16341          });
16342          var div =
16343            `
16344		        <div class="row">
16345		            <div class="col-12">
16346		                <div class="card m-b-0">
16347		                    <div class="form-horizontal">
16348		                        <div class="card-body">
16349		                            <h4 class="card-title" lang="en">Select Place</h4>
16350		                            <div class="form-group row">
16351		                                <div class="col-sm-12">
16352		                                    <ul class="list-icons">
16353		                                        ` +
16354            entries +
16355            `
16356		                                    </ul>
16357		                                </div>
16358		                            </div>
16359		                        </div>
16360		                    </div>
16361		                </div>
16362		            </div>
16363		        </div>
16364		        `;
16365          if (entries !== "") {
16366            swal.close();
16367            swal({
16368              content: createElementFromHTML(div),
16369              buttons: false,
16370              className: "bg-org",
16371            });
16372          } else {
16373            message(
16374              "API Error",
16375              "No results found...",
16376              activeInfo.settings.notifications.position,
16377              "#FFF",
16378              "warning",
16379              "10000"
16380            );
16381          }
16382        } else {
16383          message(
16384            "API Error",
16385            "",
16386            activeInfo.settings.notifications.position,
16387            "#FFF",
16388            "warning",
16389            "10000"
16390          );
16391          console.error("Organizr Function: API failed");
16392        }
16393      } catch (e) {
16394        organizrCatchError(e, data);
16395      }
16396    })
16397    .fail(function (xhr) {
16398      OrganizrApiError(xhr, "API Error");
16399    });
16400}
16401function showLookupCoordinatesModal() {
16402  var div = `
16403    <div class="row">
16404        <div class="col-12">
16405            <div class="card m-b-0">
16406                <div class="form-horizontal">
16407                    <div class="card-body">
16408                        <h4 class="card-title" lang="en">Enter City or Address</h4>
16409                        <div class="form-group row">
16410                            <div class="col-sm-12">
16411                                <input type="text" class="form-control" id="coordinatesModalCityInput" placeholder="Enter City or Address...">
16412                            </div>
16413                        </div>
16414                        <div class="form-group mb-0 p-r-10 text-right">
16415                            <button type="submit" onclick="searchCoordinatesAPI($('#coordinatesModalCityInput').val())" class="btn btn-info waves-effect waves-light">Submit</button>
16416                        </div>
16417                    </div>
16418                </div>
16419            </div>
16420        </div>
16421    </div>
16422    `;
16423  swal({
16424    content: createElementFromHTML(div),
16425    buttons: false,
16426    className: "bg-org",
16427  });
16428}
16429function showLDAPLoginTest() {
16430  var div = `
16431        <div class="row">
16432            <div class="col-12">
16433                <div class="card m-b-0">
16434                    <div class="form-horizontal">
16435                        <div class="card-body">
16436                            <h4 class="card-title" lang="en">LDAP User Info</h4>
16437                            <div class="form-group row">
16438                                <div class="col-sm-12">
16439                                    <input type="text" class="form-control" id="ldapUsernameTest" placeholder="Username">
16440                                </div>
16441                            </div>
16442                            <div class="form-group row">
16443                                <div class="col-sm-12">
16444                                    <input type="password" class="form-control" id="ldapPasswordTest" placeholder="Password">
16445                                </div>
16446                            </div>
16447                            <div class="form-group mb-0 p-r-10 text-right">
16448                                <button type="submit" onclick="testAPIConnection('ldap/login', {'username':$('#ldapUsernameTest').val(),'password':$('#ldapPasswordTest').val()})" class="btn btn-info waves-effect waves-light">Test Login</button>
16449                            </div>
16450                        </div>
16451                    </div>
16452                </div>
16453            </div>
16454        </div>
16455    `;
16456  swal({
16457    content: createElementFromHTML(div),
16458    buttons: false,
16459    className: "bg-org",
16460  });
16461}
16462
16463function showPlexTokenForm(selector = null) {
16464  var div =
16465    `
16466		<form id="get-plex-token-form">
16467		    <h1 lang="en">Get Plex Token</h1>
16468		    <div class="panel plexTokenHeader">
16469		        <div class="panel-heading plexTokenMessage" lang="en">Enter Plex Details</div>
16470		    </div>
16471		    <fieldset style="border:0;">
16472		        <div class="form-group">
16473		            <label class="control-label" for="plex-token-form-username" lang="en">Plex Username</label>
16474		            <input type="text" class="form-control" id="plex-token-form-username" name="username" required="" autofocus>
16475		        </div>
16476		        <div class="form-group">
16477		            <label class="control-label" for="plex-token-form-password" lang="en">Plex Password</label>
16478		            <input type="password" class="form-control" id="plex-token-form-password" name="password"  required="">
16479		        </div>
16480		        <div class="form-group">
16481		            <label class="control-label" for="plex-token-form-tfa" lang="en">Plex 2FA (if applicable)</label>
16482		            <input type="text" class="form-control" id="plex-token-form-tfa" name="tfa" >
16483		        </div>
16484		    </fieldset>
16485		    <button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none" onclick="getPlexToken('` +
16486    selector +
16487    `')" type="button"><span class="btn-label"><i class="fa fa-ticket"></i></span><span lang="en">Grab It</span></button>
16488		    <div class="clearfix"></div>
16489		</form>
16490	`;
16491  swal({
16492    content: createElementFromHTML(div),
16493    buttons: false,
16494    className: "bg-org",
16495  });
16496}
16497function getPlexToken(selector) {
16498  $(".plexTokenMessage").text("Grabbing Token");
16499  $(".plexTokenHeader")
16500    .addClass("panel-info")
16501    .removeClass("panel-warning")
16502    .removeClass("panel-danger");
16503  var plex_username = $("#get-plex-token-form [name=username]").val().trim();
16504  var plex_password = $("#get-plex-token-form [name=password]").val().trim();
16505  var plex_tfa = $("#get-plex-token-form [name=tfa]").val().trim();
16506  if (plex_password !== "" && plex_password !== "") {
16507    $.ajax({
16508      type: "POST",
16509      headers: {
16510        "X-Plex-Product": "Organizr",
16511        "X-Plex-Version": "2.0",
16512        "X-Plex-Client-Identifier": "01010101-10101010",
16513      },
16514      url: "https://plex.tv/users/sign_in.json",
16515      data: {
16516        "user[login]": plex_username,
16517        "user[password]": plex_password + plex_tfa,
16518        force: true,
16519      },
16520      cache: false,
16521      async: true,
16522      complete: function (xhr, status) {
16523        var result = $.parseJSON(xhr.responseText);
16524        if (xhr.status === 201) {
16525          $(".plexTokenMessage").text(xhr.statusText);
16526          $(".plexTokenHeader")
16527            .addClass("panel-success")
16528            .removeClass("panel-info")
16529            .removeClass("panel-warning")
16530            .removeClass("panel-danger");
16531          $(selector).val(result.user.authToken);
16532          $(selector).change();
16533          messageSingle(
16534            "Token created",
16535            "Please save...",
16536            activeInfo.settings.notifications.position,
16537            "#FFF",
16538            "success",
16539            "5000"
16540          );
16541        } else {
16542          $(".plexTokenMessage").text(xhr.statusText);
16543          $(".plexTokenHeader")
16544            .addClass("panel-danger")
16545            .removeClass("panel-info")
16546            .removeClass("panel-warning");
16547        }
16548      },
16549    });
16550  } else {
16551    $(".plexTokenMessage").text("Enter Username and Password");
16552    $(".plexTokenHeader")
16553      .addClass("panel-warning")
16554      .removeClass("panel-info")
16555      .removeClass("panel-danger");
16556  }
16557}
16558function showPlexMachineForm(selector = null) {
16559  var div = `
16560		<form id="get-plex-machine-form">
16561		    <h1 lang="en">Get Plex Machine</h1>
16562		    <div class="panel plexMachineHeader">
16563		        <div class="panel-heading plexMachineMessage" lang="en">Contacting server...</div>
16564		    </div>
16565		    <fieldset style="border:0;">
16566		        <div class="form-group">
16567		            <label class="control-label" for="plex-machine-form-machine" lang="en">Plex Machine</label>
16568		            <div class="plexMachineListing"></div>
16569		        </div>
16570		    </fieldset>
16571		    <div class="clearfix"></div>
16572		</form>
16573	`;
16574  swal({
16575    content: createElementFromHTML(div),
16576    buttons: false,
16577    className: "bg-org",
16578  }).then(
16579    organizrAPI2("GET", "api/v2/plex/servers?owned")
16580      .success(function (data) {
16581        try {
16582          let response = data.response;
16583          $(".plexMachineMessage").text("Choose Plex Server");
16584          $(".plexMachineHeader")
16585            .addClass("panel-success")
16586            .removeClass("panel-info")
16587            .removeClass("panel-warning");
16588          let machines = '<option lang="en">Choose Plex Machine</option>';
16589          $.each(response.data, function (i, v) {
16590            let name = v.name;
16591            let machine = v.machineIdentifier;
16592            name = name + " [" + machine + "]";
16593            machines += '<option value="' + machine + '">' + name + "</option>";
16594          });
16595          let listing =
16596            '<select class="form-control" id="plexMachineSelector" data-selector="' +
16597            selector +
16598            '" data-type="select">' +
16599            machines +
16600            "</select>";
16601          $(".plexMachineListing").html(listing);
16602        } catch (e) {
16603          organizrCatchError(e, data);
16604        }
16605      })
16606      .fail(function (xhr) {
16607        OrganizrApiError(xhr, "API Error");
16608        $(".plexMachineMessage").text("Plex Token Needed First");
16609        $(".plexMachineHeader")
16610          .addClass("panel-warning")
16611          .removeClass("panel-info")
16612          .removeClass("panel-danger");
16613      })
16614  );
16615}
16616function bypassLocalLogin() {
16617  if (activeInfo.settings.user.bypass !== true) {
16618    return false;
16619  }
16620  const bypass = $.urlParam("bypassDisable");
16621  if (bypass) {
16622    return false;
16623  }
16624  OAuthLoginNeeded = true;
16625  oAuthLoginNeededCheck("Bypass");
16626}
16627function oAuthLoginNeededCheck(type = "OAuth") {
16628  if (OAuthLoginNeeded == false) {
16629    return false;
16630  } else {
16631    if (activeInfo.user.loggedin == true) {
16632      return false;
16633    }
16634  }
16635  let data = "";
16636  if (type === "Bypass") {
16637    const bypass = $.urlParam("bypassDisable");
16638    if (bypass) {
16639      data = "bypass";
16640    }
16641  }
16642  message(
16643    type,
16644    " Proceeding to login",
16645    activeInfo.settings.notifications.position,
16646    "#FFF",
16647    "info",
16648    "10000"
16649  );
16650  organizrAPI2("POST", "api/v2/login", data)
16651    .success(function (data) {
16652      local("set", "message", "Welcome|Login Successful|success");
16653      local("r", "loggingIn");
16654      location.reload();
16655    })
16656    .fail(function (xhr) {
16657      $("div.login-box").unblock({});
16658      switch (xhr.status) {
16659        case 401:
16660          if (xhr.responseJSON.response.message == "2FA Code incorrect") {
16661            $("div.login-box").unblock({});
16662            $("#tfa-div").removeClass("hidden");
16663            $("#loginform [name=tfaCode]").focus();
16664          }
16665          break;
16666        case 403:
16667          $("div.login-box").block({
16668            message: '<h5><i class="fa fa-close"></i> Locked Out!</h4>',
16669            css: {
16670              color: "#fff",
16671              border: "1px solid #e91e63",
16672              backgroundColor: "#f44336",
16673            },
16674          });
16675          setTimeout(function () {
16676            local("r", "loggingIn");
16677            location.reload();
16678          }, 10000);
16679          break;
16680        case 422:
16681          $("div.login-box").unblock({});
16682          $("#tfa-div").removeClass("hidden");
16683          $("#loginform [name=tfaCode]").focus();
16684          break;
16685        default:
16686          message(
16687            "Login Error",
16688            "API Connection Failed",
16689            activeInfo.settings.notifications.position,
16690            "#FFF",
16691            "error",
16692            "10000"
16693          );
16694          console.error("Organizr Function: API Connection Failed");
16695      }
16696      message(
16697        "Login Error",
16698        xhr.responseJSON.response.message,
16699        activeInfo.settings.notifications.position,
16700        "#FFF",
16701        "warning",
16702        "10000"
16703      );
16704      console.error("Organizr Function: " + xhr.responseJSON.response.message);
16705      local("r", "loggingIn");
16706    });
16707}
16708function ipInfoSpan(ip) {
16709  return '<span class="ipInfo mouse">' + ip + "</span>";
16710}
16711function jsFriendlyJSONStringify(s) {
16712  return JSON.stringify(s)
16713    .replace("'", "")
16714    .replace(/\u2028/g, "\\u2028")
16715    .replace(/\u2029/g, "\\u2029");
16716}
16717function exportLogs() {
16718  const query = "api/v2/log/0?filter=NONE&pageSize=1000&offset=0";
16719  $.get(query, function (data) {
16720    const logs = data.response.data.results;
16721    let csvContent =
16722      "data:text/csv;charset=utf-8,Date,Severity,Function,Message,IP Address,User\n";
16723    logs.forEach(function (log) {
16724      const row = [
16725        log.datetime,
16726        log.log_level,
16727        log.channel,
16728        log.message,
16729        log.remote_ip_address,
16730        log.username,
16731      ].join(",");
16732      csvContent += row + "\n";
16733    });
16734    const encodedUri = encodeURI(csvContent);
16735    const link = document.createElement("a");
16736    link.setAttribute("href", encodedUri);
16737    link.setAttribute("download", "organizr_logs.csv");
16738    document.body.appendChild(link);
16739    link.click();
16740    document.body.removeChild(link);
16741  });
16742}
16743function logContext(row) {
16744  let buttons = "";
16745  buttons +=
16746    Object.keys(row).length > 0
16747      ? '<button data-toggle="tooltip" title="" data-original-title="View Details" class="btn btn-xs btn-primary waves-effect waves-light log-details m-r-5" data-trace="' +
16748        row.trace_id +
16749        '"><i class="mdi mdi-file-find"></i></button>'
16750      : "";
16751  buttons +=
16752    Object.keys(row).length > 0
16753      ? '<button data-toggle="tooltip" title="" data-original-title="Copy Log" class="btn btn-xs btn-info waves-effect waves-light log-details m-r-5" data-trace="' +
16754        row.trace_id +
16755        '" data-clipboard="true"><i class="mdi mdi-content-copy"></i></button>'
16756      : "";
16757  return buttons;
16758}
16759function formatLogDetails(details) {
16760  if (!details) {
16761    return false;
16762  }
16763  let m = moment.tz(details.datetime + "Z", activeInfo.timezone);
16764  details.datetime = moment(m).format("LLL");
16765  let items = "";
16766  items += `<li><div class="bg-inverse"><i class="mdi mdi-calendar-text text-white"></i></div> ${details.datetime}<span class="text-muted" lang="en">Date</span></li>`;
16767  items += `<li><div class="bg-warning"><i class="mdi mdi-robot text-white"></i></div> ${details.trace_id}<span class="text-muted" lang="en">Trace ID</span></li>`;
16768  items += `<li><div class="bg-primary"><i class="mdi mdi-account-box-outline text-white"></i></div> ${details.username}<span class="text-muted" lang="en">User</span></li>`;
16769  items += `<li><div class="bg-info"><i class="mdi mdi-function text-white"></i></div> ${details.channel}<span class="text-muted" lang="en">Function</span></li>`;
16770  items += `<li><div class="bg-plex"><i class="mdi mdi-language-php text-white"></i></div> ${details.file}<code>#L${details.line}</code><span class="text-muted" lang="en">File</span></li>`;
16771  let items2 = "";
16772  items2 +=
16773    Object.keys(details.context).length > 0
16774      ? `<div class="sl-item"><div class="sl-left bg-inverse"> <i class="mdi mdi-json"></i></div><div class="sl-right"><div class="p-t-10 desc" lang="en">Context</div></div><pre class="m-5 fc-scroller">${JSON.stringify(
16775          details.context,
16776          null,
16777          5
16778        )}</pre></div>`
16779      : "";
16780  items2 +=
16781    typeof details.errors !== "undefined"
16782      ? `<div class="sl-item"><div class="sl-left bg-danger"> <i class="mdi mdi-code-braces"></i></div><div class="sl-right"><div class="p-t-10 desc" lang="en">Errors</div></div><pre class="m-5 fc-scroller">${JSON.stringify(
16783          details.errors,
16784          null,
16785          5
16786        )}</pre></div>`
16787      : "";
16788  var div = `
16789		<div class="col-lg-12">
16790			<div class="panel panel-default text-left">
16791				<div class="panel-heading"><i class="mdi mdi-file-find fa-lg fa-2x"></i> <span lang="en">Log Details</span> <span class="pull-right">${logIcon(
16792          details.log_level,
16793          true
16794        )}</span></div>
16795				<div class="panel-wrapper collapse in">
16796					<div class="panel-body bg-org">
16797						<h3>${details.message}</h3>
16798						<div class="white-box">
16799							<ul class="feeds">
16800								${items}
16801							</ul>
16802						</div>
16803						<div class="steamline">
16804							${items2}
16805						</div>
16806					</div>
16807				</div>
16808			</div>
16809		</div>`;
16810  swal({
16811    content: createElementFromHTML(div),
16812    buttons: false,
16813    className: "orgAlertTransparent",
16814  });
16815  pageLoad();
16816}
16817function checkToken(activate = false) {
16818  if (typeof activeInfo !== "undefined") {
16819    if (typeof activeInfo.settings.misc.uuid !== "undefined") {
16820      var token = getCookie("organizr_token_" + activeInfo.settings.misc.uuid);
16821      if (token) {
16822        setTimeout(function () {
16823          checkToken(true);
16824        }, 5000);
16825      } else {
16826        if (activate) {
16827          local(
16828            "set",
16829            "message",
16830            "Token Expired|You have been logged out|error"
16831          );
16832          location.reload();
16833        }
16834      }
16835    }
16836  }
16837}
16838function objDiff(obj1, obj2) {
16839  // Make sure an object to compare is provided
16840  if (!obj2 || Object.prototype.toString.call(obj2) !== "[object Object]") {
16841    return obj1;
16842  }
16843
16844  //
16845  // Variables
16846  //
16847
16848  var diffs = {};
16849  var key;
16850
16851  //
16852  // Methods
16853  //
16854
16855  /**
16856   * Check if two arrays are equal
16857   * @param  {Array}   arr1 The first array
16858   * @param  {Array}   arr2 The second array
16859   * @return {Boolean}      If true, both arrays are equal
16860   */
16861  var arraysMatch = function (arr1, arr2) {
16862    // Check if the arrays are the same length
16863    if (arr1.length !== arr2.length) return false;
16864
16865    // Check if all items exist and are in the same order
16866    for (var i = 0; i < arr1.length; i++) {
16867      if (arr1[i] !== arr2[i]) return false;
16868    }
16869
16870    // Otherwise, return true
16871    return true;
16872  };
16873
16874  /**
16875   * Compare two items and push non-matches to object
16876   * @param  {*}      item1 The first item
16877   * @param  {*}      item2 The second item
16878   * @param  {String} key   The key in our object
16879   */
16880  var compare = function (item1, item2, key) {
16881    // Get the object type
16882    var type1 = Object.prototype.toString.call(item1);
16883    var type2 = Object.prototype.toString.call(item2);
16884
16885    // If type2 is undefined it has been removed
16886    if (type2 === "[object Undefined]") {
16887      diffs[key] = null;
16888      return;
16889    }
16890
16891    // If items are different types
16892    if (type1 !== type2) {
16893      diffs[key] = item2;
16894      return;
16895    }
16896
16897    // If an object, compare recursively
16898    if (type1 === "[object Object]") {
16899      var objDifference = objDiff(item1, item2);
16900      if (Object.keys(objDifference).length > 1) {
16901        diffs[key] = objDifference;
16902      }
16903      return;
16904    }
16905
16906    // If an array, compare
16907    if (type1 === "[object Array]") {
16908      if (!arraysMatch(item1, item2)) {
16909        diffs[key] = item2;
16910      }
16911      return;
16912    }
16913
16914    // Else if it's a function, convert to a string and compare
16915    // Otherwise, just compare
16916    if (type1 === "[object Function]") {
16917      if (item1.toString() !== item2.toString()) {
16918        diffs[key] = item2;
16919      }
16920    } else {
16921      if (item1 !== item2) {
16922        diffs[key] = item2;
16923      }
16924    }
16925  };
16926
16927  //
16928  // Compare our objects
16929  //
16930
16931  // Loop through the first object
16932  for (key in obj1) {
16933    if (obj1.hasOwnProperty(key)) {
16934      compare(obj1[key], obj2[key], key);
16935    }
16936  }
16937
16938  // Loop through the second object and find missing items
16939  for (key in obj2) {
16940    if (obj2.hasOwnProperty(key)) {
16941      if (!obj1[key] && obj1[key] !== obj2[key]) {
16942        diffs[key] = obj2[key];
16943      }
16944    }
16945  }
16946
16947  // Return the object of differences
16948  return diffs;
16949}
16950function organizrConsole(subject, msg, type = "info") {
16951  let color;
16952  switch (type) {
16953    case "error":
16954      color = "#ed2e72";
16955      break;
16956    case "warning":
16957      color = "#272361";
16958      break;
16959    default:
16960      color = "#2cabe3";
16961      break;
16962  }
16963
16964  console.info(
16965    "%c " + subject + " %c ".concat(msg, " "),
16966    "color: white; background: " + color + "; font-weight: 700;",
16967    "color: " + color + "; background: white; font-weight: 700;"
16968  );
16969}
16970function organizrCatchError(e, data) {
16971  organizrConsole("Organizr API Function", data, "warning");
16972  orgErrorAlert(
16973    "<h4>" +
16974      e +
16975      '</h4><p><mark lang="en">Trace Log has been outputted to Browser Console</mark></p><h5 lang="en">Output of last API call</h5>' +
16976      formatDebug(data)
16977  );
16978  console.trace();
16979  return false;
16980}
16981function OrganizrApiError(xhr, secondaryMessage = null) {
16982  let msg = "";
16983  if (typeof xhr.responseJSON !== "undefined") {
16984    msg = xhr.responseJSON.response.message;
16985  } else if (typeof xhr.statusText !== "undefined") {
16986    msg = xhr.statusText;
16987  } else if (typeof xhr.responseText !== "undefined") {
16988    msg = xhr.responseText;
16989  } else {
16990    msg = "Connection Error";
16991  }
16992  organizrConsole("Organizr API Function", msg, "error");
16993
16994  if (msg !== "abort") {
16995    if (secondaryMessage) {
16996      messageSingle(
16997        secondaryMessage,
16998        msg,
16999        activeInfo.settings.notifications.position,
17000        "#FFF",
17001        "error",
17002        "10000"
17003      );
17004    }
17005    console.trace();
17006  }
17007  return false;
17008}
17009function checkForUpdates() {
17010  if (
17011    activeInfo.user.loggedin &&
17012    activeInfo.user.groupID <= 1 &&
17013    activeInfo.settings.misc.checkForUpdate
17014  ) {
17015    updateCheck();
17016    checkCommitLoad();
17017    checkPluginUpdates();
17018  }
17019}
17020
17021function loadJavascript(script = null, defer = false) {
17022  if (script) {
17023    organizrConsole("JS Loader", script);
17024    organizrConsole("JS Loader", "Checking if script is loaded...");
17025    let loaded = $('script[src="' + script + '"]').length;
17026    if (!loaded) {
17027      organizrConsole("JS Loader", "Script is NOT loaded... Loading now...");
17028      let head = document.getElementsByTagName("head")[0];
17029      let scriptEl = document.createElement("script");
17030      scriptEl.type = "text/javascript";
17031      scriptEl.src = script;
17032      scriptEl.defer = false;
17033      head.appendChild(scriptEl);
17034    } else {
17035      organizrConsole("JS Loader", "Script already loaded");
17036    }
17037  }
17038}
17039
17040function tabShit() {}
17041
17042function msToTime(s) {
17043  let pad = (n, z = 2) => ("00" + n).slice(-z);
17044  let hours = pad((s / 3.6e6) | 0) !== "00" ? pad((s / 3.6e6) | 0) + ":" : "";
17045  let mins = pad(((s % 3.6e6) / 6e4) | 0) + ":";
17046  let secs = pad(((s % 6e4) / 1000) | 0);
17047  let ms = pad(s % 1000, 3);
17048  if (ms >= "500") {
17049    secs = pad(parseFloat(secs) + 1, 2);
17050  }
17051  return hours + mins + secs;
17052}
17053function clickSettingsTab() {
17054  let tabs = $(".allTabsList");
17055  $.each(tabs, function (i, v) {
17056    let tab = $(v);
17057    if (tab.attr("data-url") == "api/v2/page/settings") {
17058      tab.find("a").trigger("click");
17059    }
17060  });
17061}
17062function clickMenuItem(selector) {
17063  if ($(selector).length >= 1) {
17064    $(selector).click();
17065  } else {
17066    $("body").arrive(selector, { onceOnly: true }, function () {
17067      $(selector).click();
17068    });
17069  }
17070}
17071function shortcut(selectors = "") {
17072  let timeout = 200;
17073  if (typeof selectors == "string") {
17074    if (selectors == "") {
17075      selectors = [];
17076    } else {
17077      switch (selectors) {
17078        case "log-settings":
17079          clickSettingsTab();
17080          selectors = [
17081            "#settings-main-system-settings-anchor",
17082            "#settings-settings-main-anchor",
17083            'a[href$="Logs"]',
17084          ];
17085          break;
17086        case "plugin-marketplace":
17087          clickSettingsTab();
17088          selectors = [
17089            "#settings-main-plugins-anchor",
17090            "#settings-plugins-marketplace-anchor",
17091          ];
17092          break;
17093        case "custom-cert":
17094          clickSettingsTab();
17095          selectors = [
17096            "#settings-main-system-settings-anchor",
17097            "#settings-settings-main-anchor",
17098            'a[href$="Certificate"]',
17099          ];
17100          break;
17101        default:
17102          clickSettingsTab();
17103          selectors = ["#settings-main-system-settings-anchor"];
17104      }
17105    }
17106  }
17107  selectors.forEach(function (selector) {
17108    timeout = timeout + 200;
17109    setTimeout(function () {
17110      clickMenuItem(selector);
17111    }, timeout);
17112  });
17113}
17114function getJournalMode() {
17115  organizrAPI2("GET", "api/v2/database/journal")
17116    .success(function (data) {
17117      try {
17118        let response = data.response;
17119        $(".journal-mode").html(response.data.journal_mode);
17120      } catch (e) {
17121        organizrCatchError(e, data);
17122      }
17123    })
17124    .fail(function (xhr) {
17125      OrganizrApiError(xhr);
17126    });
17127}
17128function setJournalMode(mode) {
17129  messageSingle(
17130    "Setting New Journal Mode",
17131    "",
17132    activeInfo.settings.notifications.position,
17133    "#FFF",
17134    "info",
17135    "1500"
17136  );
17137  organizrAPI2("PUT", "api/v2/database/journal/" + mode, {})
17138    .success(function (data) {
17139      try {
17140        getJournalMode();
17141        let response = data.response;
17142        message(
17143          "Set New Journal Mode",
17144          response.data.journal_mode,
17145          activeInfo.settings.notifications.position,
17146          "#FFF",
17147          "success",
17148          "5000"
17149        );
17150      } catch (e) {
17151        organizrCatchError(e, data);
17152      }
17153    })
17154    .fail(function (xhr) {
17155      OrganizrApiError(xhr);
17156    });
17157}
17158function toggleSideMenuClasses() {
17159  $("#page-wrapper").toggleClass("sidebar-hidden");
17160  $(".sidebar").toggleClass("sidebar-hidden");
17161  $(".navbar").toggleClass("sidebar-hidden");
17162}
17163function sideMenuCollapsed() {
17164  if (activeInfo.settings.misc.sideMenuCollapsed) {
17165    toggleSideMenuClasses();
17166  }
17167}
17168function toggleSideMenu() {
17169  toggleSideMenuClasses();
17170  $(".sidebar-head .open-close i")
17171    .first()
17172    .toggleClass("ti-menu ti-shift-left mouse");
17173  $(".toggle-side-menu").toggleClass("hidden");
17174}
17175
17176function toggleTopBarHamburger() {
17177  toggleSideMenuClasses();
17178  $(".sidebar-head .hide-menu.hidden-xs").text("Hide Menu");
17179  $(".sidebar-head .open-close i")
17180    .first()
17181    .toggleClass("ti-menu ti-shift-left mouse");
17182  $(".toggle-side-menu").toggleClass("hidden");
17183}
17184function toggleLogFilter(filter = "INFO") {
17185  //choose-organizr-log
17186  filter = filter.toUpperCase();
17187  $.each($(".choose-organizr-log").children(), function (i, v) {
17188    let url = $(v).val();
17189    let newURL = updateUrlParameter(url, "filter", filter);
17190    $(v).val(newURL);
17191  });
17192  $(".log-filter-text").text(filter);
17193  $(".log-filter-text").text(filter);
17194  let currentURL = organizrLogTable.ajax.url();
17195  let updatedURL = updateUrlParameter(currentURL, "filter", filter);
17196  organizrLogTable.ajax.url(updatedURL);
17197  organizrLogTable.clear().draw().ajax.reload(null, false);
17198}
17199function updateUrlParameter(uri, key, value) {
17200  // remove the hash part before operating on the uri
17201  var i = uri.indexOf("#");
17202  var hash = i === -1 ? "" : uri.substr(i);
17203  uri = i === -1 ? uri : uri.substr(0, i);
17204  var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
17205  var separator = uri.indexOf("?") !== -1 ? "&" : "?";
17206  if (value === null) {
17207    // remove key-value pair if value is specifically null
17208    uri = uri.replace(new RegExp("([?&]?)" + key + "=[^&]*", "i"), "");
17209    if (uri.slice(-1) === "?") {
17210      uri = uri.slice(0, -1);
17211    }
17212    // replace first occurrence of & by ? if no ? is present
17213    if (uri.indexOf("?") === -1) uri = uri.replace(/&/, "?");
17214  } else if (uri.match(re)) {
17215    uri = uri.replace(re, "$1" + key + "=" + value + "$2");
17216  } else {
17217    uri = uri + separator + key + "=" + value;
17218  }
17219  return uri + hash;
17220}
17221function launch() {
17222  console.info(
17223    "https://docs.organizr.app/help/faq/migration-guide#version-2-0-greater-than-version-2-1"
17224  );
17225  organizrConsole(
17226    "API V2 API",
17227    "If you see a 404 Error for api/v2/launch below this line, you have not setup the new location block... See URL above this line",
17228    "error"
17229  );
17230  organizrConnect("api/v2/launch")
17231    .success(function (data) {
17232      try {
17233        let json = data.response;
17234        if (json.data.user == false) {
17235          location.reload();
17236        }
17237        currentVersion = json.data.version;
17238        activeInfo = {
17239          tabs: json.data.tabs,
17240          categories: json.data.categories,
17241          timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
17242          offest: new Date().getTimezoneOffset(),
17243          language: language(moment.locale(navigator.languages[0])),
17244          browserVersion: bowser.name,
17245          browserName: bowser.version,
17246          mobile: bowser.mobile,
17247          tablet: bowser.tablet,
17248          osName: bowser.osname,
17249          osVersion: bowser.osversion,
17250          serverOS: json.data.status.os,
17251          phpVersion: json.data.status.php,
17252          token: json.data.user.token,
17253          user: json.data.user,
17254          plugins: json.data.plugins,
17255          branch: json.data.branch,
17256          sso: json.data.sso,
17257          settings: json.data.settings,
17258          appearance: json.data.appearance,
17259          theme: json.data.theme,
17260          style: json.data.style,
17261          version: json.data.version,
17262        };
17263        // Add element to signal activeInfo Ready
17264        $("#wrapper").after('<div id="activeInfo"></div>');
17265        console.info(
17266          "%c Organizr %c ".concat(currentVersion, " "),
17267          "color: white; background: #66D9EF; font-weight: 700; font-size: 24px; font-family: Monospace;",
17268          "color: #66D9EF; background: white; font-weight: 700; font-size: 24px; font-family: Monospace;"
17269        );
17270        console.info(
17271          "%c Status %c ".concat("Starting Up...", " "),
17272          "color: white; background: #F92671; font-weight: 700;",
17273          "color: #F92671; background: white; font-weight: 700;"
17274        );
17275        //local('set','initial',true);
17276        //setTimeout(function(){ local('r','initial'); }, 300);
17277        defineNotification();
17278        checkMessage();
17279        errorPage();
17280        uriRedirect();
17281        changeStyle(activeInfo.style);
17282        //changeTheme(activeInfo.theme);
17283        setSSO();
17284        checkToken();
17285        switch (json.data.status.status) {
17286          case "wizard":
17287            buildWizard();
17288            buildLanguage("wizard");
17289            break;
17290          case "dependencies":
17291            buildDependencyCheck(json);
17292            break;
17293          case "ok":
17294            loadAppearance(json.data.appearance);
17295            sideMenuCollapsed();
17296            if (activeInfo.user.locked == 1) {
17297              buildLockscreen();
17298            } else {
17299              userMenu(json);
17300              categoryProcess(json);
17301              tabProcess(json);
17302              buildSplashScreen(json);
17303              accountManager(json);
17304              organizrSpecialSettings(json.data);
17305              getPingList(json);
17306              checkLocalForwardStatus(json.data);
17307              checkForUpdates();
17308            }
17309            loadCustomJava(json.data.appearance);
17310            if (getCookie("lockout")) {
17311              $(".show-login").click();
17312              setTimeout(function () {
17313                $("div.login-box").block({
17314                  message: '<h5><i class="fa fa-close"></i> Locked Out!</h4>',
17315                  css: {
17316                    color: "#fff",
17317                    border: "1px solid #e91e63",
17318                    backgroundColor: "#f44336",
17319                  },
17320                });
17321              }, 1000);
17322              setTimeout(function () {
17323                location.reload();
17324              }, 60000);
17325            }
17326            break;
17327          default:
17328            console.error("Organizr Function: Action not set or defined");
17329        }
17330        console.info(
17331          "%c Organizr %c ".concat("DOM Fully loaded", " "),
17332          "color: white; background: #AD80FD; font-weight: 700;",
17333          "color: #AD80FD; background: white; font-weight: 700;"
17334        );
17335        oAuthLoginNeededCheck();
17336        bypassLocalLogin();
17337      } catch (e) {
17338        orgErrorCode(data);
17339        defineNotification();
17340        message("FATAL ERROR", data, "br", "#FFF", "error", "60000");
17341        console.warn(data);
17342        console.warn(e);
17343        return false;
17344      }
17345    })
17346    .fail(function (xhr) {
17347      defineNotification();
17348      if (xhr.status == 404) {
17349        orgErrorAlert(
17350          '<h2>Webserver not setup for Organizr v2.1</h2><h4>Please goto <a href="https://docs.organizr.app/help/faq/migration-guide#version-2-0-greater-than-version-2-1">Migration guide to complete the changes...</a></h4><h3>Webserver Error:</h3>' +
17351            xhr.responseText
17352        );
17353        message(
17354          "FATAL ERROR",
17355          "You need to update webserver location block... check browser console for migration URL",
17356          "br",
17357          "#FFF",
17358          "error",
17359          "60000"
17360        );
17361      } else {
17362        orgErrorAlert("<h3>Webserver Error:</h3>" + xhr.responseText);
17363      }
17364    });
17365}
17366
17367function homepageBookmarks(timeout) {
17368  var timeout =
17369    typeof timeout !== "undefined"
17370      ? timeout
17371      : activeInfo.settings.homepage.refresh.homepageBookmarksRefresh;
17372  organizrAPI2("GET", "api/v2/plugins/bookmark/page")
17373    .success(function (data) {
17374      try {
17375        let response = data.response;
17376        document.getElementById("homepageOrderBookmarks").innerHTML = "";
17377        if (response.data !== null) {
17378          $("#homepageOrderBookmarks").html(buildBookmarks(response.data));
17379        }
17380      } catch (e) {
17381        organizrCatchError(e, data);
17382      }
17383    })
17384    .fail(function (xhr) {
17385      OrganizrApiError(xhr);
17386    });
17387  let timeoutTitle = "Bookmarks-Homepage";
17388  if (typeof timeouts[timeoutTitle] !== "undefined") {
17389    clearTimeout(timeouts[timeoutTitle]);
17390  }
17391  timeouts[timeoutTitle] = setTimeout(function () {
17392    homepageBookmarks(timeout);
17393  }, timeout);
17394  delete timeout;
17395}
17396
17397function buildBookmarks(data) {
17398  var returnData = data;
17399  return returnData;
17400}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.