YMS Util

Adds Valet-style move notification (DISABLED FOR REIMPLEMENTATION). Changes links next to VRIDs and ISAs (!!BROKEN!!). Visually groups spots with 2 assets (tractor and trailer). Pings Event History for recent TDR release and notifies.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         YMS Util
// @namespace    https://trans-logistics.amazon.com
// @version      2026.01.15
// @description  Adds Valet-style move notification (DISABLED FOR REIMPLEMENTATION). Changes links next to VRIDs and ISAs (!!BROKEN!!). Visually groups spots with 2 assets (tractor and trailer). Pings Event History for recent TDR release and notifies.
// @author       bjerkt@
// @license      MIT
// @match        https://trans-logistics.amazon.com/yms/shipclerk/*
// @match        https://trans-logistics-eu.amazon.com/yms/shipclerk/*
// @grant        GM_addStyle
// @grant        window.focus
// @grant        parent.focus
// ==/UserScript==
(function() {
    'use strict';
     // Handle Tampermonkey running the script too late to catch the loading events
    if (document.readyState != 'complete') {
        window.addEventListener('load', windowLoadedCallback);
    } else {
        windowLoadedCallback();
    }
    function windowLoadedCallback() {
        if (window.location.href.indexOf("shipclerk/#/yard") > -1) {
            // Fix comment button collapsing to a few pixels wide
            document.styleSheets[0].insertRule('.note-present-icon {flex-shrink: 0;}');
            // ISA link to DM: https://fc-inbound-dock-hub-na.aka.amazon.com/en_US/#/dockmaster/appointment/{yard}/view/{ISA}/appointmentDetail
            // VRID link to FMC: https://trans-logistics.amazon.com/fmc/execution/search/{VRID}
            // Add the dashboard
            document.getElementById('mainContainer').insertAdjacentHTML('beforebegin', `
               <div id="dashboard">
               <!--
                   <div>
                       <h2>Notify on TDR Release</h2>
                       <label class="switch">
                           <input id="dingToggle" type="checkbox" checked>
                           <span class="slider round"></span>
                       </label>
                   </div>
                 -->
                   <div>
                       <h2>Highlight Tractor-Trailer Pairs</h2>
                       <label class="switch">
                           <input id="highlightToggle" type="checkbox">
                           <span class="slider round"></span>
                       </label>
                   </div>
               </div>
          ` );
            GM_addStyle ( `
               /* Override move queue dialog width because it's very narrow for some reason */
               div#moveQueueDialog {
                   width: 75vw !important;
               }
               /*SCACs overflow into next row, this fixes*/
               table#ship-clerk-dashboard-table .masterYard td:nth-child(5) > div {
                   overflow: auto;
               }
               table.movementQueueTable > tbody > tr > td:last-child {}
               #dashboard{
                   position:       relative;
                   top:            0px;
                   left:           0px;
                   border-top-style:    solid;
                   border-bottom-style: solid;
                   border-color:   grey;
                   display:        flex;
               }
               /* The switch - the box around the slider */
               .switch {
                   position: relative;
                   display: inline-block;
                   width: 60px;
                   height: 34px;
                }
                /* Hide default HTML checkbox */
                .switch input {
                   opacity: 0;
                   width: 0;
                   height: 0;
                }
                /* The slider */
                .slider {
                   position: absolute;
                   cursor: pointer;
                   top: 0;
                   left: 0;
                   right: 0;
                   bottom: 0;
                   background-color: #ccc;
                   -webkit-transition: .4s;
                   transition: .4s;
                }
                .slider:before {
                   position: absolute;
                   content: "";
                   height: 26px;
                   width: 26px;
                   left: 4px;
                   bottom: 4px;
                   background-color: white;
                   -webkit-transition: .4s;
                   transition: .4s;
                }
                input:checked + .slider {
                   background-color: #2196F3;
                }
                input:focus + .slider {
                   box-shadow: 0 0 1px #2196F3;
                }
                input:checked + .slider:before {
                   -webkit-transform: translateX(26px);
                   -ms-transform: translateX(26px);
                   transform: translateX(26px);
                }
                /* Rounded sliders */
                .slider.round {
                   border-radius: 34px;
                }
                .slider.round:before {
                   border-radius: 50%;
                }
            ` );
//=============================================
// Set up MutationObsevers
//=============================================
            // Is there a better way to detect list changes?
            //document.getElementById('searchInput').addEventListener(search or something)
            var overlayChangeCallback = function(mutationsList, observer) {
                for (var mutation of mutationsList) {
                    // When loading overlay is re-hidden, i.e. all elements are created and populated
                    // THIS IS NOT TRUE, angular performs another digest cycle after hiding overlay, oerwriting everything that goes on here.
                    // TODO: figure out what to do about this. $$PostDigest()?
                    if (mutation.oldValue == 'yms-modal-backdrop ng-scope' && mutation.target.className == 'yms-modal-backdrop ng-scope hidden') {
                        // Add links here
                        /*
                        var loadIdContainers = document.querySelectorAll('div.load-identifiers > div.shipclerk-std-label > span');
                        if (loadIdContainers.length > 0) {
                            addLinks(loadIdContainers);
                        }
                        */
                        // Hook in pair highlighting here
                        if (highlightPairsBool) {
                            highlightPairs()
                        }
                    }
                }
            };
            // Need to wait for loadingMask to be created by angularjs
            // Simple polling of DOM, not ideal but it gets the job done
            var findLoadingMask = new Promise(function (resolve){
                waitForLoadingMask(resolve);
            });
            function waitForLoadingMask(resolve) {
                var loadingMask = document.getElementById('loadingMask');
                if (loadingMask == undefined) {
                    setTimeout(waitForLoadingMask.bind(this, resolve), 500); // Wait 50ms before polling DOM again
                } else {
                    resolve(loadingMask);
                }
            }
            // Watch loading mask to see when list has been built & it's safe to grab elements
            var overlayObserver = new MutationObserver(overlayChangeCallback);
            var overlayObsConfig = { attributes: true, attributeFilter:["class"], attributeOldValue: true };
            findLoadingMask.then(function(loadingMask) {
                overlayObserver.observe(loadingMask, overlayObsConfig);
            });
//=============================================
// Add links to the VRIDs & ISAs
//=============================================
            var yard = document.querySelector('#headerNav > div.header-rightpanel > span.a-text-bold:not(.a-color-link)').textContent.match(/\S{4}/)[0];
            /*
            function addLinks(idConts) {
                var regExVRID = /VRID (\w{9})/; //VRIDs always 9 chars long
                var regExISA = /ISA (\d+)/; //ISAs are indeterminate length (8? now 9?)
                Array.prototype.forEach.call(idConts, function(container, i) {
                    // Don't make a double
                    if (container.querySelector('a.yms-util-link') === null) {
                        var vrid = regExVRID.exec(container.innerText);
                        var isa = regExISA.exec(container.innerText);
                        if (vrid) {
                            requestAnimationFrame(function() {
                                var sspLink = container.querySelector('span > a');
                                sspLink.target = "_blank";
                                var a = document.createElement('a');
                                var linkText = document.createTextNode("FMC");
                                a.appendChild(linkText);
                                a.title = "Open VRID in FMC";
                                a.href = "https://trans-logistics.amazon.com/fmc/execution/search/" + vrid[1];
                                a.target = "_blank";
                                a.classList.add('yms-util-link');
                                container.appendChild(a);
                            });
                        } else if (isa) {
                            requestAnimationFrame(function() {
                                // Change vanilla DM link to regular text
                                var dmLink = container.querySelector('a');
                                var txt = document.createElement('span');
                                txt.innerHTML = dmLink.innerHTML;
                                container.children[0].insertBefore(txt, dmLink);
                                container.children[0].removeChild(dmLink);
                                // Add my own DM link
                                var b = document.createElement('a');
                                var bc = document.createTextNode('DM');
                                b.appendChild(bc);
                                b.title = "Open ISA in DockMaster";
                                b.href = "https://fc-inbound-dock-hub-na.aka.amazon.com/en_US/#/dockmaster/appointment/" + yard + "/view/" + isa[1] + "/appointmentDetail";
                                b.target = "_blank";
                                b.classList.add('yms-util-link');
                                container.appendChild(b);
                            });
                        }
                    }
                });
            };
            */
//=============================================
// Highlight tractor-trailer pairs
//=============================================
            var highlightPairsBool = false;
            document.getElementById('highlightToggle').addEventListener('change', function(event) {
               if (this.checked) {
                   highlightPairs();
                   highlightPairsBool = true;
               } else {
                   // Clear any highlighted rows
                   var rows = document.querySelectorAll('tbody.masterYard > tr');
                   Array.prototype.forEach.call(rows, function(row, i) {
                       row.removeAttribute('style');
                   });
                   highlightPairsBool = false;
               }
            });;
            function highlightPairs() {
                var dblHeightRows = document.querySelectorAll('tr > td.col2[rowspan="2"]');
                if (dblHeightRows && dblHeightRows.length > 0) {
                    var colors = [];
                    var huedelta = Math.trunc(360 / dblHeightRows.length);
                    for (let j = 0; j < dblHeightRows.length; j++) {
                        var hue = j * huedelta;
                        colors.push('hsla('+hue+',100%,50%, 0.5)');
                    }
                    Array.prototype.forEach.call(dblHeightRows, function(col, i) {
                        // Make sure it's a tractor & trailer
                        if (col.children[0].childElementCount == 2 && col.children[0].children[0].children[0].classList.value.match(/TRAILER/) && col.children[0].children[1].children[0].classList.value.match(/TRACTOR/)) {
                            var row = col.parentNode;
                            var nextRow = getNextSibling(row, 'tr');
                            var color = colors.pop();
                            row.style.setProperty('background-color', color, 'important');
                            nextRow.style.setProperty('background-color', color, 'important');
                            row.style.borderTop = '5px solid #722727';
                            nextRow.style.borderBottom = '5px solid #722727';
                        }
                    });
                }
            }
            var getNextSibling = function (elem, selector) {
	            // Get the next sibling element
	            var sibling = elem.nextElementSibling;
	            // If the sibling matches our selector, use it
	            // If not, jump to the next sibling and continue the loop
	            while (sibling) {
	            	if (sibling.matches(selector)) return sibling;
	            	sibling = sibling.nextElementSibling
            	}
            };
//=============================================
// Notification on new move detected
//=============================================
            /*
            var hasNotifyLock = false;
            var shouldNotifyToggle = true;
            var movePoll = setInterval(checkMoves ,60000); // Poll for moves every 60 seconds. Upped from 10sec by YardTech request
            var moves = {"movements": []};
            document.getElementById('dingToggle').addEventListener('change', function(event) {
                if (document.getElementById('dingToggle').checked) {
                    shouldNotifyToggle = true;
               } else {
                   shouldNotifyToggle = false;
               }
            });
            var notifySound = new Audio("https://raw.githubusercontent.com/ipython-contrib/jupyter_contrib_nbextensions/master/src/jupyter_contrib_nbextensions/nbextensions/notify/notify.mp3");

            function checkMoves() {
                var postUrl = "https://yms-na.amazon.com/YMSServiceInternal/";
                var postHeaders = {"content-encoding": "amz-1.0",
                                  "Content-Type": "application/json",
                                  "X-Amz-Target": "com.amazon.yms.coral.privateapi.YMSServiceInternal.listHostlerMoves"}
                var postBody = {method: "com.amazon.yms.coral.privateapi.YMSServiceInternal.listHostlerMoves",
                                context: {securityToken: ymsSecurityToken}}
                jQuery.ajax({
                    method: "POST",
                    cache: false,
                    url: postUrl,
                    headers: postHeaders,
                    processData: true,
                    data: JSON.stringify(postBody),
                    dataType: "json"
                })
                .done(function(response) {
                    // Check if moves have been added (more or different)
                    // Any unregistered moves? If response has entries not in moves...if response is not complete subset of moves
                    // Shouldn't even have to worry about count except for short-circuiting to bypass subset calculation
                    if ((response.movements.length > moves.movements.length) || (!isSubset(response.movements, moves.movements))) {
                        // Can this instance get the notification lock?
                        if(!cookieExists() && !hasNotifyLock) {
                            setCookie(1);
                            hasNotifyLock = true;
                        }
                        // Do we have notification lock? Does user want notification (toggle switch)?
                        if(cookieExists() && hasNotifyLock && shouldNotifyToggle) {
                            // Notify user of new moves
                            let promise = notifySound.play();
                            if (promise !== undefined) {
                                promise.catch(function(e) {
                                    console.log('Error playing notification sound:\n' + e.message);
                                });
                            }
                            // Make a notification if we don't have focus
                            if (!document.hasFocus()) {
                                buildNotification(response);
                            }
                        }
                    }
                    // Sould we bother touching DOM
                    if (response.movements.length != moves.movements.length) {
                        // Set hostler moves in ui to new move count
                        document.querySelector("div.mainAlert[ng-click='topbar.showHostlerMovesDialog()'] > div").textContent = response.movements.length;
                    }
                    moves = response;
                })
                .fail(function(jqXHRObj, errorMsg) {console.log('listHostlerMoves fail.'); console.log(errorMsg);});
            }
            var isSubset = function(a, b) {
                // Compare array a to array b
                // True if all elements of a are found in b
                for (var i = 0; i < a.length; i++) {
                    var isPresent = false
                    for (var j = 0; j < b.length; j++) {
                        if (a[i].toString() == b[j].toString()) {
                            isPresent = true;
                            break;
                        }
                    }
                    if (!isPresent) {return false;}
                }
                return true;
            }
            var buildNotification = function(response) {
                if (Notification.permission !== "granted") {
                    Notification.requestPermission();
                }
                else {
                    var moveStr = 'Moves: ' + response.movements.length + '\n\n';
                    Array.prototype.forEach.call(response.movements, function(move, i){
                        var src = move.sourceLocation.name;
                        var dst = move.targetLocation.name;
                        var tlr = move.yardAssets[0].owner.code;
                            moveStr += tlr + ': ' + src + ' -> ' + dst + '\n';
                    });

                    var notifyIcon = 'https://upload.wikimedia.org/wikipedia/commons/0/0f/Move_icon.svg';
                    // new notifications with same tag replace instead of stack
                    var notification = new Notification('Hostler', {body: moveStr, icon: notifyIcon, tag: 'movesNotify'});
                    notification.onclick = function () {
                        parent.focus();
                        window.focus(); //just in case, older browsers
                        this.close();
                    };
                    // Timeout 5 sec
                    setTimeout(function() {notification.close()}, 5000);
                }
            }
            function setCookie(exdays) {
                var d = new Date();
                d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
                var expires = "expires="+d.toUTCString();
                document.cookie = 'notification-lock' + "=" + 'locked' + ";" + expires + ";path=/";
            }
            function getCookie() {
                var name = 'notification-lock' + '=';
                var ca = document.cookie.split(';');
                for(var i = 0; i < ca.length; i++) {
                    var c = ca[i];
                    while (c.charAt(0) == ' ') {
                        c = c.substring(1);
                    }
                    if (c.indexOf(name) == 0) {
                        return c.substring(name.length, c.length);
                    }
                }
                return "";
            }
            function cookieExists() {
                var c = getCookie();
                if (c != "") {
                    return true;
                } else {
                    return false;
                }
            }
            // Release lock on window exit
            window.addEventListener('unload', function() {
                setCookie(0);
                hasNotifyLock = false;
            });
            */
//=============================================
// Batch rename dialog
//=============================================
            /*
            var assetsBySCAC = {};
            document.getElementById('batchRenameDialogBtn').addEventListener('click', openRenameDialog);
            document.getElementById('batchRenameBtn').addEventListener('click', function() {
                try {
                    // Commit changes
                    var scacsSelected = document.getElementById('scacList').selectedOptions;
                    var reasonSelected = document.getElementById('reasonList').value;
                    // Loop through all selected SCACs
                    Array.prototype.forEach.call(scacsSelected, function(option) {
                        // Loop through all recorded assets with selected SCAC
                        Array.prototype.forEach.call(assetsBySCAC[option.value], function(asset) {
                            var updateModel = buildUpdateModel(asset, reasonSelected.toUpperCase());
                            var postUrl = "https://yms-na.amazon.com/YMSServiceInternal/";
                            var postHeaders = {"content-encoding": "amz-1.0",
                                              "Content-Type": "application/json",
                                              "X-Amz-Target": "com.amazon.yms.coral.privateapi.YMSServiceInternal.updateAsset"}
                            var postBody = {method: "com.amazon.yms.coral.privateapi.YMSServiceInternal.updateAsset",
                                            updateModel: updateModel,
                                            context: {securityToken: ymsSecurityToken}}
                            jQuery.ajax({
                                method: "POST",
                                cache: false,
                                url: postUrl,
                                headers: postHeaders,
                                processData: true,
                                data: JSON.stringify(postBody),
                                dataType: "json"
                            })
                            .done(function(response) {
                                 console.log('updateAsset success.');
                            })
                            .fail(function(jqXHRObj, textStatus, errorMsg) {console.log('updateAsset fail.'); console.log(textStatus); console.log(errorMsg);});
                        });
                    });
                    document.getElementById('rightMenu').querySelector('.ui-refresh-icon').click();
                } catch(e) {
                    console.log('caught error:')
                    console.log(e);
                } finally {closeRenameOverlay();}
            });
            document.getElementById('batchRenameCancelBtn').addEventListener('click', closeRenameOverlay);
            function openRenameDialog() {
                // Initialize display
                document.getElementById('renameDialog').style.display = 'flex';
                document.getElementById('reasonList').selectedIndex = -1;
                var postUrl = "https://yms-na.amazon.com/YMSServiceInternal/";
                var postHeaders = {"content-encoding": "amz-1.0",
                                  "Content-Type": "application/json",
                                  "X-Amz-Target": "com.amazon.yms.coral.privateapi.YMSServiceInternal.listYardAssetsInYard"}
                var postBody = {method: "com.amazon.yms.coral.privateapi.YMSServiceInternal.listYardAssetsInYard",
                                context: {securityToken: ymsSecurityToken}}
                jQuery.ajax({
                    method: "POST",
                    cache: false,
                    url: postUrl,
                    headers: postHeaders,
                    processData: true,
                    data: JSON.stringify(postBody),
                    dataType: "json"
                })
                .done(function(response) {
                    assetsBySCAC = {};
                    var scacs = [];
                    // Group assets by SCAC, must have a blank visit reason
                    Array.prototype.forEach.call(response.yardAssets, function(asset) {
                        if (asset.visitReason == null) {
                            if (assetsBySCAC[asset.owner.code]) {
                                assetsBySCAC[asset.owner.code].push(asset);
                            } else {
                                assetsBySCAC[asset.owner.code] = [asset];
                            }
                        }
                    });
                    // Add SCACs to listbox, clearing out any existing data
                    scacs = Object.keys(assetsBySCAC).sort();
                    var list = document.getElementById('scacList');
                    if (list.hasChildNodes()) {
                        requestAnimationFrame(function() {
                            for(var i = list.options.length-1; i >= 0; i--) {
                                list.remove(i);
                            }
                        });
                    }
                    requestAnimationFrame(function() {
                        Array.prototype.forEach.call(scacs, function(scac) {
                            var option = document.createElement('option')
                            option.text = scac;
                            list.add(option);
                        });
                    });
                })
                .fail(function(jqXHRObj, errorMsg) {console.log('listYardAssetsInYard fail.'); console.log(errorMsg);});
            }
            function closeRenameOverlay() {document.getElementById('renameDialog').style.display = 'none';};
            function buildUpdateModel(asset, visitString) {
                var sealNum = [];
                if (asset.sealNumbers.length > 0) {sealNum = [asset.sealNumbers[0]]}
                var model = {
                    assetType: asset.type,
                    isSealable: asset.isSealable,
                    expectedNumberOfSeals: asset.expectedNumberOfSeals,
                    owner: {
                        code: asset.owner.code,
                        name: asset.owner.name
                    },
                    vehicleNumber: asset.vehicleNumber,
                    visitReason: visitString,
                    rangeOfSeals: [0],
                    sealNumbers: sealNum,
                    status: asset.status,
                    broker: {
                        originalObject: {
                            code: asset.broker.code,
                            name: asset.broker.name
                        }
                    },
                    ownerCode: asset.owner.code,
                    brokerCode: asset.brokerCode,
                    id: asset.id,
                    removeLoad: false,
                    annotationsToBeAdded: {}
                };
                return model;
            }
            */
//=============================================
// Notification on TDR release
//=============================================

            var tdrNotifySound = new Audio("https://drive.corp.amazon.com/view/bjerkt@/Tools%20-%20Mine/Userscripts/res/tdr-notify.mp3");
            let postUrlRegional = window.location.href.indexOf('trans-logistics-eu') > 0 ? "https://yms-eu.amazon.com/YMSServiceInternal/" : "https://yms-na.amazon.com/YMSServiceInternal/"
            var tdrPoll = setInterval(checkTDR, 10000); // Poll for new TDR release events every 10 seconds
            function checkTDR() {
                const dateFull = new Date();
                const dtEnd = Math.floor(dateFull.getTime()/1000) // Round to seconds
                const dtStart = dtEnd - 9; // Look back 9 seconds (was getting duplicate events with a 10/10 setup)
                const postHeaders = {"api": "getEventReport",
                                  "Content-Type": "application/json;charset=UTF-8",
                                   "method": "POST",
                                  "token": ymsSecurityToken}
                const postBody = {annotation: "",
                                eventType: "TDR_RELEASE",
                                firstRow: 0,
                                fromDate: dtStart,
                                loadIdentifier: "",
                                loadIdentifierType: "VRID",
                                location: "",
                                requester: {system: "YMSWebApp"},
                                rowCount: 20,
                                seal: "",
                                toDate: dtEnd,
                                userId: "",
                                vehicleNumber: "",
                                vehicleOwner: "",
                                vehicleType: "",
                                visitReason: "OUTBOUND",
                                yard: yard
                               }
                jQuery.ajax({
                    method: "POST",
                    cache: false,
                    url: yardConsoleServiceEndpoint + 'call/getEventReport',
                    headers: postHeaders,
                    processData: true,
                    data: JSON.stringify(postBody),
                    dataType: "json"
                })
                .done(function(response) {
                    if (response.events.length > 0) {
                        // Notify user of TDR event(s)
                        const promise = tdrNotifySound.play()
                        if (promise !== undefined) {
                            promise.catch(function(e) {
                                console.log('Error playing notification sound:\n' + e.message);
                            });
                        }
                        // Build notification
                        if (Notification.permission !== "granted") {
                            Notification.requestPermission();
                        } else {
                            var bodyStr = "";
                            Array.prototype.forEach.call(response.events, function(event, i) {
                                bodyStr += event.location;
                                // If we have other doors to add...
                                if (i < response.events.length-1) {
                                    bodyStr += '\n';
                                }
                            });
                            var notifyIcon = 'https://d36nckxftc4f80.cloudfront.net/YMSWebsiteAngularApp-3_0_201127_0/assets/images/dock.png';
                            var notification = new Notification('TDR Release', {body: bodyStr, icon: notifyIcon, tag: 'tdrNotify'});
                            notification.onclick = function() {
                                patrent.focus();
                                window.focus(); // For older browsers
                                this.close();
                            }
                            // Timeout 5 sec
                            setTimeout(function() {notification.close()}, 5000);
                        }
                    }
                })
                .fail(function(jqXHRObj, errorMsg) {console.log('getTDRStatus fail.'); console.log(errorMsg);});
            }
        }
    };
})();