PageSourceSearch

https://napoparte-cmms.hf.space/assets/maint_settings_btn_lock.js?m=1789806719.0

js napoparte-cmms.hf.space collected 2026-10-03 17:10:02 UTC 4,894 bytes, 100 lines download raw bytes

1// maint_settings_btn_lock.js
2//
3// (FIX 2026-08 <chống spam click — Auto PM Settings>) Tắt (khoá) NGAY LẬP
4// TỨC 3 nút hành động trong tab "Auto PM" (Settings > Bảo trì) khi vừa bấm,
5// trước khi server kịp phản hồi — tránh user bấm liên tục trong lúc callback
6// server đang chạy (VD auto-PM quét/áp dụng nhiều thiết bị mất vài giây).
7//
8// ⚠️ ROOT CAUSE của bug "<2> nút không được bật lại sau khi lỗi" (2026-08):
9// Bản cũ set thẳng `el.disabled = true` bằng JS thuần. React (bên trong
10// Dash) vẫn nghĩ prop "disabled" của nút là False (vì nó không biết JS bên
11// ngoài vừa đổi DOM thật). Khi callback server chạy xong và trả về
12// Output("disabled", False) — CÙNG giá trị False mà React đang lưu trong bộ
13// nhớ — React so sánh thấy "không đổi" nên KHÔNG động vào DOM thật nữa =>
14// nút bị kẹt disabled=true vĩnh viễn trên DOM dù Dash nghĩ nó đã là False.
15// Đây là lỗi kinh điển khi mutate trực tiếp 1 DOM property mà React đang
16// quản lý (React/Dash không hề biết để đồng bộ lại).
17//
18// FIX: không đụng vào thuộc tính "disabled" thật nữa. Khoá bằng 1 class CSS
19// (pointer-events: none + giảm opacity) — hoàn toàn tách biệt khỏi state
20// React đang theo dõi, nên không bao giờ bị desync. Mở khoá bằng cách theo
21// dõi (MutationObserver) khối kết quả (Alert) tương ứng của mỗi nút — server
22// LUÔN ghi nội dung mới vào đúng khối đó ở MỌI nhánh xử lý (thành công/lỗi/
23// không đủ quyền...), nên đây là tín hiệu đáng tin cậy nhất báo "callback đã
24// chạy xong", không phụ thuộc vào thuộc tính "disabled".
25//
26// Cố tình KHÔNG dùng Dash clientside_callback cho việc khoá/mở khoá vì các
27// nút này đã có callback SERVER ghi vào đúng Output("disabled",...) đó (cho
28// mục đích khác — VD readonly theo quyền) — thêm 1 clientside callback nữa
29// cùng Output sẽ gây lỗi "Duplicate callback outputs" (đã gặp thực tế).
30(function () {
31  // id nút -> id khối kết quả (Alert) mà server luôn cập nhật sau khi xử lý xong
32  var LOCK_MAP = {
33    "maint-auto-run-now-btn": "maint-auto-run-result",
34    "maint-rebalance-scan-btn": "maint-rebalance-preview",
35    "maint-rebalance-apply-btn": "maint-rebalance-result",
36  };
37  var LOCK_IDS = Object.keys(LOCK_MAP);
38  var LOCK_CLASS = "asm-btn-locked";
39  // Khoá cứng tối đa (an toàn nếu vì lý do nào đó không nhận được tín hiệu
40  // mutation — VD lỗi mạng khiến request không bao giờ hoàn tất) để nút
41  // không bị kẹt vĩnh viễn.
42  var SAFETY_UNLOCK_MS = 30000;
43
44  function unlock(el) {
45    el.classList.remove(LOCK_CLASS);
46    if (el._asmLockTimeout) {
47      clearTimeout(el._asmLockTimeout);
48      el._asmLockTimeout = null;
49    }
50  }
51
52  document.addEventListener(
53    "click",
54    function (e) {
55      for (var i = 0; i < LOCK_IDS.length; i++) {
56        var id = LOCK_IDS[i];
57        var el = e.target.closest ? e.target.closest("#" + id) : null;
58        if (!el) continue;
59        if (el.disabled || el.classList.contains(LOCK_CLASS)) {
60          // Đã bị disable thật (VD readonly/không đủ quyền) hoặc đang khoá
61          // chờ server — chặn không cho Dash nhận thêm click này.
62          e.stopPropagation();
63          e.preventDefault();
64          return;
65        }
66        el.classList.add(LOCK_CLASS);
67        el._asmLockTimeout = setTimeout(function (elRef) {
68          return function () { unlock(elRef); };
69        }(el), SAFETY_UNLOCK_MS);
70      }
71    },
72    true // capture phase — chạy trước khi Dash xử lý sự kiện click
73  );
74
75  function watchResult(btnId, resultId) {
76    var resultEl = document.getElementById(resultId);
77    if (!resultEl || resultEl._asmLockObserver) return;
78    var obs = new MutationObserver(function () {
79      var btnEl = document.getElementById(btnId);
80      if (btnEl) unlock(btnEl);
81    });
82    obs.observe(resultEl, { childList: true, subtree: true, characterData: true });
83    resultEl._asmLockObserver = obs;
84  }
85
86  function attachAll() {
87    for (var id in LOCK_MAP) {
88      watchResult(id, LOCK_MAP[id]);
89    }
90  }
91
92  // Modal Auto PM có thể mở/đóng nhiều lần trong 1 phiên — Dash re-mount lại
93  // các node này mỗi lần mở lại (MutationObserver cũ gắn trên node cũ sẽ
94  // không còn hiệu lực), nên cần tự gắn lại observer mỗi khi khối kết quả
95  // xuất hiện lại trong DOM.
96  var bodyObserver = new MutationObserver(attachAll);
97  bodyObserver.observe(document.body, { childList: true, subtree: true });
98  document.addEventListener("DOMContentLoaded", attachAll);
99  attachAll();
100})();

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.