@logboard/cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/.env.example +37 -0
  2. package/README.md +200 -0
  3. package/bin/logboard +536 -0
  4. package/client/logger.js +309 -0
  5. package/config/index.js +142 -0
  6. package/config.js +2 -0
  7. package/controllers/AnalyticsController.js +46 -0
  8. package/controllers/ApiAnalyticsController.js +129 -0
  9. package/controllers/ApiKeyController.js +58 -0
  10. package/controllers/AuthController.js +131 -0
  11. package/controllers/HealthController.js +56 -0
  12. package/controllers/LogController.js +197 -0
  13. package/controllers/OrgController.js +152 -0
  14. package/controllers/RoleConfigController.js +20 -0
  15. package/controllers/SettingsController.js +39 -0
  16. package/controllers/StreamController.js +55 -0
  17. package/controllers/UiController.js +789 -0
  18. package/controllers/UserController.js +79 -0
  19. package/lib/batchWriter.js +57 -0
  20. package/lib/cleanup.js +67 -0
  21. package/lib/ejs.js +103 -0
  22. package/lib/emitter.js +5 -0
  23. package/lib/healthMonitor.js +245 -0
  24. package/lib/logger.js +21 -0
  25. package/lib/streams.js +32 -0
  26. package/lib/theme.js +77 -0
  27. package/lib/userStore.js +13 -0
  28. package/lib/utils.js +44 -0
  29. package/middleware/apiKey.js +82 -0
  30. package/middleware/auth.js +55 -0
  31. package/middleware/ipWhitelist.js +59 -0
  32. package/middleware/org.js +85 -0
  33. package/middleware/pageAccess.js +20 -0
  34. package/middleware/rateLimit.js +29 -0
  35. package/middleware/roles.js +11 -0
  36. package/package.json +77 -0
  37. package/routes/alerts.js +18 -0
  38. package/routes/analytics.js +26 -0
  39. package/routes/api-analytics.js +30 -0
  40. package/routes/api-keys.js +12 -0
  41. package/routes/archive.js +91 -0
  42. package/routes/audit.js +50 -0
  43. package/routes/auth.js +22 -0
  44. package/routes/bookmarks.js +13 -0
  45. package/routes/health.js +11 -0
  46. package/routes/logs.js +88 -0
  47. package/routes/metrics.js +66 -0
  48. package/routes/notifications.js +14 -0
  49. package/routes/orgs.js +98 -0
  50. package/routes/registration.js +202 -0
  51. package/routes/role-config.js +97 -0
  52. package/routes/saved-searches.js +12 -0
  53. package/routes/server.js +151 -0
  54. package/routes/settings.js +28 -0
  55. package/routes/status.js +21 -0
  56. package/routes/stream.js +11 -0
  57. package/routes/super.js +129 -0
  58. package/routes/ui.js +120 -0
  59. package/routes/users.js +13 -0
  60. package/server.js +172 -0
  61. package/services/AlertRulesService.js +323 -0
  62. package/services/AnalyticsService.js +665 -0
  63. package/services/ApiAnalyticsService.js +471 -0
  64. package/services/ApiKeyService.js +166 -0
  65. package/services/AuditService.js +249 -0
  66. package/services/AuthService.js +234 -0
  67. package/services/BookmarkService.js +49 -0
  68. package/services/GlobalSettingsService.js +44 -0
  69. package/services/LogService.js +1066 -0
  70. package/services/MetricsService.js +116 -0
  71. package/services/NotificationService.js +70 -0
  72. package/services/OrgService.js +217 -0
  73. package/services/ReportService.js +247 -0
  74. package/services/RoleConfigService.js +201 -0
  75. package/services/SavedSearchService.js +63 -0
  76. package/services/SettingsService.js +220 -0
  77. package/services/UserService.js +121 -0
  78. package/setup.js +132 -0
  79. package/views/404.ejs +8 -0
  80. package/views/alerts.ejs +190 -0
  81. package/views/analytics.ejs +209 -0
  82. package/views/api-analytics.ejs +660 -0
  83. package/views/api-keys.ejs +150 -0
  84. package/views/archive.ejs +123 -0
  85. package/views/audit.ejs +314 -0
  86. package/views/bookmarks.ejs +54 -0
  87. package/views/custom-dashboard.ejs +162 -0
  88. package/views/dashboard.ejs +186 -0
  89. package/views/diff.ejs +98 -0
  90. package/views/health.ejs +269 -0
  91. package/views/heatmap.ejs +126 -0
  92. package/views/insights.ejs +334 -0
  93. package/views/invite.ejs +74 -0
  94. package/views/live.ejs +299 -0
  95. package/views/login.ejs +64 -0
  96. package/views/logo.png +0 -0
  97. package/views/logs.ejs +754 -0
  98. package/views/notifications.ejs +58 -0
  99. package/views/partials/head.ejs +282 -0
  100. package/views/partials/sidebar.ejs +168 -0
  101. package/views/register.ejs +100 -0
  102. package/views/roles.ejs +279 -0
  103. package/views/saved-searches.ejs +51 -0
  104. package/views/service-map.ejs +142 -0
  105. package/views/settings.ejs +1159 -0
  106. package/views/sidebar.ejs +129 -0
  107. package/views/status.ejs +100 -0
  108. package/views/super-admin-admins.ejs +58 -0
  109. package/views/super-admin-analytics.ejs +49 -0
  110. package/views/super-admin-orgs.ejs +310 -0
  111. package/views/super-admin-profile.ejs +77 -0
  112. package/views/super-admin-settings.ejs +108 -0
  113. package/views/super-admin-system.ejs +46 -0
  114. package/views/users.ejs +153 -0
