Subversion Repositories ESP8266_P1_Meter

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 raymond 1
<!DOCTYPE html>
2
<html lang="en">
3
<head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>RFID Log</title>
7
  <style>
8
    /* Base Styles */
9
    body {background:#f5f5f5; margin:0; font-family:Arial,sans-serif; margin-top:20px;}
10
    header, footer {padding:10px; background:#024c5b; color:#fff; width:90%; text-align:center;}
11
    label {display:block; margin-bottom:5px;}
12
    input[type="text"], input[type="password"], input[type="email"] {width:100%; padding:8px; border:1px solid #ddd; border-radius:3px;}
13
    button.submit {margin:0 20px -10px 20px; width:80%; min-width:60px; padding:10px; background:#607D8B; color:white; border:none; cursor:pointer;}
14
    button.submit:hover {background:#16729F;}
15
    button[disabled]:hover, button[disabled] {background:#ccc; color:#666;}
16
    select.submit {width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 3px; background-color: white; cursor: pointer;}
17
    select.submit:hover {border-color: #16729F;}
18
 
19
    /* Layout */
20
    .container {display:flex; flex-direction:column; align-items:center; min-height:100vh;}
21
    .custom-container {padding:10px; width:90%; box-shadow:0 0 10px rgba(0,0,0,0.1); background:#fff;}
22
    .sidebar {background:#f8f9fa; flex:0 0 10%; transition:all 0.3s ease;}
23
    .tab {padding:10px 20px; cursor:pointer;}
24
    .tab.active {background:#e9ecef;}
25
    .content {flex-grow:1; display:flex; justify-content:center; align-items:flex-start;}
26
    .main-content {width:100%; padding:0 0 20px 20px; min-height:70vh;}
27
    .section {position:relative;}
28
    .collapse-container {margin-bottom:20px;}
29
    .collapse-content {display:none; padding:10px; border:1px solid #ddd; border-radius:5px; margin-bottom:20px;}
30
    .collapse-content.show {display:block;}
31
    .form-row {display:flex; flex-wrap:wrap; margin-bottom:10px; align-items:flex-end;}
32
    .form-column {flex:1; margin:0 20px 0 20px;}
33
    .form-column:last-child {margin-right:40px;}
34
    .but-row {display:flex; margin:20px 0 10px 0;}
35
 
36
    /* Interactive Elements */
37
    .button:hover {cursor:pointer; background:#16729F;}
38
    .button:active {transform:scale(0.8);}
39
    #about {color:lightgray;}
40
    details, main, summary {display:block;}
41
 
42
    /* Floating Elements */
43
    .floating {position:absolute; float:right; z-index:1; right:-4px;}
44
    .floating.top {top:-8px;}
45
    .floating.bottom {bottom:-8px;}
46
    .floating.inline {position:sticky;}
47
    .floating .button {
48
      width:24px; height:24px; margin-top:2px; border-radius:50%; 
49
      box-shadow:0 2px 4px rgba(0,0,0,0.8); border:1px solid transparent; 
50
      font-family:monospace; font-size:larger; color:cornsilk;
51
    }
52
 
53
    /* Color Classes */
54
    .green {background:rgba(0,128,0,0.73);}
55
    .blue {background:#607D8B;}
56
 
57
    /* Tables */
58
    .responsive-table {width:100%; overflow-x:auto; -webkit-overflow-scrolling:touch; margin-bottom:1rem;}
59
    .table-container {min-width:600px; border:1px solid #ddd; border-radius:5px; overflow:hidden;}
60
    .table-header, .table-row {display:flex; flex-wrap:nowrap;}
61
    .table-header {background:#f2f2f2; font-weight:bold; margin-top:10px;}
62
    .table-header>div, .table-row>div {padding:10px 5px; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
63
    .table-row {border-bottom:1px solid #ddd;}
64
    .table-row:nth-child(even) {background:#f9f9f9;}
65
    .table-row.selected {background:#d3d3d3; border:1px solid orange; color:blue;}
66
 
67
    /* Table Columns */
68
    .col-tag {flex:1 1 25%; text-align:center;}
69
    .col-reader {flex:1 1 10%; text-align:center;}
70
    .col-level {flex:0 0 5%; text-align:center;}
71
    .col-timestamp {flex:1 1 30%;}
72
    .col-name {flex:1 1 25%;}
73
    .col-email {flex:1 1 25%;}
74
    .col-username {flex:1 1 20%;}
75
 
76
 
77
    /* Modal */
78
    .d-modal {
79
      width:-webkit-fill-available; border-radius:10px; border:1px solid rgba(51,51,51,0.43);
80
      box-shadow:0 3px 8px rgba(0,0,0,0.24); left:50%; position:absolute; top:60%; 
81
      transform:translate(-50%,-50%); flex-direction:column; background:hsla(200,18%,46%,0.9); z-index:999;
82
    }
83
    .d-modal-title {color:#111827; padding:1.5em; position:relative; width:calc(100% - 4.5em);}
84
    .d-modal-content {border-top:1px solid #e0e0e0; padding:1.5em; overflow:auto;}
85
    .btn {display:inline-flex; padding:10px 15px; background:#2E8BC0; color:#fff; border:0; cursor:pointer; min-width:40%; border-radius:5px; justify-content:center;}
86
    .btn:hover {filter:brightness(85%);}
87
    .btn-bar {display:flex; padding:20px; justify-content:center; flex-wrap:wrap; gap:30px 20px; order:998;}
88
 
89
    /* Utility Classes */
90
    .hide {display:none;}
91
    .loader {
92
      width:120px; height:120px; border:24px solid rgba(222,219,219,0.66); 
93
      border-top:24px solid rgba(52,152,219,0.89); border-radius:50%;
94
      animation:spin 2s linear infinite; position:fixed; top:50%; left:50%; 
95
      margin:-60px; display:none;
96
    }
97
    @keyframes spin {0%{transform:rotate(0deg);} 100%{transform:rotate(360deg);}}
98
 
99
    /* Mobile */
100
    @media (max-width:768px) {
101
      .custom-container {width:95%; padding:5px;}
102
      .table-header>div, .table-row>div {padding:8px 10px; font-size:0.9em;}
103
      .form-column {flex:1 1 100%; margin:0 0 10px 0;}
104
      .form-column:last-child {margin-right:0;}
105
      .but-row {flex-wrap:wrap;}
106
      button.submit {margin:5px 0; width:100%;}
107
    }
108
  </style>
109
</head>
110
<body>
111
  <div class="loader" id="loader"></div>
112
 
113
  <details id=modal-message>
114
    <summary style="display: block"></summary>
115
    <div class=d-modal>
116
      <div class=d-modal-title><h2 id=message-title>t</h2></div>
117
      <div class=d-modal-content><p id=message-body></p></div>
118
      <div class=btn-bar>
119
          <a id=ok-modal class="btn hide" onclick=closeModal(true)><span>OK</span></a>
120
          <a id=close-modal class="btn" onclick=closeModal(false)><span>Close</span></a>
121
      </div>
122
    </div>
123
  </details>
124
 
125
  <div id="main-box" class="container">
126
    <header>
127
      <h1 class="title is-3">ESP32 RFID Logs</h1> 
128
    </header>
129
    <div class="custom-container">
130
      <div class="content">
131
        <div class="sidebar">
132
          <div class="tab" id="logsTab" data-target="logsContent">Logs</div>
133
          <div class="tab" id="usersTab" data-target="usersContent">Users</div>
134
          <div class="tab"><a id="setup" style="color: inherit; text-decoration: none;" href="#" disabled>Setup</a></div>
135
        </div>
136
        <div class="main-content">
137
          <div id="logsContent" class="section">
138
 
139
            <div class="floating top">
140
              <button class="button green" onclick="toggleCollapse('collapse-logs')"><b id='toggle-logs'>+</b></button>
141
            </div>
142
 
143
            <div class="floating bottom">
144
              <button class="button blue" id="prev-log"><b>&lt;</b></button>
145
              <button class="button blue" id="next-log"><b>&gt;</b></button>
146
            </div>
147
 
148
            <div class="collapsible">
149
              <div id="collapse-logs" class="collapse-content">
150
               <div id="insertForm">
151
                  <div class="form-row">
152
                    <div class="form-column">
153
                      <label for="log-file-select">Select Log File:</label>
154
                      <select id="log-file-select" class="submit">
155
                        <!-- Options will be added dynamically -->
156
                      </select>
157
                    </div>
158
                    <div class="form-column">
159
                      <label for="username-log">Username:</label>
160
                      <input type="text" id="username-log" name="username-log" placeholder="Username">
161
                    </div>
162
                    <div class="form-column">
163
                      <label for="reader-log">Reader:</label>
164
                      <input type="text" id="reader-log" name="reader-log" placeholder="Reader number">
165
                    </div>
166
 
167
                    <div class="but-row">
168
                      <button class="submit" id="export-log">Export</button>
169
                    </div>
170
                  </div>
171
                </div>
172
              </div>
173
            </div>
174
 
175
            <div class="responsive-table">
176
              <div class="table-container">
177
                <div class="table-header">
178
                  <div class="col-reader">Reader</div>
179
                  <div class="col-username">Username</div>
180
                  <div class="col-tag">Tag Code</div>
181
                  <div class="col-timestamp">Timestamp</div>
182
                </div>
183
                <div id="logsTable" class="table-body">
184
                  <!-- Rows will be added dynamically -->
185
                </div>
186
              </div>
187
            </div>
188
          </div>
189
 
190
          <div id="usersContent" class="section hide">
191
            <div class="floating top">
192
              <button class="button green" id="handle-users" onclick="toggleCollapse('collapse-user')" disabled><b id='toggle-user'>+</b></button>
193
            </div>
194
            <div class="collapsible">
195
              <div id="collapse-user" class="collapse-content">
196
                <div id="insertForm">
197
                  <div class="form-row">
198
                    <div class="form-column">
199
                      <label for="tag">Tag Code:</label>
200
                      <span style="display: inline-flex;">
201
                        <input type="text" id="tag" name="tag" placeholder="Tag Code">
202
                        <div class="floating inline">
203
                          <button id="get-tag" class="button blue" title="Read RFID tag code"><b>@</b></button>
204
                        </div>
205
                      </span>
206
                    </div>
207
                    <div class="form-column">
208
                      <label for="name">Name:</label>
209
                      <input type="text" id="name" name="name" placeholder="Name">
210
                    </div>
211
                    <div class="form-column">
212
                      <label for="level">Level:</label>
213
                      <input type="text" id="level" name="level" placeholder="Level">
214
                    </div>
215
                  </div>
216
                  <div class="form-row">
217
                    <div class="form-column">
218
                      <label for="username">Username:</label>
219
                      <input type="text" id="username" name="username" placeholder="Username">
220
                    </div>
221
                    <div class="form-column">
222
                      <label for="password">Password:</label>
223
                      <input type="password" id="password" name="password" placeholder="password">
224
                    </div>
225
                    <div class="form-column">
226
                      <label for="email">Email:</label>
227
                      <input type="email" id="email" name="email" placeholder="Email">
228
                    </div>
229
                  </div>
230
 
231
                  <div class="but-row">
232
                    <button class="submit" id="add-user">Insert</button>
233
                    <button class="submit" id="delete-user" disabled>Delete</button>
234
                  </div>
235
                </div>
236
              </div>
237
            </div>
238
 
239
            <div class="responsive-table">
240
              <div class="table-container">
241
                <div class="table-header">
242
                  <div class="col-username">Username</div>
243
                  <div class="col-name">Name</div>
244
                  <div class="col-email">Email</div>
245
                  <div class="col-tag">Tag Code</div>
246
                  <div class="col-level">Role</div>
247
                </div>
248
                <div id="usersTable" class="table-body">
249
                  <!-- Rows will be added dynamically -->
250
                </div>
251
              </div>
252
            </div>
253
          </div>
254
        </div>
255
      </div>
256
    </div>
257
    <footer class="footer">
258
      <div class="has-text-centered">
259
        <p> RFID Log &copy; 2025. All rights reserved.</p>
260
        <a id=about target=_blank rel=noopener href="https://github.com/cotestatnt/async-esp-fs-webserver">Created with https://github.com/cotestatnt/async-esp-fs-webserver</a>
261
      </div>
262
    </footer>
263
  </div>
264
 
265
  <script>
266
    var userLevel = 0;
267
    let currentCsvData = [];
268
    let currentPage = 0;
269
    const recordsPerPage = 15;
270
    let totalFilteredRecords = 0;
271
 
272
 
273
    // Callback function called on modal box close
274
    var closeCb = function(){};
275
 
276
    // ID Element selector shorthands
277
    var $ = function(el) {
278
        return document.getElementById(el);
279
    };
280
 
281
    // Switch active page
282
    function tabClick() {
283
      const tabs = document.querySelectorAll('.tab');
284
      const sections = document.querySelectorAll('.section');
285
      tabs.forEach(t => t.classList.remove('active'));
286
      sections.forEach(s => s.classList.add('hide'));
287
      const target = this.dataset.target;
288
      $(target).classList.remove('hide');
289
      this.classList.remove('hide');
290
      this.classList.add('active');
291
    }
292
 
293
    // Toggle the collapsible user section 
294
    function toggleCollapse(id, keep) {
295
      if (keep)
296
        $(id).classList.add('show');
297
      else
298
        $(id).classList.toggle('show');
299
 
300
      const allRows = document.querySelectorAll('.table-row');
301
      allRows.forEach(row => row.classList.remove('selected'));
302
      const allInput = $('insertForm').querySelectorAll('input');
303
      allInput.forEach(inp => inp.value = '');
304
      $('add-user').innerHTML = 'Insert';
305
      $('delete-user').disabled = true;
306
      $('add-user').disabled = true;
307
      const btnId = id.split('-')[1];
308
      $('toggle-' + btnId).innerHTML = $(id).classList.contains('show') ? '-' : '+';
309
    }
310
 
311
    // Show a message, if fn != undefinded run as callback on OK button press
312
    function openModal(title, msg, fn) {
313
      $('message-title').innerHTML = title;
314
      $('message-body').innerHTML = msg;
315
      $('modal-message').open = true;
316
      $('main-box').style.filter = "blur(3px)";
317
      if (typeof fn != 'undefined') {
318
        closeCb = fn;
319
        $('ok-modal').classList.remove('hide');
320
      }
321
      else
322
        $('ok-modal').classList.add('hide');
323
    }
324
 
325
    // Close modal box
326
    function closeModal(do_cb) {
327
      $('modal-message').open = false;
328
      $('main-box').style.filter = "";
329
      if (typeof closeCb != 'undefined' && do_cb)
330
        closeCb();
331
    }
332
 
333
    // Helper to format timestamp
334
    function formatTimestamp(timestamp) {
335
      const date = new Date(timestamp);
336
      return isNaN(date.getTime()) ? timestamp : date.toLocaleString();
337
    }
338
 
339
 
340
    //////////////////////////////////////////////////////////////////////////////////////////////////
341
    //////////////////////////////   Users setup handling ////////////////////////////////////////////
342
    //////////////////////////////////////////////////////////////////////////////////////////////////
343
 
344
 
345
    // Send command to MCU before the readings in order to handle properly logs record
346
    function readTagCode() {
347
      fetch('/waitCode')
348
      .then(response => {
349
        openModal('Read new TAG', "<br>Please hold your tag close to the RFID reader", getTagCode);
350
      })
351
    }
352
 
353
    // Read the RFID code for current user
354
    function getTagCode() {
355
      fetch('/getCode')
356
      .then(response => {
357
        if (!response.ok) {
358
          throw new Error('Request error');
359
        }
360
        return response.json();
361
      })
362
      .then(data => {
363
        $('tag').value = data.tag;
364
        $('add-user').disabled = false;
365
      })
366
      .catch(error => {
367
        alert('Error:' + error);
368
      });
369
    }
370
 
371
    // Get list of registered users
372
    function getUsers() {
373
      const usersTable = $('usersTable');
374
      fetch('/users')
375
      .then(response => {
376
        if (!response.ok) {
377
          throw new Error('Request error');
378
        }
379
        return response.json();
380
      })
381
      .then(data => {
382
        usersTable.innerHTML = '';
383
        data.forEach((user, index) => {
384
          const userEntry = document.createElement('div');
385
          userEntry.className = 'table-row';
386
          userEntry.innerHTML = `
387
            <div class="col-username">${user.username}</div>
388
            <div class="col-name">${user.name}</div>
389
            <div class="col-email">${user.email}</div>
390
            <div class="col-tag">${user.tag}</div>
391
            <div class="col-level">${user.level}</div>`;
392
          usersTable.appendChild(userEntry);
393
 
394
          userEntry.addEventListener('click', function(ev) {
395
            const allRows = document.querySelectorAll('.table-row');
396
            allRows.forEach(row => row.classList.remove('selected'));
397
 
398
            if(userLevel >= 5) {
399
              toggleCollapse('collapse-user', true);
400
              this.classList.add('selected');
401
              const cols = Array.from(this.querySelectorAll('div')).map(el => {
402
                const id = el.className.replace('col-', '');
403
                return {
404
                  id: id,
405
                  value: el.innerHTML
406
                };
407
              });
408
 
409
              cols.forEach(item => {
410
                if ($(item.id)) {
411
                  $(item.id).value = item.value; 
412
                  $(item.id).addEventListener('input', function() {
413
                    $('add-user').disabled = false;
414
                  });
415
                }
416
              });
417
 
418
              $('delete-user').disabled = false;
419
              $('add-user').innerHTML = 'Update';
420
            }
421
          });
422
        });
423
 
424
        $('loader').style.display = "none";
425
      })
426
      .catch(error => {
427
        console.error('Error:', error);
428
      });
429
    }
430
 
431
    // Send command to MCU before the readings in order to handle properly logs record
432
    function deleteUser() {
433
      $('delete-user').disabled = true;
434
      sendUserForm('/delUser');
435
    }
436
 
437
    // Insert or update a new user record
438
    function sendUserForm(url) {
439
      var formData = new FormData();
440
      formData.append("username", $('username').value);
441
      formData.append("password", $('password').value);
442
      formData.append("name", $('name').value);
443
      formData.append("email", $('email').value);    
444
      formData.append("tag", $('tag').value);
445
      formData.append("level", $('level').value);
446
      formData.append("type", $('add-user').innerHTML);
447
      const option = {
448
        method: 'POST',
449
        body: formData
450
      };
451
      fetch(url, option)
452
        .then(response => {
453
          if (!response.ok) {
454
            throw new Error('Request error');
455
          }
456
          return response.text();
457
        })
458
        .then(result => {
459
          openModal('Users', "<br>New record inserted or updated");
460
          getUsers();
461
        })
462
        .catch(error => {
463
          openModal('Error', error);
464
        });
465
    }
466
 
467
    //////////////////////////////////////////////////////////////////////////////////////////////////
468
    //////////////////////////////    CSV logs handling //////////////////////////////////////////////
469
    //////////////////////////////////////////////////////////////////////////////////////////////////
470
    function listLogFiles() {
471
      const url = '/list?dir=/logs';
472
      $('loader').style.display = "block";
473
 
474
      fetch(url)
475
      .then(response => {
476
        if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
477
        return response.json();
478
      })
479
      .then(files => {
480
        const select = $('log-file-select');
481
        select.innerHTML = '';
482
 
483
        if (!files || files.length === 0) {
484
          select.innerHTML = '<option value="-1">No log files found</option>';
485
          $('loader').style.display = "none";
486
          return;
487
        }
488
 
489
        const csvFiles = files
490
        .filter(file => file.name.endsWith('.csv'))
491
        .sort((a, b) => {
492
          const dateA = a.name.split('.')[0].split('_');
493
          const dateB = b.name.split('.')[0].split('_');
494
          const dateObjA = new Date(dateA[0], dateA[1] - 1, dateA[2]);
495
          const dateObjB = new Date(dateB[0], dateB[1] - 1, dateB[2]);
496
          return dateObjB - dateObjA;
497
        });
498
 
499
        csvFiles.forEach(file => {
500
          const dateParts = file.name.split('.')[0].split('_');
501
          const formattedDate = `${dateParts[2]}/${dateParts[1]}/${dateParts[0]}`;
502
          const option = new Option(
503
            formattedDate,
504
            file.name,
505
            false,
506
            false
507
          );
508
          select.add(option);
509
        });
510
 
511
        if (csvFiles.length > 0) {
512
          select.value = csvFiles[0].name;
513
          loadCsvFile(csvFiles[0].name);
514
        }
515
      })
516
      .catch(error => {
517
        console.error('Error listing files:', error);
518
        openModal('Error', `Failed to list log files: ${error.message}`);
519
      })
520
      .finally(() => {
521
        $('loader').style.display = "none";
522
      });
523
 
524
      $('log-file-select').addEventListener('change', function() {
525
        const selectedFile = this.value;
526
        if (selectedFile && selectedFile !== '-1') {
527
          $('loader').style.display = "block";
528
          loadCsvFile(selectedFile)
529
          .catch(error => {
530
            console.error('Error loading file:', error);
531
            openModal('Error', 'Failed to load selected file');
532
          })
533
          .finally(() => {
534
            $('loader').style.display = "none";
535
          });
536
        }
537
      });
538
    }
539
 
540
 
541
    function loadCsvFile(filename) {
542
      $('loader').style.display = "block";
543
      currentCsvData = []; // Reset data
544
 
545
      fetch(`/logs/${encodeURIComponent(filename)}`)
546
        .then(response => response.text())
547
        .then(csvText => {
548
            // Convert CSV to array of arrays
549
            currentCsvData = csvText.split('\n')
550
                .filter(line => line.trim())
551
                .map(line => line.split(',').map(cell => cell.replace(/"/g, '').trim()));
552
 
553
            applyFilters(); // Apply filters immediately after loading
554
        })
555
        .catch(error => {
556
            console.error("Error loading file:", error);
557
            $('logsTable').innerHTML = `<div class="table-row error">Error loading file</div>`;
558
        })
559
        .finally(() => {
560
            $('loader').style.display = "none";
561
        });
562
    }
563
 
564
    function updatePaginationButtons() {
565
      $('prev-log').disabled = currentPage === 0;
566
      $('next-log').disabled = (currentPage + 1) * recordsPerPage >= totalFilteredRecords;
567
    }
568
 
569
    function applyFilters() {
570
      if (currentCsvData.length === 0) return;
571
      const usernameFilter = $('username-log').value.toLowerCase();
572
      const readerFilter = $('reader-log').value.toLowerCase();
573
 
574
      // Filter data (skip header row)
575
      let filteredData = currentCsvData.slice(1).filter(row => {
576
          return (!usernameFilter || row[1].toLowerCase().includes(usernameFilter)) &&
577
                 (!readerFilter || row[0].toLowerCase().includes(readerFilter));
578
      });
579
 
580
      totalFilteredRecords = filteredData.length;
581
      currentPage = 0; // Reset to first page when filters change
582
 
583
      // Get data for current page
584
      const pagedData = filteredData.slice(
585
        currentPage * recordsPerPage,
586
        (currentPage + 1) * recordsPerPage
587
      );
588
 
589
      // Update table
590
      updateTable(pagedData);
591
      updatePaginationButtons();
592
      return filteredData;
593
    }
594
 
595
    function updateTable(data) {
596
      const tableBody = $('logsTable');
597
      tableBody.innerHTML = '';
598
 
599
      if (data.length === 0) {
600
        tableBody.innerHTML = '<div class="table-row"><div class="col-empty">No matching records</div></div>';
601
        return;
602
      }
603
 
604
      data.forEach(row => {
605
        const rowElement = document.createElement('div');
606
        rowElement.className = 'table-row';
607
        rowElement.innerHTML = `
608
          <div class="col-reader">${row[0]}</div>
609
          <div class="col-username">${row[1]}</div>
610
          <div class="col-tag">${row[2]}</div>
611
          <div class="col-timestamp">${formatTimestamp(row[3])}</div>
612
        `;
613
        tableBody.appendChild(rowElement);
614
      });
615
 
616
      // Mostra informazioni sulla paginazione (opzionale)
617
      const startRecord = currentPage * recordsPerPage + 1;
618
      const endRecord = Math.min((currentPage + 1) * recordsPerPage, totalFilteredRecords);
619
      console.log(`Showing records ${startRecord}-${endRecord} of ${totalFilteredRecords}`);
620
    }
621
 
622
    // Setup event listeners
623
    function setupFilters() {
624
      const inputs = [$('username-log'), $('reader-log')];
625
      inputs.forEach(input => {
626
        input.addEventListener('input', () => {
627
          applyFilters(); // Reapply filters on each change
628
        });
629
      });
630
    }
631
 
632
    function goToPrevPage() {
633
      if (currentPage > 0) {
634
        currentPage--;
635
        showCurrentPage();
636
      }
637
    }
638
 
639
    function goToNextPage() {
640
      if ((currentPage + 1) * recordsPerPage < totalFilteredRecords) {
641
        currentPage++;
642
        showCurrentPage();
643
      }
644
    }
645
 
646
    function showCurrentPage() {
647
      const usernameFilter = $('username-log').value.toLowerCase();
648
      const readerFilter = $('reader-log').value.toLowerCase();
649
 
650
      let filteredData = currentCsvData.slice(1).filter(row => {
651
          return (!usernameFilter || row[1].toLowerCase().includes(usernameFilter)) &&
652
                 (!readerFilter || row[0].toLowerCase().includes(readerFilter));
653
      });
654
 
655
      const pagedData = filteredData.slice(
656
        currentPage * recordsPerPage,
657
        (currentPage + 1) * recordsPerPage
658
      );
659
 
660
      updateTable(pagedData);
661
      updatePaginationButtons();
662
    }
663
 
664
    // Export and download filtered data
665
    function exportCSV() {
666
      let csvString = 'reader, username, tag, timestamp\n'
667
      let filteredData = applyFilters();
668
      filteredData.forEach( row => {
669
        csvString += row.join(', ') + '\n';
670
      });
671
 
672
      // Download as CSV file
673
      const blob = new Blob([csvString], { type: 'text/csv' });
674
      const url = window.URL.createObjectURL(blob);
675
      const a = document.createElement('a');
676
      a.href = url;
677
      a.download = 'data.csv';
678
      document.body.appendChild(a);
679
      a.click();
680
      document.body.removeChild(a);
681
      window.URL.revokeObjectURL(url);
682
    }
683
 
684
    // Initialization
685
    document.addEventListener('DOMContentLoaded', function() {   
686
      $('loader').style.display = "block";
687
 
688
      setupFilters();
689
      listLogFiles();
690
      getUsers();
691
 
692
      // Event listeners
693
      $('logsTab').addEventListener('click', tabClick);
694
      $('usersTab').addEventListener('click', tabClick);
695
      $('get-tag').addEventListener('click', readTagCode);
696
      $('export-log').addEventListener('click', exportCSV);
697
      $('prev-log').addEventListener('click', goToPrevPage);
698
      $('next-log').addEventListener('click', goToNextPage);
699
 
700
      $('add-user').addEventListener('click', function(event) {
701
        event.preventDefault(); 
702
        $('add-user').disabled = true;
703
        $('delete-user').disabled = true;
704
        sendUserForm('/addUser');
705
      });
706
 
707
      $('delete-user').addEventListener('click', function(event) {
708
        event.preventDefault(); 
709
        openModal('Delete user', "Do you really want to drop current user record?", deleteUser);
710
      });
711
 
712
      // Check if user has admin level (>= 5)
713
      var usernameValue = document.cookie.replace(/(?:(?:^|.*;\s*)username\s*=\s*([^;]*).*$)|^.*$/, "$1");
714
      userLevel = usernameValue.split(',')[1];
715
      if(userLevel >= 5) {
716
        console.log(usernameValue.split(',')[0], "is admin");
717
        $('handle-users').disabled = false;
718
        $('setup').href = '/setup';
719
      }
720
    });
721
  </script>
722
</body>
723
</html>