Add code
This commit is contained in:
181
views/admin.ejs
Normal file
181
views/admin.ejs
Normal file
@ -0,0 +1,181 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Admin - Key/Info Management</title>
|
||||
<link rel="stylesheet" href="/css/admin_styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Secret Management</h1>
|
||||
<div class="nav-links">
|
||||
<a href="/admin">Manage Secrets</a>
|
||||
<a href="/admin/clients">Manage Clients</a>
|
||||
<a href="/admin/logout" class="logout-link">Logout</a>
|
||||
</div>
|
||||
|
||||
<% if (message && message.text) { %>
|
||||
<div class="alert <%= message.type === 'success' ? 'alert-success' : 'alert-error' %>">
|
||||
<%= message.text %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<h2>Manage Secrets</h2>
|
||||
|
||||
<% if (editingItemKey !== null && itemToEdit !== undefined && typeof itemToEdit.value !== 'undefined') { %>
|
||||
<h3>Edit Secret Value: <em><%= editingItemKey %></em></h3>
|
||||
<form action="/admin/update-secret" method="POST">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<input type="hidden" name="originalKey" value="<%= editingItemKey %>">
|
||||
<input type="hidden" name="secretKey" value="<%= editingItemKey %>"> <%# Key is not changeable here %>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Secret Key:</label>
|
||||
<p><strong><%= editingItemKey %></strong></p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Belongs to Group:</label>
|
||||
<p>
|
||||
<strong><%= itemToEdit.groupName %></strong> (ID: <%= itemToEdit.groupId %>)
|
||||
<% if (!itemToEdit.groupId) { %> <span class="text-danger">(Warning: No group assigned or group missing!)</span> <% } %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="editSecretValue">Value (JSON format recommended for complex data):</label>
|
||||
<textarea id="editSecretValue" name="secretValue" required><%= typeof itemToEdit.value === 'string' ? itemToEdit.value : JSON.stringify(itemToEdit.value, null, 2) %></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn">Update Secret Value</button>
|
||||
<a href="/admin" class="btn btn-secondary">Cancel Edit</a>
|
||||
</form>
|
||||
<% } else { %>
|
||||
<h3>Add New Secret</h3>
|
||||
<form action="/admin/add-secret" method="POST">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<div class="form-group">
|
||||
<label for="groupId">Select Group:</label>
|
||||
<select id="groupId" name="groupId" required class="form-control"> <%# Using form-control for consistent styling if bootstrap-like styles are used %>
|
||||
<% if (secretGroups && secretGroups.length > 0) { %>
|
||||
<option value="" disabled selected>-- Select a Group --</option>
|
||||
<% secretGroups.forEach(group => { %>
|
||||
<option value="<%= group.id %>"><%= group.name %> (ID: <%= group.id %>)</option>
|
||||
<% }); %>
|
||||
<% } else { %>
|
||||
<option value="" disabled selected>No groups available. Please create a group first.</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="secretKey">Secret Key:</label>
|
||||
<input type="text" id="secretKey" name="secretKey" required <% if (!secretGroups || secretGroups.length === 0) { %>disabled<% } %>>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="secretValue">Secret Value (JSON format recommended for complex data):</label>
|
||||
<textarea id="secretValue" name="secretValue" required <% if (!secretGroups || secretGroups.length === 0) { %>disabled<% } %>></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn" <% if (!secretGroups || secretGroups.length === 0) { %>disabled<% } %>>Add Secret</button>
|
||||
</form>
|
||||
<% } %>
|
||||
|
||||
<% if (typeof editingGroup !== 'undefined' && editingGroup) { %>
|
||||
<hr>
|
||||
<h2>Rename Secret Group: <%= editingGroup.name %> (ID: <%= editingGroup.id %>)</h2>
|
||||
<form action="/admin/groups/rename/<%= editingGroup.id %>" method="POST" class="mb-3">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<div class="form-group">
|
||||
<label for="newGroupName">New Group Name:</label>
|
||||
<input type="text" id="newGroupName" name="newGroupName" value="<%= editingGroup.name %>" required>
|
||||
</div>
|
||||
<button type="submit" class="btn">Rename Group</button>
|
||||
<a href="/admin" class="btn btn-secondary ml-2">Cancel</a>
|
||||
</form>
|
||||
<% } %>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Secret Groups</h2>
|
||||
|
||||
<h3>Create New Secret Group</h3>
|
||||
<form action="/admin/groups/create" method="POST" class="mb-3">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<div class="form-group">
|
||||
<label for="groupName">Group Name:</label>
|
||||
<input type="text" id="groupName" name="groupName" required>
|
||||
</div>
|
||||
<button type="submit" class="btn">Create Group</button>
|
||||
</form>
|
||||
|
||||
<% if (secretGroups && secretGroups.length > 0) { %>
|
||||
<h3>Existing Secret Groups</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Keys in Group</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% secretGroups.forEach(group => { %>
|
||||
<tr>
|
||||
<td><%= group.id %></td>
|
||||
<td><%= group.name %></td>
|
||||
<td><%= group.keys.length %></td>
|
||||
<td class="actions">
|
||||
<a href="/admin/groups/<%= group.id %>/secrets">View/Manage Secrets</a>
|
||||
<a href="/admin/groups/edit/<%= group.id %>" class="ml-2">Rename</a>
|
||||
<form action="/admin/groups/delete/<%= group.id %>" method="POST" class="form-inline" onsubmit="return confirm('Are you sure you want to delete the group \"<%= group.name %>\" (ID: <%= group.id %>)? All secrets within this group will also be permanently deleted.');">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<button type="submit" class="delete">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% } else { %>
|
||||
<p>No secret groups found. Create one above or via API.</p> <!-- Placeholder for create form -->
|
||||
<% } %>
|
||||
|
||||
|
||||
<hr style="margin: 30px 0;">
|
||||
|
||||
<h2>Existing Secrets</h2>
|
||||
<% if (secrets && secrets.length > 0) { %>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value (Preview)</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% secrets.forEach(secret => { %>
|
||||
<tr>
|
||||
<td><%= secret.key %></td>
|
||||
<td>
|
||||
<%
|
||||
let preview = typeof secret.value === 'string' ? secret.value : JSON.stringify(secret.value);
|
||||
if (preview.length > 50) preview = preview.substring(0, 50) + '...';
|
||||
%>
|
||||
<%= preview %>
|
||||
</td>
|
||||
<td class="actions">
|
||||
<a href="/admin/edit-secret/<%= encodeURIComponent(secret.key) %>">Edit</a>
|
||||
<form action="/admin/delete-secret/<%= encodeURIComponent(secret.key) %>" method="POST" class="form-inline">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<button type="submit" class="delete">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% } else { %>
|
||||
<p>No secrets stored yet.</p>
|
||||
<% } %>
|
||||
</div>
|
||||
<%# Password param and client-side script for it are no longer needed %>
|
||||
</body>
|
||||
</html>
|
||||
140
views/clients.ejs
Normal file
140
views/clients.ejs
Normal file
@ -0,0 +1,140 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Admin - Client Management</title>
|
||||
<link rel="stylesheet" href="/css/admin_styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Client Application Management</h1>
|
||||
<div class="nav-links">
|
||||
<a href="/admin">Manage Secrets</a>
|
||||
<a href="/admin/clients">Manage Clients</a>
|
||||
<a href="/admin/logout" class="logout-link">Logout</a>
|
||||
</div>
|
||||
|
||||
<% if (message && message.text) { %>
|
||||
<div class="alert <%= message.type === 'success' ? 'alert-success' : 'alert-error' %>">
|
||||
<%= message.text %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if (!managingClientGroups) { %>
|
||||
<div class="settings-section">
|
||||
<h3>WebSocket Settings (Debug)</h3>
|
||||
<form action="/admin/settings/toggle-auto-approve-ws" method="POST">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<label for="autoApproveWsToggle" class="form-group">
|
||||
<input type="checkbox" id="autoApproveWsToggle" name="autoApproveWs" <%= autoApproveWsEnabled ? 'checked' : '' %>>
|
||||
Automatically Approve New WebSocket Registrations
|
||||
</label>
|
||||
<button type="submit" class="btn ml-2">Update Setting</button>
|
||||
</form>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if (managingClientGroups) { %>
|
||||
<h2>Manage Associated Groups for Client: <span class="mono"><%= managingClientGroups.client.name %> (<%= managingClientGroups.client.id %>)</span></h2>
|
||||
<form action="/admin/clients/<%= managingClientGroups.client.id %>/groups/update" method="POST">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<div class="form-group checkbox-list">
|
||||
<label>Available Groups (select to associate):</label>
|
||||
<% if (managingClientGroups.allGroups && managingClientGroups.allGroups.length > 0) { %>
|
||||
<% managingClientGroups.allGroups.forEach(group => { %>
|
||||
<div>
|
||||
<input type="checkbox"
|
||||
id="group_<%= group.id %>"
|
||||
name="associatedGroupIds"
|
||||
value="<%= group.id %>"
|
||||
<%= managingClientGroups.client.associatedGroupIds.includes(group.id) ? 'checked' : '' %>>
|
||||
<label for="group_<%= group.id %>"><%= group.name %> (ID: <%= group.id %>)</label>
|
||||
</div>
|
||||
<% }); %>
|
||||
<% } else { %>
|
||||
<p>No groups available to associate. Create groups in the main admin panel first.</p>
|
||||
<% } %>
|
||||
</div>
|
||||
<button type="submit" class="btn">Update Associated Groups</button>
|
||||
<a href="/admin/clients" class="btn btn-secondary">Back to Client List</a>
|
||||
</form>
|
||||
<% } else { %>
|
||||
<h2>Pending Client Registrations</h2>
|
||||
<% if (pendingClients && pendingClients.length > 0) { %>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Client ID / Temp ID</th>
|
||||
<th>Requested Secrets (Legacy)</th>
|
||||
<th>Date Registered</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% pendingClients.forEach(client => { %>
|
||||
<tr>
|
||||
<td><%= client.name %></td>
|
||||
<td class="mono"><%= client.id %></td>
|
||||
<td><%= client.requestedSecretKeys && client.requestedSecretKeys.length > 0 ? client.requestedSecretKeys.join(', ') : 'None' %></td>
|
||||
<td><%= new Date(client.dateCreated).toLocaleString() %></td>
|
||||
<td class="actions">
|
||||
<form action="/admin/clients/approve/<%= client.id %>" method="POST" class="form-inline">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<button type="submit" class="approve">Approve</button>
|
||||
</form>
|
||||
<form action="/admin/clients/reject/<%= client.id %>" method="POST" class="form-inline">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<button type="submit" class="reject">Reject</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% } else { %>
|
||||
<p>No pending client registrations.</p>
|
||||
<% } %>
|
||||
|
||||
<h2>Approved Clients</h2>
|
||||
<% if (approvedClients && approvedClients.length > 0) { %>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Client ID</th>
|
||||
<th>Associated Groups</th>
|
||||
<th>Date Approved/Updated</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% approvedClients.forEach(client => { %>
|
||||
<tr>
|
||||
<td><%= client.name %></td>
|
||||
<td class="mono"><%= client.id %></td>
|
||||
<td><%= client.associatedGroupNames %></td> <%# Now using associatedGroupNames from server %>
|
||||
<td><%= new Date(client.dateUpdated).toLocaleString() %></td>
|
||||
<td class="actions">
|
||||
<a href="/admin/clients/<%= client.id %>/groups">Manage Groups</a>
|
||||
<form action="/admin/clients/revoke/<%= client.id %>" method="POST" class="form-inline">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<button type="submit" class="delete">Revoke</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% } else { %>
|
||||
<p>No approved clients.</p>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<%# The script for auto-approve toggle has been removed as it's now a form submission %>
|
||||
<%# The csrfToken is still available globally in the template if other scripts need it, %>
|
||||
<%# passed directly from the route handler. %>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
97
views/group_secrets.ejs
Normal file
97
views/group_secrets.ejs
Normal file
@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Admin - Manage Secrets in Group: <%= group.name %></title>
|
||||
<link rel="stylesheet" href="/css/admin_styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="nav-links">
|
||||
<a href="/admin">Back to Main Admin</a>
|
||||
<a href="/admin/clients">Manage Clients</a>
|
||||
<a href="/admin/logout" class="logout-link">Logout</a>
|
||||
</div>
|
||||
|
||||
<h1>Manage Secrets in Group: <em><%= group.name %></em> (ID: <%= group.id %>)</h1>
|
||||
|
||||
<% if (message && message.text) { %>
|
||||
<div class="alert <%= message.type === 'success' ? 'alert-success' : 'alert-error' %>">
|
||||
<%= message.text %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<%# Placeholder for "Add New Secret to This Group" form - Step 5c %>
|
||||
<h3>Add New Secret to Group "<%= group.name %>"</h3>
|
||||
<form action="/admin/groups/<%= group.id %>/secrets/add" method="POST" class="mb-3">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<div class="form-group">
|
||||
<label for="secretKey">Secret Key:</label>
|
||||
<input type="text" id="secretKey" name="secretKey" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="secretValue">Secret Value (JSON format recommended):</label>
|
||||
<textarea id="secretValue" name="secretValue" required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn">Add Secret to Group</button>
|
||||
</form>
|
||||
<hr>
|
||||
|
||||
<% if (typeof editingSecretKey !== 'undefined' && editingSecretKey && typeof secretToEdit !== 'undefined') { %>
|
||||
<h3>Edit Secret Value: <em><%= editingSecretKey %></em> in Group <em><%= group.name %></em></h3>
|
||||
<form action="/admin/groups/<%= group.id %>/secrets/<%= encodeURIComponent(editingSecretKey) %>/update" method="POST" class="mb-3">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<div class="form-group">
|
||||
<label for="editSecretKeyDisplay">Secret Key (read-only):</label>
|
||||
<input type="text" id="editSecretKeyDisplay" name="secretKeyDisplay" value="<%= editingSecretKey %>" readonly class="form-control-plaintext">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="editSecretValue">New Value (JSON format recommended):</label>
|
||||
<textarea id="editSecretValue" name="secretValue" required><%= typeof secretToEdit === 'string' ? secretToEdit : JSON.stringify(secretToEdit, null, 2) %></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn">Update Secret Value</button>
|
||||
<a href="/admin/groups/<%= group.id %>/secrets" class="btn btn-secondary ml-2">Cancel Edit</a>
|
||||
</form>
|
||||
<hr>
|
||||
<% } %>
|
||||
|
||||
|
||||
<h2>Secrets in this Group</h2>
|
||||
<% if (secretsInGroup && secretsInGroup.length > 0) { %>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value (Preview)</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% secretsInGroup.forEach(secret => { %>
|
||||
<tr>
|
||||
<td><%= secret.key %></td>
|
||||
<td>
|
||||
<%
|
||||
let preview = typeof secret.value === 'string' ? secret.value : JSON.stringify(secret.value);
|
||||
if (preview.length > 50) preview = preview.substring(0, 50) + '...';
|
||||
%>
|
||||
<%= preview %>
|
||||
</td>
|
||||
<td class="actions">
|
||||
<a href="/admin/groups/<%= group.id %>/secrets/<%= encodeURIComponent(secret.key) %>/edit">Edit Value</a>
|
||||
<form action="/admin/groups/<%= group.id %>/secrets/<%= encodeURIComponent(secret.key) %>/delete" method="POST" class="form-inline" onsubmit="return confirm('Are you sure you want to delete the secret \"<%= secret.key %>\"? This action cannot be undone.');">
|
||||
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
|
||||
<button type="submit" class="delete">Delete Secret</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% } else { %>
|
||||
<p>No secrets currently in this group. You can add one above.</p>
|
||||
<% } %>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user