diff options
Diffstat (limited to 'api/templates')
-rw-r--r-- | api/templates/dashboard.html | 160 | ||||
-rw-r--r-- | api/templates/login.html | 105 | ||||
-rw-r--r-- | api/templates/signup.html | 112 |
3 files changed, 377 insertions, 0 deletions
diff --git a/api/templates/dashboard.html b/api/templates/dashboard.html new file mode 100644 index 0000000..25372bd --- /dev/null +++ b/api/templates/dashboard.html @@ -0,0 +1,160 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>LinkLogger | Dashboard</title> + + <link rel="stylesheet" href="/static/css/dashboard.css"> + <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet"> + <script src="/static/js/jwt.js"></script> +</head> +<body> + <div> + <!-- Create a table with 4 columns with a total of 1000px width --> + <table> + <tr style="border: 2px solid #ccc;"> + <th>Link</th> + <th>Visits</th> + <th>Redirect</th> + <th>Expire Date</th> + </tr> + </table> + </div> +</body> +</html> +<script> + function createRow(index, link, logs) { + // Create the sub-table with the logs + let subTable = ` + <table> + <tr> + <th>ID</th> + <th>Timestamp</th> + <th>IP</th> + <th>Location</th> + <th colspan="2">ISP</th> + </tr> + `; + // Loop through the logs and create a row for each one + logs.forEach((log, index) => { + let logTimestamp = new Date(log.timestamp).toLocaleString('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }).replace(',', ''); + + let row = ` + <tr id="${log.id}-log"> + <td>${logs.length - index}</td> + <td>${logTimestamp}</td> + <td>${log.ip}</td> + <td>${log.location}</td> + <td>${log.isp}</td> + <td><i class="fa-solid fa-trash" id="${log.id}/${link.link}""></i></td> + </tr> + `; + subTable += row; + }); + subTable += '</table>'; + + // Convert the link expire timestamp to a readable date + let date = new Date(link.expire_date); + let expireDate = date.toLocaleTimeString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }); + + // Create the HTML for the row with sub-table + let row = ` + <tr class="link-table-row"> + <td> + <button class="link-button" id="${index}">${link.link}</button> + </td> + <td>${logs.length}</td> + <td>${link.redirect_link}</td> + <td>${expireDate}</td> + </tr> + <tr class="log-table-row" id="${index}-logTR" style="display: none;"> + <td colspan="6"> + ${subTable} + </td> + </tr> + `; + return row; + } + + + + async function getData() { + const links = await accessAPI(`/links`, 'GET') + if (!links) { + throw new Error('Failed to fetch links'); + } + // Links is an Array of objects with the link data + // Loop through the links and create a row for each one + // Do not use async because then the order or data in the + // table will change from time to time + for (let i = 0; i < links.length; i++) { + let link = links[i]; + let logs = await accessAPI(`/links/${link.link}/logs`, 'GET') + if (!logs) { + throw new Error('Failed to fetch logs'); + } + let row = createRow(i, link, logs); + document.querySelector('table').innerHTML += row; + } + } + + // hideLogRows to all log-table-rows + function hideLogRows() { + let logTRs = document.querySelectorAll('.log-table-row'); + logTRs.forEach(row => { + row.style.display = 'none'; + }); + } + + // Add event listener to all link buttons + document.addEventListener('click', (event) => { + if (event.target.classList.contains('link-button')) { + let id = event.target.id; + let logTR = document.getElementById(`${id}-logTR`); + if (logTR.style.display === 'none') { + // Hide any open log tables + hideLogRows(); + logTR.style.display = 'table-row'; + } else { + logTR.style.display = 'none'; + } + } + }); + + // Add an event listen to all trash bins + document.addEventListener('click', (event) => { + if (event.target.classList.contains('fa-trash')) { + // Confirm the user wants to delete the log + let confirmDelete = confirm('Are you sure you want to delete this log?'); + if (confirmDelete) { + let id = event.target.id; + let link = id.split('/')[1]; + let logId = id.split('/')[0]; + fetch(`/api/links/${link}/logs/${logId}`, { + method: 'DELETE' + }); + let logRow = document.getElementById(`${logId}-log`) + logRow.remove(); + } + } + }); + + getData(); +</script>
\ No newline at end of file diff --git a/api/templates/login.html b/api/templates/login.html new file mode 100644 index 0000000..8e59481 --- /dev/null +++ b/api/templates/login.html @@ -0,0 +1,105 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>LinkLogger | Login</title> +</head> +<body> + <div> + <p id="error">Incorrect username/password. Please try again.</p> + <form action="/login" method="POST"> + <input type="text" name="username" placeholder="Username" required> + <input type="password" name="password" placeholder="Password" required> + <button type="submit">Login</button> + </form> + <hr> + <p>Don't have an account? <a href="/signup">Create one now</a></p> + </div> +</body> +</html> + +<style> + body { + margin: 0; + padding: 0; + font-family: Arial, sans-serif; + background-color: #2c3338; + } + + div { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; + } + + input { + display: block; + margin: 10px auto; + width: 300px; + border-radius: 5px; + padding: 15px; + color: #ccc; + background-color: #3b4148; + border: none; + font-size: 17px; + } + + button { + display: block; + margin: 10px auto; + width: 100%; + border-radius: 5px; + padding: 15px; + color: #ccc; + background-color: #415eac; + border: none; + font-size: 17px; + cursor: pointer; + } + + hr { + color: #606468; + } + + p { + color: #606468; + } + + #error { + font-size: 15px; + color: #f55757; + display: none; + } + + a { + color: #ccc; + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } +</style> + +<script> + document.querySelector('form').addEventListener('submit', async function(event) { + // Prevent default form submission + event.preventDefault(); + + const formData = new FormData(this); + // Send POST request to /token containing form data + const response = await fetch('/api/auth/token', { + method: 'POST', + body: formData + }); + + if (response.status != 200) { + document.getElementById('error').style.display = 'block'; + } else { + window.location.href = '/dashboard'; + } + }); +</script>
\ No newline at end of file diff --git a/api/templates/signup.html b/api/templates/signup.html new file mode 100644 index 0000000..32962b7 --- /dev/null +++ b/api/templates/signup.html @@ -0,0 +1,112 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>LinkLogger | Signup</title> +</head> +<body> + <div> + <p id="error"></p> + <form action="/signup" method="POST"> + <input type="text" name="username" placeholder="Username" required> + <input type="password" name="password" placeholder="Password" required> + <button type="submit">Signup</button> + </form> + <hr> + <p>Already have an account? <a href="/login">Log in now</a></p> + <p>Passwords must be at least 8 characters long and contain a number, special character, and uppercase character.</p> + </div> +</body> +</html> + +<style> + body { + margin: 0; + padding: 0; + font-family: Arial, sans-serif; + background-color: #2c3338; + } + + div { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; + max-width: 330px; + } + + input { + display: block; + margin: 10px auto; + width: 300px; + border-radius: 5px; + padding: 15px; + color: #ccc; + background-color: #3b4148; + border: none; + font-size: 17px; + } + + button { + display: block; + margin: 10px auto; + width: 100%; + border-radius: 5px; + padding: 15px; + color: #ccc; + background-color: #415eac; + border: none; + font-size: 17px; + cursor: pointer; + } + + hr { + color: #606468; + } + + p { + color: #606468; + } + + #error { + font-size: 15px; + color: #f55757; + display: none; + } + + a { + color: #ccc; + text-decoration: none; + } + + a:hover { + text-decoration: underline; + } +</style> + +<script> + document.querySelector('form').addEventListener('submit', async function(event) { + // Prevent default form submission + event.preventDefault(); + + // Get form data + const formData = new FormData(this); + + // Send POST request + const response = await fetch('/api/users/register', { + method: 'POST', + body: formData + }); + + if (response.status != 200) { + const data = await response.json() + + document.getElementById('error').style.display = 'block'; + document.getElementById('error').innerText = data.detail; + } else { + window.location.href = '/dashboard'; + } + }); +</script>
\ No newline at end of file |