@@ -0,0 +1,153 @@
1
+ <%- include('partials/head', { title: 'Users' }) %>
2
+ <div class="app-shell">
3
+ <%- include('partials/sidebar') %>
4
+ <div class="main-area">
5
+ <header class="top-header">
6
+ <div class="page-title">User Management</div>
7
+ <div class="header-actions">
8
+ <button class="btn btn-primary btn-sm" onclick="openAddModal()">
9
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
10
+ Add User
11
+ </button>
12
+ </div>
13
+ </header>
14
+ <div class="page-content">
15
+ <div class="card">
16
+ <div class="table-wrap">
17
+ <table id="users-table">
18
+ <thead><tr><th>User</th><th>Role</th><th>2FA</th><th>Created</th><th>Actions</th></tr></thead>
19
+ <tbody>
20
+ <% users.forEach(function(u){ %>
21
+ <tr id="row-<%=u.username%>">
22
+ <td>
23
+ <div style="display:flex;align-items:center;gap:10px;">
24
+ <div style="width:30px;height:30px;border-radius:50%;background:var(--accent);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:#fff;flex-shrink:0;"><%=u.username[0].toUpperCase()%></div>
25
+ <div><div style="font-weight:600;color:var(--text);font-size:13px;"><%=u.username%></div></div>
26
+ </div>
27
+ </td>
28
+ <td>
29
+ <% if(u.username !== user.username){ %>
30
+ <select class="form-select" style="max-width:120px;padding:4px 8px;" onchange="changeRole('<%=u.username%>',this.value)">
31
+ <% roles.forEach(function(r){ %><option value="<%=r%>" <%=u.role===r?'selected':''%>><%=r%></option><% }) %>
32
+ </select>
33
+ <% }else{ %>
34
+ <span class="badge badge-purple"><%=u.role%> (you)</span>
35
+ <% } %>
36
+ </td>
37
+ <td>
38
+ <% if(u.totpEnabled){ %>
39
+ <div style="display:flex;align-items:center;gap:6px;"><span class="dot dot-green"></span><span style="font-size:12px;color:var(--green)">Enabled</span>
40
+ <% if(u.username!==user.username){ %><button class="btn btn-danger btn-xs" onclick="revoke2fa('<%=u.username%>')">Revoke</button><% } %>
41
+ </div>
42
+ <% }else{ %><span style="font-size:12px;color:var(--text3)">Off</span><% } %>
43
+ </td>
44
+ <td style="color:var(--text3);font-size:11px;"><%=u.createdAt?new Date(u.createdAt).toLocaleDateString():'—'%></td>
45
+ <td>
46
+ <div style="display:flex;gap:6px;">
47
+ <button class="btn btn-secondary btn-xs" onclick="openResetModal('<%=u.username%>')">Reset PW</button>
48
+ <% if(u.username!==user.username){ %>
49
+ <button class="btn btn-danger btn-xs" onclick="deleteUser('<%=u.username%>')">Delete</button>
50
+ <% } %>
51
+ </div>
52
+ </td>
53
+ </tr>
54
+ <% }) %>
55
+ </tbody>
56
+ </table>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </div>
62
+
63
+ <!-- Add User Modal -->
64
+ <div class="modal-overlay" id="add-modal">
65
+ <div class="modal-box">
66
+ <div class="modal-title">Add User</div>
67
+ <div class="modal-sub">New user will be able to log in immediately.</div>
68
+ <div class="form-group"><label class="form-label">Username</label><input type="text" id="new-un" class="form-input" placeholder="johndoe" autocomplete="off"/></div>
69
+ <div class="form-group"><label class="form-label">Password</label><input type="password" id="new-pw" class="form-input" placeholder="Min. 8 characters"/></div>
70
+ <div class="form-group"><label class="form-label">Role</label>
71
+ <select id="new-role" class="form-select">
72
+ <% roles.forEach(function(r){ %><option value="<%=r%>"><%=r%></option><% }) %>
73
+ </select>
74
+ </div>
75
+ <div id="add-err" style="display:none;color:var(--red);font-size:12px;margin-bottom:12px;"></div>
76
+ <div style="display:flex;gap:8px;">
77
+ <button class="btn btn-primary" style="flex:1;" onclick="addUser()">Create User</button>
78
+ <button class="btn btn-secondary" onclick="closeModal('add-modal')">Cancel</button>
79
+ </div>
80
+ </div>
81
+ </div>
82
+
83
+ <!-- Reset Password Modal -->
84
+ <div class="modal-overlay" id="reset-modal">
85
+ <div class="modal-box">
86
+ <div class="modal-title">Reset Password</div>
87
+ <div class="modal-sub">Set a new password for <strong id="reset-target-label"></strong></div>
88
+ <input type="hidden" id="reset-target"/>
89
+ <div class="form-group"><label class="form-label">New Password</label><input type="password" id="reset-pw" class="form-input" placeholder="Min. 8 characters"/></div>
90
+ <div id="reset-err" style="display:none;color:var(--red);font-size:12px;margin-bottom:12px;"></div>
91
+ <div style="display:flex;gap:8px;">
92
+ <button class="btn btn-primary" style="flex:1;" onclick="doResetPw()">Update Password</button>
93
+ <button class="btn btn-secondary" onclick="closeModal('reset-modal')">Cancel</button>
94
+ </div>
95
+ </div>
96
+ </div>
97
+
98
+ <script>
99
+ function openAddModal(){document.getElementById('new-un').value='';document.getElementById('new-pw').value='';document.getElementById('add-err').style.display='none';document.getElementById('add-modal').classList.add('open');}
100
+ function openResetModal(un){document.getElementById('reset-target').value=un;document.getElementById('reset-target-label').textContent=un;document.getElementById('reset-pw').value='';document.getElementById('reset-err').style.display='none';document.getElementById('reset-modal').classList.add('open');}
101
+ function closeModal(id){document.getElementById(id).classList.remove('open');}
102
+ document.querySelectorAll('.modal-overlay').forEach(m=>m.addEventListener('click',e=>{if(e.target===m)m.classList.remove('open');}));
103
+ document.addEventListener('keydown',e=>{if(e.key==='Escape')document.querySelectorAll('.modal-overlay.open').forEach(m=>m.classList.remove('open'));});
104
+
105
+ async function addUser(){
106
+ const un=document.getElementById('new-un').value.trim(),pw=document.getElementById('new-pw').value,role=document.getElementById('new-role').value;
107
+ const errEl=document.getElementById('add-err');errEl.style.display='none';
108
+ try{
109
+ const r=await fetch('/api/users',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:un,password:pw,role})});
110
+ const d=await r.json();
111
+ if(!r.ok){errEl.textContent=d.error;errEl.style.display='';return;}
112
+ toast('User "'+un+'" created','success');closeModal('add-modal');location.reload();
113
+ }catch(e){errEl.textContent='Network error';errEl.style.display='';}
114
+ }
115
+
116
+ async function changeRole(un,role){
117
+ try{
118
+ const r=await fetch('/api/users/'+encodeURIComponent(un)+'/role',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({role})});
119
+ const d=await r.json();
120
+ if(!r.ok){toast(d.error,'error');return;}
121
+ toast('"'+un+'" role updated to '+role,'success');
122
+ }catch{toast('Error updating role','error');}
123
+ }
124
+
125
+ async function doResetPw(){
126
+ const un=document.getElementById('reset-target').value,pw=document.getElementById('reset-pw').value;
127
+ const errEl=document.getElementById('reset-err');errEl.style.display='none';
128
+ try{
129
+ const r=await fetch('/api/users/'+encodeURIComponent(un)+'/reset-password',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify({newPassword:pw})});
130
+ const d=await r.json();
131
+ if(!r.ok){errEl.textContent=d.error;errEl.style.display='';return;}
132
+ toast('Password reset for "'+un+'"','success');closeModal('reset-modal');
133
+ }catch(e){errEl.textContent='Network error';errEl.style.display='';}
134
+ }
135
+
136
+ async function revoke2fa(un){
137
+ if(!confirm('Revoke 2FA for "'+un+'"?'))return;
138
+ const r=await fetch('/api/users/'+encodeURIComponent(un)+'/2fa',{method:'DELETE'});
139
+ const d=await r.json();
140
+ if(!r.ok){toast(d.error,'error');return;}
141
+ toast('2FA revoked for "'+un+'"','success');location.reload();
142
+ }
143
+
144
+ async function deleteUser(un){
145
+ if(!confirm('Delete user "'+un+'"? This cannot be undone.'))return;
146
+ const r=await fetch('/api/users/'+encodeURIComponent(un),{method:'DELETE'});
147
+ const d=await r.json();
148
+ if(!r.ok){toast(d.error,'error');return;}
149
+ toast('User "'+un+'" deleted','success');
150
+ const row=document.getElementById('row-'+un);if(row)row.remove();
151
+ }
152
+ </script>
153
+ </body></html>