main.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. /*!
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. /* global $, moment, Airflow, window, localStorage, document, hostName, csrfToken, CustomEvent */
  20. import {
  21. dateTimeAttrFormat,
  22. formatTimezone,
  23. isoDateToTimeEl,
  24. setDisplayedTimezone,
  25. TimezoneEvent,
  26. } from "./datetime_utils";
  27. window.isoDateToTimeEl = isoDateToTimeEl;
  28. /*
  29. We pull moment in via a webpack entrypoint rather than import
  30. so that we don't put it in more than a single .js file.
  31. This "exports" it to be globally available.
  32. */
  33. window.moment = Airflow.moment;
  34. function displayTime() {
  35. const now = moment();
  36. $("#clock")
  37. .attr("datetime", now.format(dateTimeAttrFormat))
  38. .html(`${now.format("HH:mm")} <strong>${formatTimezone(now)}</strong>`);
  39. }
  40. function changeDisplayedTimezone(tz) {
  41. localStorage.setItem("selected-timezone", tz);
  42. // dispatch an event that React can listen for
  43. const event = new CustomEvent(TimezoneEvent, {
  44. detail: tz,
  45. });
  46. document.dispatchEvent(event);
  47. setDisplayedTimezone(tz);
  48. displayTime();
  49. $("body").trigger({
  50. type: "airflow.timezone-change",
  51. timezone: tz,
  52. });
  53. }
  54. const el = document.createElement("span");
  55. export function escapeHtml(text) {
  56. el.textContent = text;
  57. return el.innerHTML;
  58. }
  59. window.escapeHtml = escapeHtml;
  60. export function convertSecsToHumanReadable(seconds) {
  61. let processedSeconds = seconds;
  62. const oriSeconds = seconds;
  63. const floatingPart = oriSeconds - Math.floor(oriSeconds);
  64. processedSeconds = Math.floor(processedSeconds);
  65. const secondsPerHour = 60 * 60;
  66. const secondsPerMinute = 60;
  67. const hours = Math.floor(processedSeconds / secondsPerHour);
  68. processedSeconds -= hours * secondsPerHour;
  69. const minutes = Math.floor(processedSeconds / secondsPerMinute);
  70. processedSeconds -= minutes * secondsPerMinute;
  71. let readableFormat = "";
  72. if (hours > 0) {
  73. readableFormat += `${hours}Hours `;
  74. }
  75. if (minutes > 0) {
  76. readableFormat += `${minutes}Min `;
  77. }
  78. if (processedSeconds + floatingPart > 0) {
  79. if (Math.floor(oriSeconds) === oriSeconds) {
  80. readableFormat += `${processedSeconds}Sec`;
  81. } else {
  82. processedSeconds += floatingPart;
  83. readableFormat += `${processedSeconds.toFixed(3)}Sec`;
  84. }
  85. }
  86. return readableFormat;
  87. }
  88. window.convertSecsToHumanReadable = convertSecsToHumanReadable;
  89. function postAsForm(url, parameters) {
  90. const form = $("<form></form>");
  91. form.attr("method", "POST");
  92. form.attr("action", url);
  93. $.each(parameters || {}, (key, value) => {
  94. const field = $("<input></input>");
  95. field.attr("type", "hidden");
  96. field.attr("name", key);
  97. field.attr("value", value);
  98. form.append(field);
  99. });
  100. const field = $("<input></input>");
  101. field.attr("type", "hidden");
  102. field.attr("name", "csrf_token");
  103. field.attr("value", csrfToken);
  104. form.append(field);
  105. // The form needs to be a part of the document in order for us to be able
  106. // to submit it.
  107. $(document.body).append(form);
  108. form.submit();
  109. }
  110. window.postAsForm = postAsForm;
  111. function initializeUITimezone() {
  112. const local = moment.tz.guess();
  113. const selectedTz = localStorage.getItem("selected-timezone");
  114. const manualTz = localStorage.getItem("chosen-timezone");
  115. function setManualTimezone(tz) {
  116. localStorage.setItem("chosen-timezone", tz);
  117. if (tz === local && tz === Airflow.serverTimezone) {
  118. $("#timezone-manual").hide();
  119. return;
  120. }
  121. $("#timezone-manual a").data("timezone", tz).text(formatTimezone(tz));
  122. $("#timezone-manual").show();
  123. }
  124. if (manualTz) {
  125. setManualTimezone(manualTz);
  126. }
  127. changeDisplayedTimezone(selectedTz || Airflow.defaultUITimezone);
  128. if (Airflow.serverTimezone !== "UTC") {
  129. $("#timezone-server a").html(
  130. `${formatTimezone(
  131. Airflow.serverTimezone
  132. )} <span class="label label-primary">Server</span>`
  133. );
  134. $("#timezone-server").show();
  135. }
  136. if (Airflow.serverTimezone !== local) {
  137. $("#timezone-local a")
  138. .attr("data-timezone", local)
  139. .html(
  140. `${formatTimezone(local)} <span class="label label-info">Local</span>`
  141. );
  142. } else {
  143. $("#timezone-local").hide();
  144. }
  145. $("a[data-timezone]").click((evt) => {
  146. changeDisplayedTimezone($(evt.currentTarget).data("timezone"));
  147. });
  148. $("#timezone-other").typeahead({
  149. source: $(
  150. moment.tz.names().map((tzName) => {
  151. const category = tzName.split("/", 1)[0];
  152. return { category, name: tzName.replace("_", " "), tzName };
  153. })
  154. ),
  155. showHintOnFocus: "all",
  156. showCategoryHeader: true,
  157. items: "all",
  158. afterSelect(data) {
  159. // Clear it for next time we open the pop-up
  160. this.$element.val("");
  161. setManualTimezone(data.tzName);
  162. changeDisplayedTimezone(data.tzName);
  163. // We need to delay the close event to not be in the form handler,
  164. // otherwise bootstrap ignores it, thinking it's caused by interaction on
  165. // the <form>
  166. setTimeout(() => {
  167. document.activeElement.blur();
  168. // Bug in typeahed, it thinks it's still shown!
  169. this.shown = false;
  170. this.focused = false;
  171. }, 1);
  172. },
  173. });
  174. }
  175. function filterOpSelected(ele) {
  176. const op = $(ele);
  177. const filterVal = $(".filter_val.form-control", op.parents("tr"));
  178. if (op.text() === "Is Null" || op.text() === "Is not Null") {
  179. if (filterVal.attr("required") !== undefined) {
  180. filterVal.removeAttr("required");
  181. filterVal.attr("airflow-required", true);
  182. }
  183. if (filterVal.parent(".datetime").length === 1) {
  184. filterVal.parent(".datetime").hide();
  185. } else {
  186. filterVal.hide();
  187. }
  188. } else {
  189. if (filterVal.attr("airflow-required") === "true") {
  190. filterVal.attr("required", true);
  191. filterVal.removeAttr("airflow-required");
  192. }
  193. if (filterVal.parent(".datetime").length === 1) {
  194. filterVal.parent(".datetime").show();
  195. } else {
  196. filterVal.show();
  197. }
  198. }
  199. }
  200. $(document).ready(() => {
  201. initializeUITimezone();
  202. $("#clock")
  203. .attr("data-original-title", hostName)
  204. .attr("data-placement", "bottom")
  205. .parent()
  206. .show();
  207. displayTime();
  208. setInterval(displayTime, 1000);
  209. $.ajaxSetup({
  210. beforeSend(xhr, settings) {
  211. if (
  212. !/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) &&
  213. !this.crossDomain
  214. ) {
  215. xhr.setRequestHeader("X-CSRFToken", csrfToken);
  216. }
  217. },
  218. });
  219. $.fn.datetimepicker.defaults.sideBySide = true;
  220. $(".datetimepicker").datetimepicker({ format: "YYYY-MM-DDTHH:mm:ssZ" });
  221. $(".datepicker").datetimepicker({ format: "YYYY-MM-DD" });
  222. $(".timepicker").datetimepicker({ format: "HH:mm:ss" });
  223. $(".filters .select2-chosen").each((idx, elem) => {
  224. filterOpSelected(elem);
  225. });
  226. $(".filters .select2-chosen").on("DOMNodeInserted", (e) => {
  227. filterOpSelected(e.target);
  228. });
  229. // Fix up filter fields from FAB adds to the page. This event is fired after
  230. // the FAB registered one which adds the new control
  231. $("#filter_form a.filter").click(() => {
  232. $(".datetimepicker").datetimepicker();
  233. $(".filters .select2-chosen").on("DOMNodeInserted", (e) => {
  234. filterOpSelected(e.target);
  235. });
  236. });
  237. // Global Tooltip selector
  238. $(".js-tooltip").tooltip();
  239. // Turn off autocomplete for login form
  240. $("#username:input")[0].autocomplete = "off";
  241. $("#password:input")[0].autocomplete = "off";
  242. });