object_store_users.templ 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. package app
  2. import (
  3. "fmt"
  4. "github.com/seaweedfs/seaweedfs/weed/admin/dash"
  5. )
  6. templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
  7. <div class="container-fluid">
  8. <!-- Page Header -->
  9. <div class="d-sm-flex align-items-center justify-content-between mb-4">
  10. <div>
  11. <h1 class="h3 mb-0 text-gray-800">
  12. <i class="fas fa-users me-2"></i>Object Store Users
  13. </h1>
  14. <p class="mb-0 text-muted">Manage S3 API users and their access credentials</p>
  15. </div>
  16. <div class="d-flex gap-2">
  17. <button type="button" class="btn btn-primary"
  18. data-bs-toggle="modal"
  19. data-bs-target="#createUserModal">
  20. <i class="fas fa-plus me-1"></i>Create User
  21. </button>
  22. </div>
  23. </div>
  24. <!-- Summary Cards -->
  25. <div class="row mb-4">
  26. <div class="col-xl-3 col-md-6 mb-4">
  27. <div class="card border-left-primary shadow h-100 py-2">
  28. <div class="card-body">
  29. <div class="row no-gutters align-items-center">
  30. <div class="col mr-2">
  31. <div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
  32. Total Users
  33. </div>
  34. <div class="h5 mb-0 font-weight-bold text-gray-800">
  35. {fmt.Sprintf("%d", data.TotalUsers)}
  36. </div>
  37. </div>
  38. <div class="col-auto">
  39. <i class="fas fa-users fa-2x text-gray-300"></i>
  40. </div>
  41. </div>
  42. </div>
  43. </div>
  44. </div>
  45. <div class="col-xl-3 col-md-6 mb-4">
  46. <div class="card border-left-success shadow h-100 py-2">
  47. <div class="card-body">
  48. <div class="row no-gutters align-items-center">
  49. <div class="col mr-2">
  50. <div class="text-xs font-weight-bold text-success text-uppercase mb-1">
  51. Total Users
  52. </div>
  53. <div class="h5 mb-0 font-weight-bold text-gray-800">
  54. {fmt.Sprintf("%d", len(data.Users))}
  55. </div>
  56. </div>
  57. <div class="col-auto">
  58. <i class="fas fa-user-check fa-2x text-gray-300"></i>
  59. </div>
  60. </div>
  61. </div>
  62. </div>
  63. </div>
  64. <div class="col-xl-3 col-md-6 mb-4">
  65. <div class="card border-left-info shadow h-100 py-2">
  66. <div class="card-body">
  67. <div class="row no-gutters align-items-center">
  68. <div class="col mr-2">
  69. <div class="text-xs font-weight-bold text-info text-uppercase mb-1">
  70. Last Updated
  71. </div>
  72. <div class="h6 mb-0 font-weight-bold text-gray-800">
  73. {data.LastUpdated.Format("15:04")}
  74. </div>
  75. </div>
  76. <div class="col-auto">
  77. <i class="fas fa-clock fa-2x text-gray-300"></i>
  78. </div>
  79. </div>
  80. </div>
  81. </div>
  82. </div>
  83. </div>
  84. <!-- Users Table -->
  85. <div class="row">
  86. <div class="col-12">
  87. <div class="card shadow mb-4">
  88. <div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
  89. <h6 class="m-0 font-weight-bold text-primary">
  90. <i class="fas fa-users me-2"></i>Object Store Users
  91. </h6>
  92. <div class="dropdown no-arrow">
  93. <a class="dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
  94. <i class="fas fa-ellipsis-v fa-sm fa-fw text-gray-400"></i>
  95. </a>
  96. <div class="dropdown-menu dropdown-menu-right shadow animated--fade-in">
  97. <div class="dropdown-header">Actions:</div>
  98. <a class="dropdown-item" href="#" onclick="exportUsers()">
  99. <i class="fas fa-download me-2"></i>Export List
  100. </a>
  101. </div>
  102. </div>
  103. </div>
  104. <div class="card-body">
  105. <div class="table-responsive">
  106. <table class="table table-hover" width="100%" cellspacing="0" id="usersTable">
  107. <thead>
  108. <tr>
  109. <th>Username</th>
  110. <th>Email</th>
  111. <th>Access Key</th>
  112. <th>Actions</th>
  113. </tr>
  114. </thead>
  115. <tbody>
  116. for _, user := range data.Users {
  117. <tr>
  118. <td>
  119. <div class="d-flex align-items-center">
  120. <i class="fas fa-user me-2 text-muted"></i>
  121. <strong>{user.Username}</strong>
  122. </div>
  123. </td>
  124. <td>{user.Email}</td>
  125. <td>
  126. <code class="text-muted">{user.AccessKey}</code>
  127. </td>
  128. <td>
  129. <div class="btn-group btn-group-sm" role="group">
  130. <button type="button" class="btn btn-outline-info"
  131. data-action="show-user-details" data-username={ user.Username }>
  132. <i class="fas fa-info-circle"></i>
  133. </button>
  134. <button type="button" class="btn btn-outline-primary"
  135. data-action="edit-user" data-username={ user.Username }>
  136. <i class="fas fa-edit"></i>
  137. </button>
  138. <button type="button" class="btn btn-outline-secondary"
  139. data-action="manage-access-keys" data-username={ user.Username }>
  140. <i class="fas fa-key"></i>
  141. </button>
  142. <button type="button" class="btn btn-outline-danger"
  143. data-action="delete-user" data-username={ user.Username }>
  144. <i class="fas fa-trash"></i>
  145. </button>
  146. </div>
  147. </td>
  148. </tr>
  149. }
  150. if len(data.Users) == 0 {
  151. <tr>
  152. <td colspan="4" class="text-center text-muted py-4">
  153. <i class="fas fa-users fa-3x mb-3 text-muted"></i>
  154. <div>
  155. <h5>No users found</h5>
  156. <p>Create your first object store user to get started.</p>
  157. </div>
  158. </td>
  159. </tr>
  160. }
  161. </tbody>
  162. </table>
  163. </div>
  164. </div>
  165. </div>
  166. </div>
  167. </div>
  168. <!-- Last Updated -->
  169. <div class="row">
  170. <div class="col-12">
  171. <small class="text-muted">
  172. <i class="fas fa-clock me-1"></i>
  173. Last updated: {data.LastUpdated.Format("2006-01-02 15:04:05")}
  174. </small>
  175. </div>
  176. </div>
  177. </div>
  178. <!-- Create User Modal -->
  179. <div class="modal fade" id="createUserModal" tabindex="-1" role="dialog">
  180. <div class="modal-dialog" role="document">
  181. <div class="modal-content">
  182. <div class="modal-header">
  183. <h5 class="modal-title">
  184. <i class="fas fa-user-plus me-2"></i>Create New User
  185. </h5>
  186. <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
  187. </div>
  188. <div class="modal-body">
  189. <form id="createUserForm">
  190. <div class="mb-3">
  191. <label for="username" class="form-label">Username *</label>
  192. <input type="text" class="form-control" id="username" name="username" required>
  193. </div>
  194. <div class="mb-3">
  195. <label for="email" class="form-label">Email</label>
  196. <input type="email" class="form-control" id="email" name="email">
  197. </div>
  198. <div class="mb-3">
  199. <label for="actions" class="form-label">Permissions</label>
  200. <select multiple class="form-control" id="actions" name="actions" size="10">
  201. <option value="Admin">Admin (Full Access)</option>
  202. <option value="Read">Read</option>
  203. <option value="Write">Write</option>
  204. <option value="List">List</option>
  205. <option value="Tagging">Tagging</option>
  206. <optgroup label="Object Lock Permissions">
  207. <option value="BypassGovernanceRetention">Bypass Governance Retention</option>
  208. <option value="GetObjectRetention">Get Object Retention</option>
  209. <option value="PutObjectRetention">Put Object Retention</option>
  210. <option value="GetObjectLegalHold">Get Object Legal Hold</option>
  211. <option value="PutObjectLegalHold">Put Object Legal Hold</option>
  212. <option value="GetBucketObjectLockConfiguration">Get Bucket Object Lock Configuration</option>
  213. <option value="PutBucketObjectLockConfiguration">Put Bucket Object Lock Configuration</option>
  214. </optgroup>
  215. </select>
  216. <small class="form-text text-muted">Hold Ctrl/Cmd to select multiple permissions</small>
  217. </div>
  218. <div class="mb-3 form-check">
  219. <input type="checkbox" class="form-check-input" id="generateKey" name="generateKey" checked>
  220. <label class="form-check-label" for="generateKey">
  221. Generate access key automatically
  222. </label>
  223. </div>
  224. </form>
  225. </div>
  226. <div class="modal-footer">
  227. <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
  228. <button type="button" class="btn btn-primary" onclick="handleCreateUser()">Create User</button>
  229. </div>
  230. </div>
  231. </div>
  232. </div>
  233. <!-- Edit User Modal -->
  234. <div class="modal fade" id="editUserModal" tabindex="-1" role="dialog">
  235. <div class="modal-dialog" role="document">
  236. <div class="modal-content">
  237. <div class="modal-header">
  238. <h5 class="modal-title">
  239. <i class="fas fa-user-edit me-2"></i>Edit User
  240. </h5>
  241. <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
  242. </div>
  243. <div class="modal-body">
  244. <form id="editUserForm">
  245. <input type="hidden" id="editUsername" name="username">
  246. <div class="mb-3">
  247. <label for="editEmail" class="form-label">Email</label>
  248. <input type="email" class="form-control" id="editEmail" name="email">
  249. </div>
  250. <div class="mb-3">
  251. <label for="editActions" class="form-label">Permissions</label>
  252. <select multiple class="form-control" id="editActions" name="actions" size="10">
  253. <option value="Admin">Admin (Full Access)</option>
  254. <option value="Read">Read</option>
  255. <option value="Write">Write</option>
  256. <option value="List">List</option>
  257. <option value="Tagging">Tagging</option>
  258. <optgroup label="Object Lock Permissions">
  259. <option value="BypassGovernanceRetention">Bypass Governance Retention</option>
  260. <option value="GetObjectRetention">Get Object Retention</option>
  261. <option value="PutObjectRetention">Put Object Retention</option>
  262. <option value="GetObjectLegalHold">Get Object Legal Hold</option>
  263. <option value="PutObjectLegalHold">Put Object Legal Hold</option>
  264. <option value="GetBucketObjectLockConfiguration">Get Bucket Object Lock Configuration</option>
  265. <option value="PutBucketObjectLockConfiguration">Put Bucket Object Lock Configuration</option>
  266. </optgroup>
  267. </select>
  268. </div>
  269. </form>
  270. </div>
  271. <div class="modal-footer">
  272. <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
  273. <button type="button" class="btn btn-primary" onclick="handleUpdateUser()">Update User</button>
  274. </div>
  275. </div>
  276. </div>
  277. </div>
  278. <!-- User Details Modal -->
  279. <div class="modal fade" id="userDetailsModal" tabindex="-1" role="dialog">
  280. <div class="modal-dialog modal-lg" role="document">
  281. <div class="modal-content">
  282. <div class="modal-header">
  283. <h5 class="modal-title">
  284. <i class="fas fa-user me-2"></i>User Details
  285. </h5>
  286. <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
  287. </div>
  288. <div class="modal-body" id="userDetailsContent">
  289. <!-- Content will be loaded dynamically -->
  290. </div>
  291. <div class="modal-footer">
  292. <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
  293. </div>
  294. </div>
  295. </div>
  296. </div>
  297. <!-- Access Keys Management Modal -->
  298. <div class="modal fade" id="accessKeysModal" tabindex="-1" role="dialog">
  299. <div class="modal-dialog modal-lg" role="document">
  300. <div class="modal-content">
  301. <div class="modal-header">
  302. <h5 class="modal-title">
  303. <i class="fas fa-key me-2"></i>Manage Access Keys
  304. </h5>
  305. <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
  306. </div>
  307. <div class="modal-body">
  308. <div class="d-flex justify-content-between align-items-center mb-3">
  309. <h6>Access Keys for <span id="accessKeysUsername"></span></h6>
  310. <button type="button" class="btn btn-primary btn-sm" onclick="createAccessKey()">
  311. <i class="fas fa-plus me-1"></i>Create New Key
  312. </button>
  313. </div>
  314. <div id="accessKeysContent">
  315. <!-- Content will be loaded dynamically -->
  316. </div>
  317. </div>
  318. <div class="modal-footer">
  319. <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
  320. </div>
  321. </div>
  322. </div>
  323. </div>
  324. <!-- JavaScript for user management -->
  325. <script>
  326. document.addEventListener('DOMContentLoaded', function() {
  327. // Event delegation for user action buttons
  328. document.addEventListener('click', function(e) {
  329. const button = e.target.closest('[data-action]');
  330. if (!button) return;
  331. const action = button.getAttribute('data-action');
  332. const username = button.getAttribute('data-username');
  333. switch (action) {
  334. case 'show-user-details':
  335. showUserDetails(username);
  336. break;
  337. case 'edit-user':
  338. editUser(username);
  339. break;
  340. case 'manage-access-keys':
  341. manageAccessKeys(username);
  342. break;
  343. case 'delete-user':
  344. deleteUser(username);
  345. break;
  346. }
  347. });
  348. });
  349. // Show user details modal
  350. async function showUserDetails(username) {
  351. try {
  352. const response = await fetch(`/api/users/${username}`);
  353. if (response.ok) {
  354. const user = await response.json();
  355. document.getElementById('userDetailsContent').innerHTML = createUserDetailsContent(user);
  356. const modal = new bootstrap.Modal(document.getElementById('userDetailsModal'));
  357. modal.show();
  358. } else {
  359. showErrorMessage('Failed to load user details');
  360. }
  361. } catch (error) {
  362. console.error('Error loading user details:', error);
  363. showErrorMessage('Failed to load user details');
  364. }
  365. }
  366. // Edit user function
  367. async function editUser(username) {
  368. try {
  369. const response = await fetch(`/api/users/${username}`);
  370. if (response.ok) {
  371. const user = await response.json();
  372. // Populate edit form
  373. document.getElementById('editUsername').value = username;
  374. document.getElementById('editEmail').value = user.email || '';
  375. // Set selected actions
  376. const actionsSelect = document.getElementById('editActions');
  377. Array.from(actionsSelect.options).forEach(option => {
  378. option.selected = user.actions && user.actions.includes(option.value);
  379. });
  380. // Show modal
  381. const modal = new bootstrap.Modal(document.getElementById('editUserModal'));
  382. modal.show();
  383. } else {
  384. showErrorMessage('Failed to load user details');
  385. }
  386. } catch (error) {
  387. console.error('Error loading user:', error);
  388. showErrorMessage('Failed to load user details');
  389. }
  390. }
  391. // Manage access keys function
  392. async function manageAccessKeys(username) {
  393. try {
  394. const response = await fetch(`/api/users/${username}`);
  395. if (response.ok) {
  396. const user = await response.json();
  397. document.getElementById('accessKeysUsername').textContent = username;
  398. document.getElementById('accessKeysContent').innerHTML = createAccessKeysContent(user);
  399. const modal = new bootstrap.Modal(document.getElementById('accessKeysModal'));
  400. modal.show();
  401. } else {
  402. showErrorMessage('Failed to load access keys');
  403. }
  404. } catch (error) {
  405. console.error('Error loading access keys:', error);
  406. showErrorMessage('Failed to load access keys');
  407. }
  408. }
  409. // Delete user function
  410. async function deleteUser(username) {
  411. if (confirm(`Are you sure you want to delete user "${username}"? This action cannot be undone.`)) {
  412. try {
  413. const response = await fetch(`/api/users/${username}`, {
  414. method: 'DELETE'
  415. });
  416. if (response.ok) {
  417. showSuccessMessage('User deleted successfully');
  418. setTimeout(() => window.location.reload(), 1000);
  419. } else {
  420. const error = await response.json();
  421. showErrorMessage('Failed to delete user: ' + (error.error || 'Unknown error'));
  422. }
  423. } catch (error) {
  424. console.error('Error deleting user:', error);
  425. showErrorMessage('Failed to delete user: ' + error.message);
  426. }
  427. }
  428. }
  429. // Handle create user form submission
  430. async function handleCreateUser() {
  431. const form = document.getElementById('createUserForm');
  432. const formData = new FormData(form);
  433. const userData = {
  434. username: formData.get('username'),
  435. email: formData.get('email'),
  436. actions: Array.from(document.getElementById('actions').selectedOptions).map(option => option.value),
  437. generate_key: document.getElementById('generateKey').checked
  438. };
  439. try {
  440. const response = await fetch('/api/users', {
  441. method: 'POST',
  442. headers: {
  443. 'Content-Type': 'application/json',
  444. },
  445. body: JSON.stringify(userData)
  446. });
  447. if (response.ok) {
  448. const result = await response.json();
  449. showSuccessMessage('User created successfully');
  450. // Show the created access key if generated
  451. if (result.user && result.user.access_key) {
  452. showNewAccessKeyModal(result.user);
  453. }
  454. // Close modal and refresh page
  455. const modal = bootstrap.Modal.getInstance(document.getElementById('createUserModal'));
  456. modal.hide();
  457. form.reset();
  458. setTimeout(() => window.location.reload(), 1000);
  459. } else {
  460. const error = await response.json();
  461. showErrorMessage('Failed to create user: ' + (error.error || 'Unknown error'));
  462. }
  463. } catch (error) {
  464. console.error('Error creating user:', error);
  465. showErrorMessage('Failed to create user: ' + error.message);
  466. }
  467. }
  468. // Handle update user form submission
  469. async function handleUpdateUser() {
  470. const username = document.getElementById('editUsername').value;
  471. const formData = new FormData(document.getElementById('editUserForm'));
  472. const userData = {
  473. email: formData.get('email'),
  474. actions: Array.from(document.getElementById('editActions').selectedOptions).map(option => option.value)
  475. };
  476. try {
  477. const response = await fetch(`/api/users/${username}`, {
  478. method: 'PUT',
  479. headers: {
  480. 'Content-Type': 'application/json',
  481. },
  482. body: JSON.stringify(userData)
  483. });
  484. if (response.ok) {
  485. showSuccessMessage('User updated successfully');
  486. // Close modal and refresh page
  487. const modal = bootstrap.Modal.getInstance(document.getElementById('editUserModal'));
  488. modal.hide();
  489. setTimeout(() => window.location.reload(), 1000);
  490. } else {
  491. const error = await response.json();
  492. showErrorMessage('Failed to update user: ' + (error.error || 'Unknown error'));
  493. }
  494. } catch (error) {
  495. console.error('Error updating user:', error);
  496. showErrorMessage('Failed to update user: ' + error.message);
  497. }
  498. }
  499. // Create user details content
  500. function createUserDetailsContent(user) {
  501. var detailsHtml = '<div class="row">';
  502. detailsHtml += '<div class="col-md-6">';
  503. detailsHtml += '<h6 class="text-muted">Basic Information</h6>';
  504. detailsHtml += '<table class="table table-sm">';
  505. detailsHtml += '<tr><td><strong>Username:</strong></td><td>' + escapeHtml(user.username) + '</td></tr>';
  506. detailsHtml += '<tr><td><strong>Email:</strong></td><td>' + escapeHtml(user.email || 'Not set') + '</td></tr>';
  507. detailsHtml += '</table>';
  508. detailsHtml += '</div>';
  509. detailsHtml += '<div class="col-md-6">';
  510. detailsHtml += '<h6 class="text-muted">Permissions</h6>';
  511. detailsHtml += '<div class="mb-3">';
  512. if (user.actions && user.actions.length > 0) {
  513. detailsHtml += user.actions.map(function(action) {
  514. return '<span class="badge bg-info me-1">' + action + '</span>';
  515. }).join('');
  516. } else {
  517. detailsHtml += '<span class="text-muted">No permissions assigned</span>';
  518. }
  519. detailsHtml += '</div>';
  520. detailsHtml += '<h6 class="text-muted">Access Keys</h6>';
  521. if (user.access_keys && user.access_keys.length > 0) {
  522. detailsHtml += '<div class="mb-2">';
  523. user.access_keys.forEach(function(key) {
  524. detailsHtml += '<div><code class="text-muted">' + key.access_key + '</code></div>';
  525. });
  526. detailsHtml += '</div>';
  527. } else {
  528. detailsHtml += '<p class="text-muted">No access keys</p>';
  529. }
  530. detailsHtml += '</div>';
  531. detailsHtml += '</div>';
  532. return detailsHtml;
  533. }
  534. // Create access keys content
  535. function createAccessKeysContent(user) {
  536. if (!user.access_keys || user.access_keys.length === 0) {
  537. return '<p class="text-muted">No access keys available</p>';
  538. }
  539. var keysHtml = '<div class="table-responsive">';
  540. keysHtml += '<table class="table table-sm">';
  541. keysHtml += '<thead><tr><th>Access Key</th><th>Status</th><th>Actions</th></tr></thead>';
  542. keysHtml += '<tbody>';
  543. user.access_keys.forEach(function(key) {
  544. keysHtml += '<tr>';
  545. keysHtml += '<td><code>' + key.access_key + '</code></td>';
  546. keysHtml += '<td><span class="badge bg-success">Active</span></td>';
  547. keysHtml += '<td>';
  548. keysHtml += '<button class="btn btn-outline-danger btn-sm" onclick="deleteAccessKey(\'' + user.username + '\', \'' + key.access_key + '\')">';
  549. keysHtml += '<i class="fas fa-trash"></i> Delete';
  550. keysHtml += '</button>';
  551. keysHtml += '</td>';
  552. keysHtml += '</tr>';
  553. });
  554. keysHtml += '</tbody>';
  555. keysHtml += '</table>';
  556. keysHtml += '</div>';
  557. return keysHtml;
  558. }
  559. // Create new access key
  560. async function createAccessKey() {
  561. const username = document.getElementById('accessKeysUsername').textContent;
  562. try {
  563. const response = await fetch(`/api/users/${username}/access-keys`, {
  564. method: 'POST',
  565. headers: {
  566. 'Content-Type': 'application/json',
  567. },
  568. body: JSON.stringify({})
  569. });
  570. if (response.ok) {
  571. const result = await response.json();
  572. showSuccessMessage('Access key created successfully');
  573. // Refresh access keys display
  574. const userResponse = await fetch(`/api/users/${username}`);
  575. if (userResponse.ok) {
  576. const user = await userResponse.json();
  577. document.getElementById('accessKeysContent').innerHTML = createAccessKeysContent(user);
  578. }
  579. } else {
  580. const error = await response.json();
  581. showErrorMessage('Failed to create access key: ' + (error.error || 'Unknown error'));
  582. }
  583. } catch (error) {
  584. console.error('Error creating access key:', error);
  585. showErrorMessage('Failed to create access key: ' + error.message);
  586. }
  587. }
  588. // Delete access key
  589. async function deleteAccessKey(username, accessKey) {
  590. if (confirm('Are you sure you want to delete this access key?')) {
  591. try {
  592. const response = await fetch(`/api/users/${username}/access-keys/${accessKey}`, {
  593. method: 'DELETE'
  594. });
  595. if (response.ok) {
  596. showSuccessMessage('Access key deleted successfully');
  597. // Refresh access keys display
  598. const userResponse = await fetch(`/api/users/${username}`);
  599. if (userResponse.ok) {
  600. const user = await userResponse.json();
  601. document.getElementById('accessKeysContent').innerHTML = createAccessKeysContent(user);
  602. }
  603. } else {
  604. const error = await response.json();
  605. showErrorMessage('Failed to delete access key: ' + (error.error || 'Unknown error'));
  606. }
  607. } catch (error) {
  608. console.error('Error deleting access key:', error);
  609. showErrorMessage('Failed to delete access key: ' + error.message);
  610. }
  611. }
  612. }
  613. // Show new access key modal (when user is created with generated key)
  614. function showNewAccessKeyModal(user) {
  615. // Create a simple alert for now - could be enhanced with a dedicated modal
  616. var message = 'New user created!\n\n';
  617. message += 'Username: ' + user.username + '\n';
  618. message += 'Access Key: ' + user.access_key + '\n';
  619. message += 'Secret Key: ' + user.secret_key + '\n\n';
  620. message += 'Please save these credentials securely.';
  621. alert(message);
  622. }
  623. // Utility functions
  624. function showSuccessMessage(message) {
  625. // Simple implementation - could be enhanced with toast notifications
  626. alert('Success: ' + message);
  627. }
  628. function showErrorMessage(message) {
  629. // Simple implementation - could be enhanced with toast notifications
  630. alert('Error: ' + message);
  631. }
  632. function escapeHtml(text) {
  633. if (!text) return '';
  634. const div = document.createElement('div');
  635. div.textContent = text;
  636. return div.innerHTML;
  637. }
  638. </script>
  639. }
  640. // Helper functions for template