From a5003d9a270ec9b4982ae086be16a586dc1ab773 Mon Sep 17 00:00:00 2001 From: adarshm11 Date: Sat, 28 Jun 2025 16:26:23 -0700 Subject: [PATCH 01/11] Add UI to manage cards, move CardReader API functions to separate file, fix Messaging bug --- api/main_endpoints/routes/OfficeAccessCard.js | 62 +++++-- src/APIFunctions/CardReader.js | 46 +++++ src/APIFunctions/Messaging.js | 2 +- src/Pages/CardReader/CardReader.js | 168 ++++++++++++++++-- 4 files changed, 250 insertions(+), 28 deletions(-) create mode 100644 src/APIFunctions/CardReader.js diff --git a/api/main_endpoints/routes/OfficeAccessCard.js b/api/main_endpoints/routes/OfficeAccessCard.js index 930c66132..f233586e7 100644 --- a/api/main_endpoints/routes/OfficeAccessCard.js +++ b/api/main_endpoints/routes/OfficeAccessCard.js @@ -12,7 +12,7 @@ const OfficeAccessCard = require('../models/OfficeAccessCard.js'); const logger = require('../../util/logger'); const { officeAccessCard = {} } = require('../../config/config.json'); const { API_KEY = 'NOTHING_REALLY' } = officeAccessCard; -const { decodeTokenFromBodyOrQuery } = require('../util/token-functions.js'); +const { decodeTokenFromBodyOrQuery, decodeToken } = require('../util/token-functions.js'); router.use(bodyParser.json()); @@ -68,6 +68,29 @@ function deleteCard(cardBytes) { }); } +function getAllCards() { + return new Promise((resolve) => { + try { + OfficeAccessCard.find( + {} + , (error, result) => { + if (error) { + logger.error('getAllCards got an error querying mongodb'); + return resolve(null); + } + if (!result) { + logger.info('Could not retrieve any cards from mongodb'); // double check that this is a correct message + } + return resolve(result); + } + ); + } catch (error) { + logger.error('getAllCards caught an error: ', error); + return resolve(null); + } + }); +} + let clients = []; const defaultResponse = { @@ -76,7 +99,7 @@ const defaultResponse = { endpoint: '/verify', }; -const writeRequestResponse = ({ statusCode, ...rest }) => { +const writeLogToClient = ({ statusCode, ...rest }) => { const response = { statusCode, ...defaultResponse, @@ -98,7 +121,7 @@ router.get('/verify', async (req, res) =>{ const missingValue = required.find(({ value }) => !value); if (missingValue) { - writeRequestResponse({ statusCode: BAD_REQUEST, message: `${missingValue.title} missing from request` }); + writeLogToClient({ statusCode: BAD_REQUEST, message: `${missingValue.title} missing from request` }); return res.status(BAD_REQUEST).send(` ${missingValue.title} missing from request`); } @@ -108,7 +131,7 @@ router.get('/verify', async (req, res) =>{ const cardExists = await checkIfCardExists(cardBytes); if (cardExists) { - writeRequestResponse({ statusCode: OK }); + writeLogToClient({ statusCode: OK }); return res.sendStatus(OK); } // if a card doesnt exist and we arent trying @@ -116,7 +139,7 @@ router.get('/verify', async (req, res) =>{ // to verify a card, and that card isnt found. // therefore return a non OK status if (!add) { - writeRequestResponse({ statusCode: NOT_FOUND, message: 'Card not found' }); + writeLogToClient({ statusCode: NOT_FOUND, message: 'Card not found' }); return res.sendStatus(NOT_FOUND); } @@ -126,12 +149,12 @@ router.get('/verify', async (req, res) =>{ await new OfficeAccessCard({ cardBytes }).save(); - writeRequestResponse({ statusCode: OK, message: 'Card added!', endpoint: '/verify?add=1' }); + writeLogToClient({ statusCode: OK, message: 'Card added!', endpoint: '/verify?add=1' }); return res.sendStatus(OK); } } catch (error) { logger.error('Error creating OfficeAccessCard: ', error); - writeRequestResponse({ + writeLogToClient({ statusCode: SERVER_ERROR, endpoint: '/verify?add=1', message: `Error creating Office AccessCard: ${error}` @@ -141,13 +164,13 @@ router.get('/verify', async (req, res) =>{ }); router.get('/delete', async (req, res) => { - if (!await decodeTokenFromBodyOrQuery(req)) { + if (!await decodeToken(req)) { return res.sendStatus(UNAUTHORIZED); } const { cardBytes } = req.query; if (!cardBytes) { - writeRequestResponse({ + writeLogToClient({ statusCode: BAD_REQUEST, endpoint: '/delete', message: 'cardBytes missing from request' @@ -157,7 +180,7 @@ router.get('/delete', async (req, res) => { if (!await checkIfCardExists(cardBytes)) { logger.info('Card does not exist'); - writeRequestResponse({ + writeLogToClient({ statusCode: NOT_FOUND, endpoint: '/delete', message: 'Card does not exist', @@ -168,7 +191,7 @@ router.get('/delete', async (req, res) => { const tryDeleteCard = await deleteCard(cardBytes); if (!tryDeleteCard) { logger.info('Error deleting card'); - writeRequestResponse({ + writeLogToClient({ statusCode: SERVER_ERROR, endpoint: '/delete', message: 'Error deleting card' @@ -176,10 +199,25 @@ router.get('/delete', async (req, res) => { return res.sendStatus(SERVER_ERROR); } logger.info('Successfully deleted card'); - writeRequestResponse({ statusCode: OK, endpoint: '/delete', message: 'Card deleted!' }); + writeLogToClient({ statusCode: OK, endpoint: '/delete', message: 'Card deleted!' }); return res.sendStatus(OK); }); +router.get('/getAllCards', async (req, res) => { + if (!await decodeToken(req)) { + return res.sendStatus(UNAUTHORIZED); + } + + let getCards = await getAllCards(); + if (!getCards) { + logger.info('Error retrieving cards'); + return res.sendStatus(SERVER_ERROR); + } + + logger.info('Retrieved all cards successfully!'); + res.json(getCards).status(OK); +}); + router.get('/listen', async (req, res) => { if (!await decodeTokenFromBodyOrQuery(req)) { return res.sendStatus(UNAUTHORIZED); diff --git a/src/APIFunctions/CardReader.js b/src/APIFunctions/CardReader.js new file mode 100644 index 000000000..7ba2492cf --- /dev/null +++ b/src/APIFunctions/CardReader.js @@ -0,0 +1,46 @@ +import { ApiResponse } from './ApiResponses'; +import { BASE_API_URL } from '../Enums'; + +export async function getCardsFromDb(token) { + let status = new ApiResponse(); + try { + const url = new URL('/api/OfficeAccessCard/getAllCards', BASE_API_URL); + const res = await fetch(url.href, { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + if (res.ok) { + const result = await res.json(); + status.responseData = result; + } else { + status.error = true; + } + } catch (err) { + status.error = true; + status.responseData = err; + } + return status; +} + +export async function deleteCardFromDb(token, cardBytes) { + let status = new ApiResponse(); + try { + const url = new URL('/api/OfficeAccessCard/delete', BASE_API_URL); + url.searchParams.append('cardBytes', cardBytes); + const res = await fetch(url.href, { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + if (res.ok) { + const result = await res.json(); + status.responseData = result; + } else { + status.error = true; + } + } catch (err) { + status.error = true; + status.responseData = err; + } +} diff --git a/src/APIFunctions/Messaging.js b/src/APIFunctions/Messaging.js index 7fb04f0bf..d29f6e5d5 100644 --- a/src/APIFunctions/Messaging.js +++ b/src/APIFunctions/Messaging.js @@ -25,7 +25,7 @@ export async function sendMessage(id, token, message) { } } catch(err) { status.error = true; - status.responseData = error; + status.responseData = err; } return status; } diff --git a/src/Pages/CardReader/CardReader.js b/src/Pages/CardReader/CardReader.js index 8f66eb6d0..1378197a2 100644 --- a/src/Pages/CardReader/CardReader.js +++ b/src/Pages/CardReader/CardReader.js @@ -1,28 +1,64 @@ import { useState, useEffect } from 'react'; import { BASE_API_URL } from '../../Enums'; +import { getCardsFromDb, deleteCardFromDb } from '../../APIFunctions/CardReader'; +import ConfirmationModal from '../../Components/DecisionModal/ConfirmationModal'; +import { trashcanSymbol } from '../Overview/SVG'; export default function CardReader(props) { const [logs, setLogs] = useState([]); - const [error, setError] = useState(''); - const token = props.user.token; + const [cards, setCards] = useState([]); + const [toggleDelete, setToggleDelete] = useState(false); + const [cardToDelete, setCardToDelete] = useState({}); + const [tab, setTab] = useState(() => { + const params = new URLSearchParams(window.location.search); + return params.get('tab') || 'registry'; + }); + const [connected, setConnected] = useState(true); const buildLog = (data) => { let date = new Date().toISOString().padEnd(30, ' '); let endpoint = data.endpoint.padEnd(20, ' '); - let statusCode = String(data.statusCode).padEnd(8, ' '); + let statusCode = String(data.statusCode).padEnd(15, ' '); return [date, endpoint, statusCode, data.message].join(''); }; + const header = [ + 'TIMESTAMP'.padEnd(30), + 'ENDPOINT'.padEnd(20), + 'STATUS CODE'.padEnd(15), + 'MESSAGE\n' + ].join(''); + + async function getAllCards() { + const cardsFromDb = await getCardsFromDb(props.user.token); + if (!cardsFromDb.error) { + setCards(cardsFromDb.responseData); + } + } + + function handleTabChange(newTab) { + setTab(newTab); + const params = new URLSearchParams(window.location.search); + params.set('tab', newTab); + + const newUrl = `${window.location.pathname}?${params.toString()}`; + window.history.pushState({}, '', newUrl); + } + useEffect(() => { + getAllCards(); const url = new URL('/api/OfficeAccessCard/listen', BASE_API_URL); - url.searchParams.append('token', token); + url.searchParams.append('token', props.user.token); const eventSource = new EventSource(url.href); eventSource.onmessage = (event) => { try { let data = JSON.parse(event.data); + if (data.endpoint === '/verify?add=1') { // re-query if card was added while client open + getAllCards(); + } let newLog = buildLog(data); setLogs(currLogs => [newLog, ...currLogs]); // prepend the new log - } catch (error) { + } catch (err) { setLogs( (currLogs) => [ '[error] unable to format response, check browser logs', @@ -33,24 +69,126 @@ export default function CardReader(props) { }; eventSource.onerror = () => { - setError('Error connecting to SSE'); + setLogs( + (currLogs) => [ + '...crickets...', + ...currLogs + ] + ); + setConnected(false); }; return () => { eventSource.close(); }; - }, []); return ( -
-

SCE Card Reader Activity

-
-        {logs.join('\n')}
-      
- {error && -

{error}

- } +
+

SCE Card Reader Page

+
+ +
 | 
+ +
+ {tab === 'registry' ? ( +
+ { + await deleteCardFromDb(props.user.token, cardToDelete.cardBytes); + await getAllCards(); + setToggleDelete(!toggleDelete); + }, + open: toggleDelete + } + } /> +
+ + + + {[ + 'Card Bytes', + 'Registration Date', + 'Last Verified At', + 'Verified Count' + ].map(title => ( + + ))} + + + + {cards.map(card => { + return ( + + + + + + + + ); + })} + +
+
+ {title} +
+
+
+ {card.cardBytes} +
+
+
+ {card.createdAt} +
+
+
+ {card.lastVerified} +
+
+
+ {card.verifiedCount} +
+
+ +
+
+
+ ) : ( +
+

+ {connected ? 'Listening for card reader activity...' : 'Client disconnected. Please reload to reconnect.'} +

+
+            {header}
+            {logs.join('\n')}
+          
+
+ )}
); } From 2dc18dd716e4afa254173147fab47277f148c507 Mon Sep 17 00:00:00 2001 From: adarshm11 Date: Sat, 28 Jun 2025 17:54:11 -0700 Subject: [PATCH 02/11] Addressed previous comments --- api/main_endpoints/routes/OfficeAccessCard.js | 17 ++--- src/APIFunctions/CardReader.js | 8 ++- src/Pages/CardReader/CardReader.js | 68 +++++++++++-------- 3 files changed, 50 insertions(+), 43 deletions(-) diff --git a/api/main_endpoints/routes/OfficeAccessCard.js b/api/main_endpoints/routes/OfficeAccessCard.js index f233586e7..8e0d39ef2 100644 --- a/api/main_endpoints/routes/OfficeAccessCard.js +++ b/api/main_endpoints/routes/OfficeAccessCard.js @@ -76,7 +76,7 @@ function getAllCards() { , (error, result) => { if (error) { logger.error('getAllCards got an error querying mongodb'); - return resolve(null); + return resolve([]); } if (!result) { logger.info('Could not retrieve any cards from mongodb'); // double check that this is a correct message @@ -163,12 +163,12 @@ router.get('/verify', async (req, res) =>{ } }); -router.get('/delete', async (req, res) => { +router.post('/delete', async (req, res) => { if (!await decodeToken(req)) { return res.sendStatus(UNAUTHORIZED); } - const { cardBytes } = req.query; + const { cardBytes } = req.body; if (!cardBytes) { writeLogToClient({ statusCode: BAD_REQUEST, @@ -208,14 +208,11 @@ router.get('/getAllCards', async (req, res) => { return res.sendStatus(UNAUTHORIZED); } - let getCards = await getAllCards(); - if (!getCards) { - logger.info('Error retrieving cards'); - return res.sendStatus(SERVER_ERROR); + const getCards = await getAllCards(); + if (getCards) { + return res.json(getCards).status(OK); } - - logger.info('Retrieved all cards successfully!'); - res.json(getCards).status(OK); + return res.sendStatus(SERVER_ERROR); }); router.get('/listen', async (req, res) => { diff --git a/src/APIFunctions/CardReader.js b/src/APIFunctions/CardReader.js index 7ba2492cf..529832448 100644 --- a/src/APIFunctions/CardReader.js +++ b/src/APIFunctions/CardReader.js @@ -27,11 +27,13 @@ export async function deleteCardFromDb(token, cardBytes) { let status = new ApiResponse(); try { const url = new URL('/api/OfficeAccessCard/delete', BASE_API_URL); - url.searchParams.append('cardBytes', cardBytes); const res = await fetch(url.href, { + method: 'POST', headers: { - 'Authorization': `Bearer ${token}` - } + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ cardBytes, }), }); if (res.ok) { const result = await res.json(); diff --git a/src/Pages/CardReader/CardReader.js b/src/Pages/CardReader/CardReader.js index 1378197a2..3a4c2b9df 100644 --- a/src/Pages/CardReader/CardReader.js +++ b/src/Pages/CardReader/CardReader.js @@ -83,25 +83,9 @@ export default function CardReader(props) { }; }, []); - return ( -
-

SCE Card Reader Page

-
- -
 | 
- -
- {tab === 'registry' ? ( + function getComponentFromTabChoice(tab) { + if (tab === 'registry') { + return (
- ) : ( -
-

- {connected ? 'Listening for card reader activity...' : 'Client disconnected. Please reload to reconnect.'} -

-
-            {header}
-            {logs.join('\n')}
-          
-
- )} + ); + } + return ( +
+

+ {connected ? 'Listening for card reader activity...' : 'Client disconnected. Please reload to reconnect.'} +

+
+          {header}
+          {logs.join('\n')}
+        
+
+ ); + } + + return ( +
+

SCE Card Reader Page

+
This webpage manages RFID cards used to unlock the office door in the SCE room
+
+ +
 | 
+ +
+ {getComponentFromTabChoice(tab)}
); } From 5e324b1f1f2b451ca9090a2f50d806bcaf84ef27 Mon Sep 17 00:00:00 2001 From: adarshm11 Date: Sat, 28 Jun 2025 18:41:58 -0700 Subject: [PATCH 03/11] Addressed comments --- api/main_endpoints/routes/OfficeAccessCard.js | 2 +- src/Pages/CardReader/CardReader.js | 88 ++++++++++--------- 2 files changed, 48 insertions(+), 42 deletions(-) diff --git a/api/main_endpoints/routes/OfficeAccessCard.js b/api/main_endpoints/routes/OfficeAccessCard.js index 8e0d39ef2..bae9be370 100644 --- a/api/main_endpoints/routes/OfficeAccessCard.js +++ b/api/main_endpoints/routes/OfficeAccessCard.js @@ -75,7 +75,7 @@ function getAllCards() { {} , (error, result) => { if (error) { - logger.error('getAllCards got an error querying mongodb'); + logger.error('getAllCards got an error querying mongodb', error); return resolve([]); } if (!result) { diff --git a/src/Pages/CardReader/CardReader.js b/src/Pages/CardReader/CardReader.js index 3a4c2b9df..08df3d6f0 100644 --- a/src/Pages/CardReader/CardReader.js +++ b/src/Pages/CardReader/CardReader.js @@ -53,7 +53,8 @@ export default function CardReader(props) { eventSource.onmessage = (event) => { try { let data = JSON.parse(event.data); - if (data.endpoint === '/verify?add=1') { // re-query if card was added while client open + const url = new URL(data.endpoint, window.location.origin); + if (url.pathname === '/verify' && url.searchParams.get('add') === '1') { // re-query if card was added while client open getAllCards(); } let newLog = buildLog(data); @@ -83,6 +84,46 @@ export default function CardReader(props) { }; }, []); + function handleDeleteClick(card) { + setToggleDelete(!toggleDelete); + setCardToDelete(card); + } + + const generateCardEntry = (card) => { + return ( + + +
+ {card.cardBytes} +
+ + +
+ {card.createdAt} +
+ + +
+ {card.lastVerified} +
+ + +
+ {card.verifiedCount} +
+ + + + + + ); + }; + function getComponentFromTabChoice(tab) { if (tab === 'registry') { return ( @@ -104,8 +145,8 @@ export default function CardReader(props) { } />
- - + + {[ 'Card Bytes', 'Registration Date', @@ -120,44 +161,8 @@ export default function CardReader(props) { ))} - - {cards.map(card => { - return ( - - - - - - - - ); - })} + + {cards.map(card => generateCardEntry(card))}
-
- {card.cardBytes} -
-
-
- {card.createdAt} -
-
-
- {card.lastVerified} -
-
-
- {card.verifiedCount} -
-
- -
@@ -188,6 +193,7 @@ export default function CardReader(props) { > Card Registry + {/* spacer to differentiate between the two options */}
 | 
+ + + ); + } + useEffect(() => { getAllCards(); const url = new URL('/api/OfficeAccessCard/listen', BASE_API_URL); @@ -54,7 +95,8 @@ export default function CardReader(props) { try { let data = JSON.parse(event.data); const url = new URL(data.endpoint, window.location.origin); - if (url.pathname === '/verify' && url.searchParams.get('add') === '1') { // re-query if card was added while client open + // if a successful call to /verify -> should re-query DB to add new card / update verifiedCount of existing card + if (url.pathname === '/verify' && data.statusCode === 200) { getAllCards(); } let newLog = buildLog(data); @@ -84,45 +126,13 @@ export default function CardReader(props) { }; }, []); - function handleDeleteClick(card) { - setToggleDelete(!toggleDelete); - setCardToDelete(card); - } - - const generateCardEntry = (card) => { - return ( - - -
- {card.cardBytes} -
- - -
- {card.createdAt} -
- - -
- {card.lastVerified} -
- - -
- {card.verifiedCount} -
- - - - - - ); - }; + useEffect(() => { + if (connected) { + setConnectionStatusText('Listening for card reader activity...'); + } else { + setConnectionStatusText('Client disconnected. Please reload to reconnect.'); + } + }, [connected]); function getComponentFromTabChoice(tab) { if (tab === 'registry') { @@ -132,7 +142,7 @@ export default function CardReader(props) { headerText: `Delete card: ${cardToDelete.cardBytes}?`, bodyText: `Are you sure you want to delete card: ${cardToDelete.cardBytes}? It'll be gone forever if you do.`, - confirmText: `Yes, delete card: ${cardToDelete.cardBytes}`, + confirmText: `Yes, delete ${cardToDelete.cardBytes}`, cancelText: 'No, keep the card', confirmClassAddons: 'bg-red-600 hover:bg-red-500', handleConfirmation: async () => { @@ -143,7 +153,7 @@ export default function CardReader(props) { open: toggleDelete } } /> -
+
@@ -162,7 +172,7 @@ export default function CardReader(props) { - {cards.map(card => generateCardEntry(card))} + {cards.map(card => )}
@@ -172,7 +182,7 @@ export default function CardReader(props) { return (

- {connected ? 'Listening for card reader activity...' : 'Client disconnected. Please reload to reconnect.'} + {connectionStatusText}

           {header}

From 0935119b004a8543a3589d083e744b8bf6e6f0d9 Mon Sep 17 00:00:00 2001
From: adarshm11 
Date: Fri, 4 Jul 2025 09:38:51 -0700
Subject: [PATCH 05/11] added light mode compatibility, adjusted log format,
 set default card verifiedCount to 1

---
 api/main_endpoints/models/OfficeAccessCard.js |  2 +-
 api/main_endpoints/routes/OfficeAccessCard.js | 17 ++++++++---
 src/Pages/CardReader/CardReader.js            | 30 ++++++++++---------
 3 files changed, 30 insertions(+), 19 deletions(-)

diff --git a/api/main_endpoints/models/OfficeAccessCard.js b/api/main_endpoints/models/OfficeAccessCard.js
index fb612711a..e6dc91968 100644
--- a/api/main_endpoints/models/OfficeAccessCard.js
+++ b/api/main_endpoints/models/OfficeAccessCard.js
@@ -14,7 +14,7 @@ const OfficeAccessCardSchema = new Schema(
     },
     verifiedCount:{
       type:Number,
-      default:0
+      default:1
     },
     lastVerified:{
       type:Date,
diff --git a/api/main_endpoints/routes/OfficeAccessCard.js b/api/main_endpoints/routes/OfficeAccessCard.js
index bae9be370..6f7921929 100644
--- a/api/main_endpoints/routes/OfficeAccessCard.js
+++ b/api/main_endpoints/routes/OfficeAccessCard.js
@@ -97,6 +97,7 @@ const defaultResponse = {
   cardWasAdded: false,
   message: 'Card authorized!',
   endpoint: '/verify',
+  requestType: 'HTTP GET',
 };
 
 const writeLogToClient = ({ statusCode, ...rest }) => {
@@ -147,7 +148,7 @@ router.get('/verify', async (req, res) =>{
     if (add) {
       logger.info('adding a new card');
       await new OfficeAccessCard({
-        cardBytes
+        cardBytes,
       }).save();
       writeLogToClient({ statusCode: OK, message: 'Card added!', endpoint: '/verify?add=1' });
       return res.sendStatus(OK);
@@ -173,7 +174,8 @@ router.post('/delete', async (req, res) => {
     writeLogToClient({
       statusCode: BAD_REQUEST,
       endpoint: '/delete',
-      message: 'cardBytes missing from request'
+      message: 'cardBytes missing from request',
+      requestType: 'HTTP POST',
     });
     return res.sendStatus(BAD_REQUEST);
   }
@@ -184,6 +186,7 @@ router.post('/delete', async (req, res) => {
       statusCode: NOT_FOUND,
       endpoint: '/delete',
       message: 'Card does not exist',
+      requestType: 'HTTP POST',
     });
     return res.sendStatus(NOT_FOUND);
   }
@@ -194,12 +197,18 @@ router.post('/delete', async (req, res) => {
     writeLogToClient({
       statusCode: SERVER_ERROR,
       endpoint: '/delete',
-      message: 'Error deleting card'
+      message: 'Error deleting card',
+      requestType: 'HTTP POST',
     });
     return res.sendStatus(SERVER_ERROR);
   }
   logger.info('Successfully deleted card');
-  writeLogToClient({ statusCode: OK, endpoint: '/delete', message: 'Card deleted!' });
+  writeLogToClient({
+    statusCode: OK,
+    endpoint: '/delete',
+    message: 'Card deleted!',
+    requestType: 'HTTP POST',
+  });
   return res.sendStatus(OK);
 });
 
diff --git a/src/Pages/CardReader/CardReader.js b/src/Pages/CardReader/CardReader.js
index f585d1397..cdb572263 100644
--- a/src/Pages/CardReader/CardReader.js
+++ b/src/Pages/CardReader/CardReader.js
@@ -6,6 +6,7 @@ import { trashcanSymbol } from '../Overview/SVG';
 
 const header = [
   'TIMESTAMP'.padEnd(30),
+  'TYPE'.padEnd(12),
   'ENDPOINT'.padEnd(20),
   'STATUS CODE'.padEnd(15),
   'MESSAGE\n'
@@ -24,10 +25,11 @@ export default function CardReader(props) {
   const [connectionStatusText, setConnectionStatusText] = useState('');
 
   function buildLog(data) {
-    let date = new Date().toISOString().padEnd(30, ' ');
-    let endpoint = data.endpoint.padEnd(20, ' ');
-    let statusCode = String(data.statusCode).padEnd(15, ' ');
-    return [date, endpoint, statusCode, data.message].join('');
+    let date = new Date().toISOString().padEnd(30);
+    let endpoint = data.endpoint.padEnd(20);
+    let statusCode = String(data.statusCode).padEnd(15);
+    let requestType = data.requestType.padEnd(12)
+    return [date, requestType, endpoint, statusCode, data.message].join('');
   }
 
   async function getAllCards() {
@@ -55,22 +57,22 @@ export default function CardReader(props) {
     return (
       
         
-          
+
{card.cardBytes}
-
+
{card.createdAt}
-
+
{card.lastVerified}
-
+
{card.verifiedCount}
@@ -111,7 +113,7 @@ export default function CardReader(props) { } }; - eventSource.onerror = () => { + eventSource.onerror = (error) => { setLogs( (currLogs) => [ '...crickets...', @@ -181,10 +183,10 @@ export default function CardReader(props) { } return (
-

+

{connectionStatusText}

-
+        
           {header}
           {logs.join('\n')}
         
@@ -194,9 +196,9 @@ export default function CardReader(props) { return (
-

SCE Card Reader Page

-
This webpage manages RFID cards used to unlock the office door in the SCE room
-
+

SCE Card Reader Page

+
This webpage manages RFID cards used to unlock the office door in the SCE room
+
+ +
+ + ); + } + return <>; + } + function getComponentFromTabChoice(tab) { if (tab === 'registry') { return ( @@ -158,19 +258,49 @@ export default function CardReader() { open: toggleDelete } } /> -
+
+
+ +
{[ - 'Card Bytes', - 'Registration Date', - 'Last Verified At', - 'Verified Count' - ].map(title => ( - ))} @@ -180,6 +310,7 @@ export default function CardReader() { {cards.map(card => )}
+ { title: 'Card Bytes', columnName: 'cardBytes' }, + { title: 'Registration Date', columnName: 'registrationDate' }, + { title: 'Last Verified At', columnName: 'lastVerifiedAt' }, + { title: 'Verified Count', columnName: 'verifiedCount' } + ].map(({ title, columnName = null }) => ( +
- {title} + + + + + + +
+ {maybeRenderPagination()}
); @@ -203,7 +334,7 @@ export default function CardReader() {
This webpage manages RFID cards used to unlock the office door in the SCE room