123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746 |
- {% extends "base.html" %}
- {% set active_page = "assets" %}
- {% block title %} {{asset.name}} {% endblock %}
- {% block divs %}
- {% block breadcrumbs %} {{ super() }} {% endblock %}
- <div class="container-fluid">
- <div class="row mx-1">
- <div class="alert alert-info d-none" id="tzwarn"></div>
- <div class="alert alert-info d-none" id="dstwarn"></div>
- </div>
- <div class="row">
- <div class="col-md-2 on-top-md">
- <div class="header-action-button">
- <div>
- <button class="btn btn-primary" type="button" data-bs-toggle="modal"
- data-bs-target="#sensorsToShowModal">
- Edit Graphs
- </button>
- </div>
- </div>
- <div class="sidepanel-container ">
- <div class="left-sidepanel-label">Select dates</div>
- <div class="sidepanel left-sidepanel">
- <div id="datepicker"></div>
- </div>
- </div>
-
- </div>
- <div class="col-md-8">
- <div id="spinner">
- <i class="fa fa-spinner fa-pulse fa-3x fa-fw"></i>
- <span class="sr-only">Loading...</span>
- </div>
- <div id="sensorchart" class="card" style="width: 100%;"></div>
- <div class="row">
- <div class="copy-url" title="Click to copy the URL to the current time range to clipboard.">
- <script>
- function toIsoString(date) {
- var tzo = -date.getTimezoneOffset(),
- dif = tzo >= 0 ? '+' : '-',
- pad = function (num) {
- return (num < 10 ? '0' : '') + num;
- };
- return date.getFullYear() +
- '-' + pad(date.getMonth() + 1) +
- '-' + pad(date.getDate()) +
- 'T' + pad(date.getHours()) +
- ':' + pad(date.getMinutes()) +
- ':' + pad(date.getSeconds()) +
- dif + pad(Math.floor(Math.abs(tzo) / 60)) +
- ':' + pad(Math.abs(tzo) % 60);
- }
- $(window).ready(() => {
- picker.on('selected', (startDate, endDate) => {
- startDate = encodeURIComponent(toIsoString(startDate.toJSDate()));
- endDate = encodeURIComponent(toIsoString(endDate.toJSDate()));
- var base_url = window.location.href.split("?")[0];
- var new_url = `${base_url}?start_time=${startDate}&end_time=${endDate}`;
- // change current url without reloading the page
- window.history.pushState({}, null, new_url);
- });
- });
- function copyUrl(event) {
- event.preventDefault();
- if (!window.getSelection) {
- alert('Please copy the URL from the location bar.');
- return;
- }
- const dummy = document.createElement('p');
- var startDate = encodeURIComponent(toIsoString(picker.getStartDate().toJSDate()));
- // add 1 day to end date as datepicker does not include the end date day
- var endDate = picker.getEndDate();
- endDate.setDate(endDate.getDate() + 1);
- endDate = encodeURIComponent(toIsoString(endDate.toJSDate()));
- var base_url = window.location.href.split("?")[0];
- dummy.textContent = `${base_url}?start_time=${startDate}&end_time=${endDate}`
- document.body.appendChild(dummy);
- const range = document.createRange();
- range.setStartBefore(dummy);
- range.setEndAfter(dummy);
- const selection = window.getSelection();
- // First clear, in case the user already selected some other text
- selection.removeAllRanges();
- selection.addRange(range);
- document.execCommand('copy');
- document.body.removeChild(dummy);
- $("#message").show().delay(1000).fadeOut();
- }
- </script>
- <a href="#" onclick="copyUrl(event)" style="display: block; text-align: center;">
- <i class="fa fa-link"></i>
- </a>
- <div id="message" style="display: none; text-align: center;">The URL to the time range currently
- shown has been copied to your clipboard.</div>
- </div>
- </div>
-
- </div>
- <div class="col-md-2">
- <div class="replay-container">
- <div id="replay" title="Press 'p' to play/pause/resume or 's' to stop." class="stopped"></div>
- <div id="replay-time"></div>
- </div>
- </div>
- </div>
- <!-- Modal -->
- <div class="modal fade modal-xl" id="sensorsToShowModal" tabindex="-1" aria-labelledby="sensorsToShowModalLabel"
- aria-hidden="true">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title pe-2">Edit Dashboard Graphs</h5>
- <div class="dropdown" data-bs-auto-close="outside">
- <span class="fa fa-info dropdown-toggle" role="button" data-bs-toggle="dropdown"
- aria-expanded="false" rel="tooltip" aria-hidden="true" tabindex="0"
- data-bs-placement="right" data-bs-toggle="tooltip"></span>
- <div class="dropdown-menu p-3" style="width: 400px;">
- <div>
- <strong>Dashboard Graphs Help</strong>
- <p class="mb-2">
- Here you can edit what data is shown in the Dashboard Graphs. Each graph can show
- one or more sensors
- (<em>it makes sense to show sensors in a graph that share the same unit</em>).
- </p>
- <p class="mb-2">
- You can also set the title of each graph and re-order them. Select a graph
- <span class="text-primary">(by clicking on its card)</span> to add sensors to it.
- </p>
- <p class="mb-0">
- Sensors can be searched on the right. This will list sensors on the asset or its
- child asset, as well as public assets.
- </p>
- </div>
- </div>
- </div>
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
- </div>
- <div class="modal-body">
- <div class="row">
- <div class="col">
- <button class="btn btn-primary mb-3" onclick="addNewGraph()">
- Add Graph
- </button>
- <div id="graphList" class="row"></div>
- </div>
- <div class="col">
- <div class="row mb-3">
- <div class="col-8">
- <input type="text" id="searchInput" class="form-control"
- placeholder="Search sensors..." oninput="filterSensors()" />
- </div>
- <div class="col-4">
- <select id="unitsSelect" class="form-select" onchange="filterSensors()">
- <option selected>Units</option>
- {% for unit in available_units %}
- <option>{{ unit }}</option>
- {% endfor %}
- </select>
- </div>
- </div>
- <div class="container">
- <div id="spinnerElement" class="d-flex justify-content-center align-items-center mb-2"
- style="height: 50vh; display: none !important;">
- <div class="spinner-border text-primary" role="status"
- style="width: 4rem; height: 4rem;">
- <span class="visually-hidden">Loading...</span>
- </div>
- </div>
- <div id="apiSensorsList" class="row" style="display: none;"></div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jstimezonedetect/1.0.7/jstz.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/litepicker/dist/litepicker.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/litepicker/dist/plugins/ranges.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/litepicker/dist/plugins/keyboardnav.js"></script>
- {% block leftsidepanel %} {{ super() }} {% endblock %}
- {% block sensorChartSetup %} {{ super() }} {% endblock %}
- <!-- Initialise the map -->
- <script src="https://cdn.jsdelivr.net/npm/leaflet@1.7.1/dist/leaflet-src.min.js"></script>
- <script src="{{ url_for('flexmeasures_ui.static', filename='js/map-init.js') }}"></script>
- <script>
- const senSearchResEle = document.getElementById("sensorsSearchResults")
- const formModal = document.getElementById('sensorsToShowModal');
- const apiSensorsListElement = document.getElementById("apiSensorsList");
- const spinnerElement = document.getElementById('spinnerElement');
- let sensorsToShowRawJSON = "{{ asset.sensors_to_show | safe }}";
- sensorsToShowRawJSON = sensorsToShowRawJSON.replace(/'/g, '"');
- let sensorsToShow = JSON.parse(sensorsToShowRawJSON);
- let cachedFilteredSensors = []; // keeps track of the filtered sensors
- let editingIndex; // keeps track of which graph we are currently editing
- let savedGraphIndex; // keeps track of the graph that is currently selected
- let selectedGraphTitle; // keeps track of the graph title that is currently selected
- const apiBasePath = window.location.origin;
- const sensorsApiUrl = `${apiBasePath}/api/v3_0/sensors?page=1&per_page=100&asset_id={{ asset.id }}&include_public_assets=true`;
- // Fetch Account Details
- async function getAccount(accountId) {
- const cacheKey = `account_${accountId}`;
- const cachedData = localStorage.getItem(cacheKey);
- if (cachedData) {
- return JSON.parse(cachedData);
- }
- const apiUrl = apiBasePath + "/api/v3_0/accounts/" + accountId;
- const response = await fetch(apiUrl);
- const account = await response.json();
- localStorage.setItem(cacheKey, JSON.stringify(account));
- return account;
- }
- // Fetch Asset Details
- async function getAsset(assetId) {
- const cacheKey = `asset_${assetId}`;
- const cachedData = localStorage.getItem(cacheKey);
- if (cachedData) {
- return JSON.parse(cachedData);
- }
- const apiUrl = apiBasePath + "/api/v3_0/assets/" + assetId;
- const response = await fetch(apiUrl);
- const asset = await response.json();
- localStorage.setItem(cacheKey, JSON.stringify(asset));
- return asset;
- }
- // Fetch Sensor Details
- async function getSensor(id) {
- const cacheKey = `sensor_${id}`;
- const cachedData = localStorage.getItem(cacheKey);
- if (cachedData) {
- return JSON.parse(cachedData);
- }
- const apiUrl = apiBasePath + "/api/v3_0/sensors/" + id;
- const response = await fetch(apiUrl);
- const sensor = await response.json();
- localStorage.setItem(cacheKey, JSON.stringify(sensor));
- return sensor;
- }
- // highlight selected graph
- async function selectGraph(graphIndex) {
- if (graphIndex !== undefined) {
- savedGraphIndex = graphIndex;
- selectedGraphTitle = sensorsToShow[graphIndex]?.title;
- // check if graphIndex still exists(This is because this function is called when the removed button is clicked as well)
- if (sensorsToShow[graphIndex]) {
- renderApiSensors(cachedFilteredSensors, graphIndex);
- } else {
- renderApiSensors(cachedFilteredSensors);
- }
- } else {
- savedGraphIndex = undefined;
- selectedGraphTitle = undefined;
- renderGraphCards();
- }
- }
- // Function to render the graph cards
- async function renderGraphCards() {
- const graphList = document.getElementById("graphList");
- graphList.innerHTML = "";
- const newSensorsToShow = [];
- if (sensorsToShow.length === 0) {
- return;
- }
- for (const [index, item] of sensorsToShow.entries()) {
- const col = document.createElement("div");
- col.classList.add("col-12", "mb-1");
- const sensorsUnits = [];
- // the initializing of the newItem is to handle the case where the item is an array of ID's instead of an object
- const newItem = {
- title: item.title ?? "No Title",
- sensors: item.sensors ?? (Array.isArray(item) ? item : [item]),
- }
- newSensorsToShow.push(newItem);
- const sensorsContent = await Promise.all(
- newItem.sensors.map(async (sensor, sensorIndex) => {
- const sensorData = await getSensor(sensor);
- const Asset = await getAsset(sensorData.generic_asset_id);
- const Account = await getAccount(Asset.account_id);
- sensorsUnits.push(sensorData.unit);
- return `
- <div class="p-1 mb-3 border-bottom border-secondary">
- <div class="d-flex justify-content-between">
- <div>
- <b>ID:</b> <a href="${apiBasePath}/sensors/${sensorData.id}">${sensorData.id}</a>,
- <b>Unit:</b> ${sensorData.unit},
- <b>Name:</b> ${sensorData.name},
- <div style="padding-top: 1px;"></div>
- <b>Asset:</b> ${Asset.name},
- <b>Account:</b> ${Account?.name ? Account.name : "PUBLIC"}
- </div>
- <i class="fa fa-times"
- onclick="removeSensorFromGraph(${index}, ${sensorIndex})"
- data-bs-toggle="tooltip"
- data-bs-placement="top"
- title="Remove Sensor"
- style="cursor: pointer;"
- ></i>
- </div>
- </div>`;
- }));
- const uniqueUnits = [...new Set(sensorsUnits)];
- col.innerHTML = `
- <div class="card m-0 p-1">
- <div class="card-body card-highlight ${newItem.title === selectedGraphTitle ? " border-on-click" : ""}" id="graph_${index}" onclick="selectGraph(${index})">
- <div class="d-flex align-items-center">
- <div>
- ${editingIndex === index
- ? `<input type="text" class="form-control mb-2 me-2" id="editTitle_${index}" value="${newItem.title}" onkeydown="handleEnterKeyEventForTitleEditing(event, ${index})" />`
- : `<h5 class="card-title me-2">${newItem.title}</h5>`
- }
- </div>
- <div>
- ${editingIndex === index
- ? `<button class="btn btn-success btn-sm ms-2" id="saveTitleBtn" onclick="saveGraphTitle(${index})">Save</button>`
- : `<button class="btn btn-warning btn-sm ms-2" id="editTitleBtn" onclick="editGraphTitle(${index})">Edit</button>`
- }
- </div>
- </div>
- <h5 class="card-title pt-2"><b> Sensors: </b></h5>
- <div>
- ${sensorsContent.length > 0 ? sensorsContent.join("") : `<div class="alert alert-warning" role="alert"> No sensors added to this graph. Add sensors by selecting them from the right</div>`}
- ${uniqueUnits.length > 1 ? `<div class="alert alert-warning" role="alert">Note that you are showing sensors with different units</div>` : ""}
- </div>
- <button class="btn btn-danger btn-sm me-2" onclick="(function(e) { e.stopPropagation(); removeGraph(${index}); })(event)">Remove</button>
- <button class="btn btn-secondary btn-sm me-2" onclick="(function(e) { e.stopPropagation(); moveGraphUp(${index}); })(event)" ${index === 0 ? "disabled" : ""}>Move Up</button>
- <button class="btn btn-secondary btn-sm" onclick="(function(e) { e.stopPropagation(); moveGraphDown(${index}); })(event)" ${index === sensorsToShow.length - 1 ? "disabled" : ""}>Move Down</button>
- </div>
- </div>`;
- graphList.appendChild(col);
- }
- sensorsToShow = newSensorsToShow;
- }
- async function filterSensors() {
- const searchValue = document.getElementById("searchInput").value.toLowerCase();
- const filterValue = document.getElementById("unitsSelect").value;
- const highlightedCard = document.querySelector('.border-on-click');
- spinnerElement.style.display = 'flex';
- apiSensorsListElement.style.display = 'none';
- // Due to the nature of async functions, the highlightedCard might not be available
- // when the filterSensors function is called. So, we need to check if it exists
- if (highlightedCard) {
- const cardId = highlightedCard.id;
- const index = cardId.split("_")[1];
- savedGraphIndex = index;
- selectedGraphTitle = sensorsToShow[index].title;
- } else {
- savedGraphIndex = undefined;
- selectedGraphTitle = undefined;
- }
- // check if apiSensorsList has been rendered
- if (apiSensorsListElement.innerHTML === "") {
- document.getElementById('apiSensorsList').style.display = 'block';
- }
- const params = new URLSearchParams();
- if (searchValue) {
- params.append('filter', searchValue);
- }
- if (filterValue !== "Units") {
- params.append('unit', filterValue);
- }
- const apiUrl = `${sensorsApiUrl}&${params.toString()} `;
- try {
- const response = await fetch(apiUrl);
- if (!response.ok) {
- throw new Error('Failed to fetch sensors');
- }
- const responseData = await response.json();
- const filteredSensors = responseData.data;
- // Render the fetched sensors
- if (savedGraphIndex !== null && savedGraphIndex !== undefined) {
- renderApiSensors(filteredSensors, savedGraphIndex);
- } else {
- renderApiSensors(filteredSensors);
- }
- cachedFilteredSensors = filteredSensors;
- spinnerElement.classList.add('hidden-important');
- apiSensorsListElement.style.display = 'block';
- } catch (error) {
- showToast("Failed to filter sensors", "error");
- console.error(error);
- }
- }
- async function searchSensors() {
- const searchValue = document.getElementById("flexContextSensorSearch").value.toLowerCase();
- spinnerElement.style.display = 'flex';
- senSearchResEle.style.display = 'none';
- const params = new URLSearchParams();
- if (searchValue) {
- params.append('filter', searchValue);
- }
- const apiUrl = `${sensorsApiUrl}&${params.toString()} `;
- try {
- const response = await fetch(apiUrl);
- if (!response.ok) {
- throw new Error('Failed to fetch sensors');
- }
- const responseData = await response.json();
- const filteredSensors = responseData.data;
- // Render the fetched sensors
- renderSensorSearchResults(filteredSensors);
- spinnerElement.classList.add('hidden-important');
- senSearchResEle.style.display = 'block';
- } catch (error) {
- showToast("Failed to search sensors", "error");
- console.error(error);
- }
- }
- // ============== Graph Cards Management ============== //
- async function removeGraph(index) {
- sensorsToShow.splice(index, 1);
- savedGraphIndex = undefined;
- selectedGraphTitle = undefined;
- editingIndex = undefined;
- renderGraphCards();
- renderApiSensors(cachedFilteredSensors);
- }
- async function swapItems(index1, index2) {
- if (index1 >= 0 && index2 >= 0 && index1 < sensorsToShow.length && index2 < sensorsToShow.length) {
- [sensorsToShow[index1], sensorsToShow[index2]] = [sensorsToShow[index2], sensorsToShow[index1]];
- renderGraphCards()
- renderApiSensors(cachedFilteredSensors, index2);
- }
- }
- async function moveGraphUp(index) {
- if (index > 0) {
- await swapItems(index, index - 1);
- }
- }
- async function moveGraphDown(index) {
- if (index < sensorsToShow.length - 1) {
- await swapItems(index, index + 1);
- }
- }
- function editGraphTitle(index) {
- editingIndex = index;
- }
- async function saveGraphTitle(index) {
- const newTitle = document.getElementById(`editTitle_${index}`).value;
- sensorsToShow[index].title = newTitle;
- editingIndex = null;
- selectedGraphTitle = newTitle;
- renderGraphCards();
- }
- // ============== Graph Cards Management ============== //
- // Render the available API sensors
- function renderApiSensors(sensors, graphIndex) {
- // graphIndex is undefined when the sensors are being added to the graph
- // graphIndex is defined when the sensors are being added to the graph cards. In other words
- // when more sensors are being added to single graph
- apiSensorsList.innerHTML = ""; // Clear the previous sensors
- if (sensors.length === 0) {
- apiSensorsList.innerHTML = "<h3>No sensors found</h3>";
- return;
- }
- sensors.forEach(async (sensor) => {
- const Asset = await getAsset(sensor.generic_asset_id);
- const Account = await getAccount(Asset.account_id);
- const col = document.createElement("div");
- col.classList.add("col-12", "mb-1");
- col.innerHTML = `
- <div class="card m-0">
- <div class="card-body p-0 sensor-card">
- <h5 class="card-title">${sensor.name}</h5>
- <p class="card-text">
- <b>ID:</b> <a href="${apiBasePath}/sensors/${sensor.id}">${sensor.id}</a>,
- <b>Unit:</b> ${sensor.unit},
- <b>Asset:</b> ${Asset.name},
- <b>Account:</b> ${Account?.name ? Account.name : "PUBLIC"}
- </p>
- ${graphIndex !== undefined && savedGraphIndex !== undefined && selectedGraphTitle !== undefined
- ? `<button class="btn btn-primary btn-sm" onclick="addSensorToExistingGraph(${graphIndex}, ${sensor.id})">Add to '${selectedGraphTitle}' Graph</button>`
- : `<button class="btn btn-primary btn-sm" onclick="addSensorAsGraph(${sensor.id})">Add new Graph</button>`
- }
- </div>
- </div>
- `;
- apiSensorsList.appendChild(col);
- });
- }
- function renderSensorSearchResults(sensors) {
- senSearchResEle.innerHTML = "";
- if (sensors.length === 0) {
- senSearchResEle.innerHTML = "<h4>No sensors found</h4>";
- return;
- }
- sensors.forEach(async (sensor) => {
- const Asset = await getAsset(sensor.generic_asset_id);
- const Account = await getAccount(Asset.account_id);
- const col = document.createElement("div");
- col.classList.add("col-12", "mb-1");
- col.innerHTML = `
- <div class="card m-0">
- <div class="card-body p-0 sensor-card">
- <h5 class="card-title">${sensor.name}</h5>
- <p class="card-text">
- <b>ID:</b> <a href="${apiBasePath}/sensors/${sensor.id}">${sensor.id}</a>,
- <b>Unit:</b> ${sensor.unit},
- <b>Asset:</b> ${Asset.name},
- <b>Account:</b> ${Account?.name ? Account.name : "PUBLIC"}
- </p>
- <button class="btn btn-primary btn-sm" onclick="udpateFlexContextFieldValue('sensor', ${sensor.id})">Add Sensor</button>
- </div>
- </div>
- `;
- senSearchResEle.appendChild(col);
- });
- }
- async function updateAssetSensorsToShow(dataType) {
- const apiURL = apiBasePath + "/api/v3_0/assets/{{ asset.id }}";
- let requestBody;
- if (dataType === "sensorToShow") {
- requestBody = JSON.stringify({ sensors_to_show: JSON.stringify(sensorsToShow) });
- // Only show the spinner if relevant to this specific data type
- document.getElementById('spinner').style.display = 'block';
- } else if (dataType === "flexContext") {
- // remove null fields
- for (const [key, value] of Object.entries(assetFlexContext)) {
- if (value === null) {
- delete assetFlexContext[key];
- }
- }
- requestBody = JSON.stringify({ flex_context: JSON.stringify(assetFlexContext) });
- }
- const response = await fetch(apiURL, {
- method: "PATCH",
- headers: {
- "Content-Type": "application/json",
- },
- body: requestBody,
- });
- if (!response.ok) {
- const errorData = await response.json();
- const errorMessage = errorData?.message || errorData?.error || response.statusText;
- showToast(`Failed to update the asset: ${errorMessage}`, "error");
- } else {
- document.dispatchEvent(new Event('sensorsToShowUpdated'));
- showToast("Changes saved successfully", "success");
- }
- }
- // Add a sensor as a new graph card
- function addSensorAsGraph(id) {
- const newAsset = {
- title: "No Title",
- sensors: [id],
- };
- sensorsToShow.push(newAsset);
- renderGraphCards();
- }
- // Add blank graph to the graph cards
- function addNewGraph() {
- const newAsset = {
- title: "No Title " + (sensorsToShow.length + 1),
- sensors: [],
- };
- selectedGraphTitle = newAsset.title;
- sensorsToShow.push(newAsset);
- selectGraph(sensorsToShow.length - 1)
- renderGraphCards();
- }
- // Add Sensor to an existing graph card
- function addSensorToExistingGraph(graphIndex, sensorId) {
- sensorsToShow[graphIndex].sensors.push(sensorId);
- renderGraphCards();
- }
- // Remove sensor from the graph sensor list
- function removeSensorFromGraph(graphIndex, sensorIndex) {
- sensorsToShow[graphIndex].sensors.splice(sensorIndex, 1);
- renderGraphCards();
- renderApiSensors(cachedFilteredSensors);
- }
- function handleEnterKeyEventForTitleEditing(event, graphIndex) {
- if (event.key === "Enter") {
- saveGraphTitle(graphIndex);
- renderApiSensors(cachedFilteredSensors, graphIndex);
- }
- }
- // ============== Page Events ============== //
- formModal.addEventListener('hidden.bs.modal', function () {
- updateAssetSensorsToShow("sensorToShow");
- });
- formModal.addEventListener('shown.bs.modal', function () {
- // Initial renders
- renderGraphCards(); // Initial render of graph cards
- filterSensors(); // Initial render of sensors
- });
- document.addEventListener("click", function (event) {
- /**
- The logic in this block is majorly to remove the border on click of the card and add it to the selected card
- but as this event is added to the document, it will be triggered on any click event on the page
- so the if statements are used to check if the click event is on the card or not
- */
- const card = event.target.closest(".card-highlight");
- const sensorCard = event.target.closest(".sensor-card");
- const searchInput = event.target.id === "searchInput";
- const cardBody = event.target.closest(".card-body");
- const editTitleBtn = event.target.id === "editTitleBtn";
- const saveTitleBtn = event.target.id === "saveTitleBtn";
- const unSetBtn = event.target.id === "unSetFlexField";
- if (card) {
- if (card.classList.contains("border-on-click")) {
- if (editTitleBtn || saveTitleBtn) {
- renderGraphCards();
- }
- // Pass
- } else {
- renderGraphCards();
- }
- } else if (
- cardBody !== null && cardBody !== undefined ||
- sensorCard !== null && sensorCard !== undefined ||
- searchInput !== null && searchInput !== undefined
- ) {
- // Pass
- } else {
- document.querySelectorAll(".card-highlight").forEach(el => el.classList.remove("border-on-click"));
- renderApiSensors([], undefined);
- }
- });
- // ============== Page Events ============== //
- </script>
- {% block paginate_tables_script %} {{ super() }} {% endblock %}
- {% endblock %}
|