Add main admin page and edit page for servers. TODO: Add page to create new servers, Add page to modify admin users
This commit is contained in:
parent
72a85c808b
commit
8c6c0daadd
38
index.js
38
index.js
|
@ -19,7 +19,7 @@ const db = new sqlite3.Database('astrocom.db', (err) => {
|
|||
|
||||
// Create 'routes' table
|
||||
// We need to store server address, port, secret, block_start and block_length. Then make a query that takes in an arbitrary number, and returns a row if that number between block start and block start + block length.
|
||||
db.run('CREATE TABLE IF NOT EXISTS routes (id INTEGER PRIMARY KEY AUTOINCREMENT, server TEXT NOT NULL, port INTEGER NOT NULL DEFAULT 4569, auth TEST NOT NULL DEFAULT astrocom, secret TEXT NOT NULL, block_start INTEGER UNIQUE NOT NULL, block_length INTEGER NOT NULL DEFAULT 9999, apiKey TEXT NOT NULL)');
|
||||
db.run('CREATE TABLE IF NOT EXISTS routes (id INTEGER PRIMARY KEY AUTOINCREMENT, server TEXT NOT NULL, port INTEGER NOT NULL DEFAULT 4569, auth TEST NOT NULL DEFAULT \'from-astrocom\', secret TEXT NOT NULL, block_start INTEGER UNIQUE NOT NULL, block_length INTEGER NOT NULL DEFAULT 9999, apiKey TEXT NOT NULL)');
|
||||
db.run('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, passwordHash TEXT NOT NULL)');
|
||||
// Check if user 1 exists, if not, create it (admin:admin)
|
||||
const saltRounds = 10;
|
||||
|
@ -67,6 +67,8 @@ app.use(expressSession({
|
|||
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', __dirname + '/views');
|
||||
// Static files
|
||||
app.use(express.static('public'));
|
||||
|
||||
// Admin routes
|
||||
|
||||
|
@ -77,7 +79,34 @@ app.get('/admin/logout', (req, res) => {
|
|||
});
|
||||
|
||||
app.get('/admin/login', (req, res) => {
|
||||
res.render('login');
|
||||
res.render('admin/login');
|
||||
});
|
||||
|
||||
app.get('/admin', (req, res) => {
|
||||
if (!req.session.authenticated) {
|
||||
res.redirect('/admin/login');
|
||||
return;
|
||||
}
|
||||
res.render('admin/index', { user: req.session.user });
|
||||
});
|
||||
|
||||
app.get('/admin/route/:id', (req, res) => {
|
||||
if (!req.session.authenticated) {
|
||||
res.redirect('/admin/login');
|
||||
return;
|
||||
}
|
||||
db.get('SELECT * FROM routes WHERE id = ?', [req.params.id], (err, row) => {
|
||||
if (err) {
|
||||
console.error('Error getting route:', err);
|
||||
res.status(500).send('Internal server error');
|
||||
return;
|
||||
}
|
||||
if (!row) {
|
||||
res.status(404).send('Not Found');
|
||||
return;
|
||||
}
|
||||
res.render('admin/edit', { user: req.session.user, data: row });
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/admin/login', (req, res) => {
|
||||
|
@ -101,6 +130,7 @@ app.post('/admin/login', (req, res) => {
|
|||
}
|
||||
if (result) {
|
||||
req.session.authenticated = true;
|
||||
req.session.user = row.username;
|
||||
res.redirect('/admin');
|
||||
} else {
|
||||
res.status(401).send('Unauthorized');
|
||||
|
@ -151,7 +181,7 @@ app.post('/api/v1/admin/route', (req, res) => { // Create a new route
|
|||
}
|
||||
const server = req.body.server;
|
||||
const port = req.body.port;
|
||||
const auth = req.body.auth || "astrocom";
|
||||
const auth = req.body.auth || "from-astrocom";
|
||||
const secret = req.body.secret || crypto.randomBytes(15).toString('hex');
|
||||
const block_start = req.body.block_start;
|
||||
const block_length = req.body.block_length || 9999;
|
||||
|
@ -225,6 +255,8 @@ app.delete('/api/v1/admin/route/:id', (req, res) => { // Delete a route
|
|||
});
|
||||
});
|
||||
|
||||
// == END ADMIN ROUTES ==
|
||||
|
||||
// Query to get a route
|
||||
app.get('/api/v1/route/:apiKey/:ani/:number', (req, res) => {
|
||||
const apiKey = req.params.apiKey;
|
||||
|
|
6
public/assets/css/bootstrap.min.css
vendored
Normal file
6
public/assets/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
28
public/assets/js/adminEdit.js
Normal file
28
public/assets/js/adminEdit.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const routeId = document.getElementById('route').value;
|
||||
|
||||
async function updateRoute(routeData) {
|
||||
try {
|
||||
const response = await fetch(`/api/v1/admin/route/${routeId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(routeData)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error updating route:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Export the function if needed
|
||||
window.updateRoute = updateRoute;
|
||||
});
|
55
public/assets/js/adminMain.js
Normal file
55
public/assets/js/adminMain.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
document.addEventListener('DOMContentLoaded', function() {
|
||||
fetchRoutes();
|
||||
});
|
||||
|
||||
async function fetchRoutes() {
|
||||
try {
|
||||
const response = await fetch('/api/v1/admin/routes');
|
||||
const routes = await response.json();
|
||||
populateTable(routes);
|
||||
} catch (error) {
|
||||
console.error('Error fetching routes:', error);
|
||||
}
|
||||
}
|
||||
|
||||
const deleteRoute = async (id) => {
|
||||
// Confirm deletion
|
||||
if (!confirm('Are you sure you want to delete this route?')) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/api/v1/admin/route/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (response.status === 200) {
|
||||
fetchRoutes();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting route:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function populateTable(routes) {
|
||||
const tableBody = document.querySelector('#adminTable tbody');
|
||||
if (!tableBody) return;
|
||||
|
||||
tableBody.innerHTML = '';
|
||||
|
||||
routes.forEach(route => {
|
||||
const row = document.createElement('tr');
|
||||
row.className = 'table-dark';
|
||||
row.innerHTML = `
|
||||
<td class="text-light">${route.id || ''}</td>
|
||||
<td class="text-light">${route.server}:${route.port || ''}</td>
|
||||
<td class="text-light">${route.auth || ''}</td>
|
||||
<td class="text-light">${route.secret || ''}</td>
|
||||
<td class="text-light">${route.block_start || ''} - ${route.block_start + route.block_length || ''}</td>
|
||||
<td class="text-light">${route.apiKey || ''}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary edit-btn" onclick="window.location.href = '/admin/route/${route.id}'" data-id="${route.id}">Edit</button>
|
||||
<button class="btn btn-sm btn-outline-danger delete-btn" onclick="deleteRoute(${route.id})" data-id="${route.id}">Delete</button>
|
||||
</td>
|
||||
`;
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}
|
7
public/assets/js/bootstrap.bundle.min.js
vendored
Normal file
7
public/assets/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
public/assets/js/bootstrap.min.js
vendored
Normal file
7
public/assets/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
public/assets/js/jquery.min.js
vendored
Normal file
2
public/assets/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,34 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="text-align: right; padding: 10px;">
|
||||
<button onclick="location.href='/admin/logout'">Logout</button>
|
||||
</div>
|
||||
<h1>Endpoints</h1>
|
||||
<table border="1">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Server:Port</th>
|
||||
<th>IAX Username</th>
|
||||
<th>IAX Secret</th>
|
||||
<th>Assigned Block</th>
|
||||
<th>API Key</th>
|
||||
</tr>
|
||||
<% routes.forEach(route => { %>
|
||||
<tr>
|
||||
<td><%= route.id %></td>
|
||||
<td><%= route.server %>:<%= route.port %></td>
|
||||
<td><%= route.auth %></td>
|
||||
<td><%= route.secret %></td>
|
||||
<td><%= route.block_start %> - <%= route.block_start + route.block_length %></td>
|
||||
<td><%= route.apiKey %></td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
70
views/admin/edit.ejs
Normal file
70
views/admin/edit.ejs
Normal file
|
@ -0,0 +1,70 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
|
||||
<title>AstroCom Admin - Editing <%= data.id %></title>
|
||||
</head>
|
||||
<body class="bg-dark text-white">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">AstroCom</a>
|
||||
<div class="navbar-nav ms-auto">
|
||||
<span class="navbar-text me-3">
|
||||
Welcome, <%= user %>
|
||||
</span>
|
||||
<a href="/admin/logout" class="btn btn-danger">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container mt-4"></div>
|
||||
<h2>Edit Entry <%= data.id %></h2>
|
||||
<form id="editForm" onsubmit="return false;">
|
||||
<% for (const [key, value] of Object.entries(data)) { %>
|
||||
<% if (key !== 'id') { %>
|
||||
<div class="mb-3">
|
||||
<label for="<%= key %>" class="form-label"><%= key.charAt(0).toUpperCase() + key.slice(1) %></label>
|
||||
<input type="text" class="form-control bg-dark text-white" id="<%= key %>" name="<%= key %>" value="<%= value %>">
|
||||
</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
<button type="submit" class="btn btn-primary">Update</button>
|
||||
<a href="/admin" class="btn btn-secondary">Cancel</a>
|
||||
</form>
|
||||
<script src="/assets/js/adminEdit.js"></script>
|
||||
</div>
|
||||
<script>
|
||||
const route = <%= data.id %>;
|
||||
|
||||
const editForm = document.getElementById('editForm');
|
||||
editForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(editForm);
|
||||
const data = {};
|
||||
|
||||
for (const [key, value] of formData.entries()) {
|
||||
data[key] = value;
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/v1/admin/route/${route}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
window.location.href = '/admin';
|
||||
} else {
|
||||
alert('Failed to update entry');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="/assets/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/assets/js/jquery.min.js"></script>
|
||||
</body>
|
||||
</html>
|
43
views/admin/index.ejs
Normal file
43
views/admin/index.ejs
Normal file
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
|
||||
<title>AstroCom Admin</title>
|
||||
</head>
|
||||
<body class="bg-dark text-white">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">AstroCom</a>
|
||||
<div class="navbar-nav ms-auto">
|
||||
<span class="navbar-text me-3">
|
||||
Welcome, <%= user %>
|
||||
</span>
|
||||
<a href="/admin/logout" class="btn btn-danger">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<h2>Admin Dashboard</h2>
|
||||
<table class="table table-striped table-dark" id="adminTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Hostname:Port</th>
|
||||
<th>IAX Username/Context</th>
|
||||
<th>IAX2 Secret</th>
|
||||
<th>Number Block</th>
|
||||
<th>API Key</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Data will be dynamically populated by adminMain.js -->
|
||||
</tbody>
|
||||
</table>
|
||||
<script src="/assets/js/adminMain.js"></script>
|
||||
<script src="/assets/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/assets/js/jquery.min.js"></script>
|
||||
</body>
|
||||
</html>
|
37
views/admin/login.ejs
Normal file
37
views/admin/login.ejs
Normal file
|
@ -0,0 +1,37 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
|
||||
<title>AstroCom Admin Login</title>
|
||||
</head>
|
||||
|
||||
<body class="bg-dark">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card bg-dark text-light shadow">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="text-center mb-4">Admin Login</h2>
|
||||
<form action="/admin/login" method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username:</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password:</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/assets/js/bootstrap.min.js"></script>
|
||||
<script src="/assets/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/assets/js/jquery.min.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,21 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/admin/login" method="POST">
|
||||
<div>
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue