aboutsummaryrefslogtreecommitdiff
path: root/api/templates
diff options
context:
space:
mode:
Diffstat (limited to 'api/templates')
-rw-r--r--api/templates/dashboard.html160
-rw-r--r--api/templates/login.html105
-rw-r--r--api/templates/signup.html112
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