asset_properties.html 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. {% extends "base.html" %}
  2. {% set active_page = "assets" %}
  3. {% block title %} {{asset.name}} {% endblock %}
  4. {% block divs %}
  5. {% block breadcrumbs %} {{ super() }} {% endblock %}
  6. <div class="container-fluid">
  7. <div class="row mx-1">
  8. <div class="alert alert-info d-none" id="tzwarn"></div>
  9. <div class="alert alert-info d-none" id="dstwarn"></div>
  10. </div>
  11. <div class="row">
  12. <div class="col-md-2 on-top-md">
  13. <div class="header-action-button">
  14. {% if user_can_delete_asset %}
  15. <div>
  16. <form action="/assets/delete_with_data/{{ asset.id }}" method="get">
  17. <button id="delete-asset-button" class="btn btn-sm btn-responsive btn-danger"
  18. type="submit">Delete this asset</button>
  19. </form>
  20. <script>
  21. $("#delete-asset-button").click(function () {
  22. if (confirm("Are you sure you want to delete this asset and all time series data associated with it?")) {
  23. return true;
  24. }
  25. else {
  26. return false;
  27. }
  28. });
  29. </script>
  30. </div>
  31. {% endif %}
  32. </div>
  33. <div class="sidepanel-container d-none d-md-block">
  34. <div class="left-sidepanel-label">Select dates</div>
  35. <div class="sidepanel left-sidepanel">
  36. <div id="datepicker"></div>
  37. </div>
  38. </div>
  39. {% if user_can_update_asset %}
  40. <div class="sidepanel-container">
  41. <div class="left-sidepanel-label">Edit asset</div>
  42. <div class="sidepanel left-sidepanel">
  43. <form class="form-horizontal" method="POST" action="/assets/{{ asset.id }}">
  44. {{ asset_form.csrf_token }}
  45. {{ asset_form.hidden_tag() }}
  46. <fieldset>
  47. <div class="asset-form">
  48. <h3>Edit {{ asset.name }}</h3>
  49. <small>Owned by account: {{ asset.account_id | accountname }} (ID: {{ asset.account_id
  50. }})</small>
  51. <div class="form-group">
  52. {{ asset_form.name.label(class="col-sm-3 control-label") }}
  53. <div class="col-md-3">
  54. {{ asset_form.name(class_="form-control") }}
  55. {% for error in asset_form.errors.name %}
  56. <span style="color: red;">[{{error}}]</span>
  57. {% endfor %}
  58. </div>
  59. </div>
  60. <div class="form-group">
  61. {{ asset_form.latitude.label(class="col-sm-6 control-label") }}
  62. <div class="col-md-6">
  63. {{ asset_form.latitude(class_="form-control") }}
  64. {% for error in asset_form.errors.latitude %}
  65. <span style="color: red;">[{{error}}]</span>
  66. {% endfor %}
  67. </div>
  68. </div>
  69. <div class="form-group">
  70. {{ asset_form.longitude.label(class="col-sm-6 control-label") }}
  71. <div class="col-md-6">
  72. {{ asset_form.longitude(class_="form-control") }}
  73. {% for error in asset_form.errors.longitude %}
  74. <span style="color: red;">[{{error}}]</span>
  75. {% endfor %}
  76. </div>
  77. </div>
  78. <div class="form-group">
  79. <label for="assset-type" class="col-sm-6 control-label">Asset Type</label>
  80. <div class="col-md-6">
  81. <input class="form-control" id="asset-type-id" name="asset-type" type="text"
  82. value="{{ asset.generic_asset_type.name }}" disabled></input>
  83. </div>
  84. </div>
  85. <div class="form-group">
  86. <label for="asset-id" class="col-sm-6 control-label">Asset id</label>
  87. <div class="col-md-6">
  88. <input class="form-control" id="asset-id" name="asset-id" type="text"
  89. value="{{ asset.id }}" disabled></input>
  90. </div>
  91. </div>
  92. <div class="form-group">
  93. {{ asset_form.attributes.label(class="col-sm-3 control-label") }}
  94. <div class="col-md-3">
  95. {{ asset_form.attributes(class_="form-control") }}
  96. {% for error in asset_form.errors.attributes %}
  97. <span style="color: red;">[{{error}}]</span>
  98. {% endfor %}
  99. </div>
  100. </div>
  101. <div class="form-group">
  102. <label class="control-label">Location</label>
  103. <small>(Click map to edit latitude and longitude in form)</small>
  104. <div id="mapid"></div>
  105. <button class="btn btn-sm btn-responsive btn-success create-button" type="submit"
  106. value="Save"
  107. style="margin-top: 20px; float: right; border: 1px solid var(--light-gray);">
  108. Save
  109. </button>
  110. </div>
  111. </div>
  112. </fieldset>
  113. </form>
  114. </div>
  115. </div>
  116. {% endif %}
  117. </div>
  118. <div class="col-md-8">
  119. <div class="sensors-asset card">
  120. <h3>Asset Properties - {{ asset.name }}</h3>
  121. <div class="row">
  122. <div class="col-md-7">
  123. <table class="table table-striped table-responsive">
  124. <thead>
  125. <tr>
  126. <th scope="col">Property</th>
  127. <th scope="col">Value</th>
  128. </tr>
  129. </thead>
  130. <tbody>
  131. {% for key, value in asset_summary.items() %}
  132. <tr>
  133. <td scope="row">{{ key }}</td>
  134. {% if key == "Parent Asset" and value != "No Parent" %}
  135. <td scope="row"><a href="/assets/{{ asset.parent_asset.id }}" class="no-style-link">{{ value }}</a></td>
  136. {% else %}
  137. <td scope="row">{{ value }}</td>
  138. {% endif %}
  139. </tr>
  140. {% endfor %}
  141. </tbody>
  142. </table>
  143. </div>
  144. <div class="col-md-5">
  145. {% from "_macros.html" import render_attributes %}
  146. {{ render_attributes(asset.attributes) }}
  147. </div>
  148. </div>
  149. </div>
  150. <div class="sensors-asset card">
  151. <h3>All sensors for {{ asset.name }}</h3>
  152. <div class="table-responsive">
  153. <table class="table table-striped paginate nav-on-click" title="View data" id="sensorsTable">
  154. </table>
  155. </div>
  156. </div>
  157. <div class="sensors-asset card">
  158. <h3>All child assets for {{ asset.name }}</h3>
  159. <div class="table-responsive">
  160. <table class="table table-striped paginate nav-on-click w-100 mx-auto" title="View this asset">
  161. <thead>
  162. <tr>
  163. <th><i class="left-icon">Name</i></th>
  164. <th>Location</th>
  165. <th>Asset ID</th>
  166. <th>Account</th>
  167. <th>Sensors</th>
  168. <th class="d-none">URL</th>
  169. <th class="no-sort"></th>
  170. </tr>
  171. </thead>
  172. <tbody>
  173. {% for child in asset.child_assets %}
  174. <tr>
  175. <td>
  176. <i class="{{ child.generic_asset_type.name | asset_icon }} left-icon">{{ child.name
  177. }}</i>
  178. </td>
  179. <td>
  180. {% if child.latitude and child.longitude %}
  181. LAT: {{ "{:,.4f}".format( child.latitude ) }} LONG:
  182. {{ "{:,.4f}".format( child.longitude ) }}
  183. {% endif %}
  184. </td>
  185. <td>
  186. {{ child.id }}
  187. </td>
  188. <td>
  189. {% if child.owner %}
  190. {{ child.owner.name }}
  191. {% else %}
  192. PUBLIC
  193. {% endif %}
  194. </td>
  195. <td>
  196. {{ child.sensors | length }}
  197. </td>
  198. <td class="d-none">
  199. /assets/{{ child.id }}
  200. </td>
  201. <td>
  202. <a href="/assets/{{ child.id }}/status">
  203. <button type="button" class="btn">Status</button>
  204. </a>
  205. </td>
  206. </tr>
  207. {% endfor %}
  208. </tbody>
  209. </table>
  210. </div>
  211. </div>
  212. </div>
  213. </div>
  214. </div>
  215. <script src="https://cdnjs.cloudflare.com/ajax/libs/jstimezonedetect/1.0.7/jstz.js"></script>
  216. <script src="https://cdn.jsdelivr.net/npm/litepicker/dist/litepicker.js"></script>
  217. <script src="https://cdn.jsdelivr.net/npm/litepicker/dist/plugins/ranges.js"></script>
  218. <script src="https://cdn.jsdelivr.net/npm/litepicker/dist/plugins/keyboardnav.js"></script>
  219. {% block leftsidepanel %} {{ super() }} {% endblock %}
  220. <!-- Initialise the map -->
  221. <script src="https://cdn.jsdelivr.net/npm/leaflet@1.7.1/dist/leaflet-src.min.js"></script>
  222. <script src="{{ url_for('flexmeasures_ui.static', filename='js/map-init.js') }}"></script>
  223. <script type="text/javascript">
  224. // create map
  225. var assetMap = L
  226. .map('mapid', { center: [{{ asset.latitude | replace("None", 10) }}, {{ asset.longitude | replace("None", 10) }}], zoom: 10})
  227. .on('popupopen', function () {
  228. $(function () {
  229. $('[data-toggle="tooltip"]').tooltip();
  230. });
  231. });
  232. addTileLayer(assetMap, '{{ mapboxAccessToken }}');
  233. // create marker
  234. var asset_icon = new L.DivIcon({
  235. className: 'map-icon',
  236. html: '<i class="icon-empty-marker center-icon supersize"></i><i class="overlay center-icon {{ asset.generic_asset_type.name | default("info") | asset_icon }}"></i>',
  237. iconSize: [100, 100], // size of the icon
  238. iconAnchor: [50, 50], // point of the icon which will correspond to marker's location
  239. popupAnchor: [0, -50] // point from which the popup should open relative to the iconAnchor
  240. });
  241. var marker = L
  242. .marker(
  243. [{{ asset.latitude | replace("None", 10) }}, {{ asset.longitude | replace("None", 10) }}],
  244. { icon: asset_icon }
  245. ).addTo(assetMap);
  246. assetMap.on('click', function (e) {
  247. $("#latitude").val(e.latlng.lat.toFixed(4));
  248. $("#longitude").val(e.latlng.lng.toFixed(4));
  249. marker.setLatLng(e.latlng);
  250. });
  251. </script>
  252. <script>
  253. function Sensor(
  254. id,
  255. name,
  256. unit,
  257. resolution,
  258. entity_address,
  259. active
  260. ) {
  261. this.id = id;
  262. this.name = name;
  263. this.unit = unit;
  264. this.resolution = resolution
  265. this.entity_address = entity_address;
  266. this.url = `/sensors/${id}`;
  267. }
  268. $(document).ready(function () {
  269. let unit = "";
  270. // Initialize the DataTable
  271. const table = $("#sensorsTable").dataTable({
  272. order: [[0, "asc"]],
  273. pageLength: 5,
  274. lengthMenu: [5, 10, 25, 50, 75, 100],
  275. serverSide: true,
  276. // make the table row vertically aligned with header
  277. columns: [
  278. { data: "id", title: "ID", orderable: true },
  279. { data: "name", title: "Name", orderable: true },
  280. { data: "unit", title: "Unit", orderable: false },
  281. { data: "resolution", title: "Resolution", orderable: true },
  282. { data: "entity_address", title: "Entity address", orderable: false },
  283. { data: "url", title: "URL", className: "d-none" },
  284. ],
  285. ajax: function (data, callback, settings) {
  286. const basePath = window.location.origin;
  287. let filter = data["search"]["value"];
  288. let orderColumnIndex = data["order"][0]["column"]
  289. let orderDirection = data["order"][0]["dir"];
  290. let orderColumnName = data["columns"][orderColumnIndex]["data"];
  291. let url = `${basePath}/api/v3_0/assets/{{asset.id}}/sensors?page=${data["start"] / data["length"] + 1
  292. }&per_page=${data["length"]}`;
  293. if (orderColumnName) {
  294. url = `${url}&sort_by=${orderColumnName}&sort_dir=${orderDirection}`;
  295. }
  296. if (filter.length > 0) {
  297. url = `${url}&filter=${filter}`;
  298. }
  299. if (unit !== "") {
  300. url = `${url}&unit=${unit}`;
  301. }
  302. $.ajax({
  303. type: "get",
  304. url: url,
  305. success: function (response, text) {
  306. let clean_response = [];
  307. response["data"].forEach((element) =>
  308. clean_response.push(
  309. new Sensor(
  310. element["id"],
  311. element["name"],
  312. element["unit"],
  313. element["event_resolution"],
  314. element["entity_address"],
  315. element["active"]
  316. )
  317. )
  318. );
  319. callback({
  320. data: clean_response,
  321. recordsTotal: response["num-records"],
  322. recordsFiltered: response["filtered-records"],
  323. });
  324. },
  325. error: function (request, status, error) {
  326. showToast(error, "error");
  327. },
  328. });
  329. },
  330. });
  331. // Event listener for the select box
  332. $("#unitFilterOptions").change(function () {
  333. unit = this.value;
  334. table.api().ajax.reload();
  335. });
  336. });
  337. </script>
  338. {% block paginate_tables_script %} {{ super() }} {% endblock %}
  339. {% endblock %}