@asifkibria/claude-code-toolkit 1.3.0 → 1.4.2
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.
- package/README.md +21 -6
- package/dist/__tests__/alerts.test.d.ts +2 -0
- package/dist/__tests__/alerts.test.d.ts.map +1 -0
- package/dist/__tests__/alerts.test.js +195 -0
- package/dist/__tests__/alerts.test.js.map +1 -0
- package/dist/__tests__/bookmarks.test.d.ts +2 -0
- package/dist/__tests__/bookmarks.test.d.ts.map +1 -0
- package/dist/__tests__/bookmarks.test.js +170 -0
- package/dist/__tests__/bookmarks.test.js.map +1 -0
- package/dist/__tests__/bulk.test.d.ts +2 -0
- package/dist/__tests__/bulk.test.d.ts.map +1 -0
- package/dist/__tests__/bulk.test.js +245 -0
- package/dist/__tests__/bulk.test.js.map +1 -0
- package/dist/__tests__/dashboard.test.js +2 -2
- package/dist/__tests__/dashboard.test.js.map +1 -1
- package/dist/__tests__/git.test.d.ts +2 -0
- package/dist/__tests__/git.test.d.ts.map +1 -0
- package/dist/__tests__/git.test.js +216 -0
- package/dist/__tests__/git.test.js.map +1 -0
- package/dist/__tests__/logs.test.d.ts +2 -0
- package/dist/__tests__/logs.test.d.ts.map +1 -0
- package/dist/__tests__/logs.test.js +196 -0
- package/dist/__tests__/logs.test.js.map +1 -0
- package/dist/__tests__/search.test.d.ts +2 -0
- package/dist/__tests__/search.test.d.ts.map +1 -0
- package/dist/__tests__/search.test.js +155 -0
- package/dist/__tests__/search.test.js.map +1 -0
- package/dist/cli.js +269 -12
- package/dist/cli.js.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/bookmarks.d.ts +69 -0
- package/dist/lib/bookmarks.d.ts.map +1 -0
- package/dist/lib/bookmarks.js +225 -0
- package/dist/lib/bookmarks.js.map +1 -0
- package/dist/lib/bulk.d.ts +78 -0
- package/dist/lib/bulk.d.ts.map +1 -0
- package/dist/lib/bulk.js +257 -0
- package/dist/lib/bulk.js.map +1 -0
- package/dist/lib/dashboard-ui.d.ts.map +1 -1
- package/dist/lib/dashboard-ui.js +425 -19
- package/dist/lib/dashboard-ui.js.map +1 -1
- package/dist/lib/dashboard.d.ts.map +1 -1
- package/dist/lib/dashboard.js +398 -20
- package/dist/lib/dashboard.js.map +1 -1
- package/dist/lib/export.d.ts +41 -0
- package/dist/lib/export.d.ts.map +1 -0
- package/dist/lib/export.js +428 -0
- package/dist/lib/export.js.map +1 -0
- package/dist/lib/git.d.ts.map +1 -1
- package/dist/lib/git.js +2 -1
- package/dist/lib/git.js.map +1 -1
- package/dist/lib/mcp-validator.d.ts.map +1 -1
- package/dist/lib/mcp-validator.js +2 -1
- package/dist/lib/mcp-validator.js.map +1 -1
- package/dist/lib/scanner.d.ts.map +1 -1
- package/dist/lib/scanner.js +2 -12
- package/dist/lib/scanner.js.map +1 -1
- package/dist/lib/security.d.ts.map +1 -1
- package/dist/lib/security.js +12 -1
- package/dist/lib/security.js.map +1 -1
- package/dist/lib/session-recovery.d.ts.map +1 -1
- package/dist/lib/session-recovery.js +25 -2
- package/dist/lib/session-recovery.js.map +1 -1
- package/dist/lib/utils.d.ts +15 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +78 -0
- package/dist/lib/utils.js.map +1 -0
- package/package.json +2 -2
package/dist/lib/dashboard-ui.js
CHANGED
|
@@ -326,10 +326,10 @@ table tbody tr:hover td { background: var(--accent-glow); }
|
|
|
326
326
|
<div class="hstat" style="text-align:center" title="Issues found"><div style="font-size:16px;font-weight:700" id="hsIssues">-</div><div style="font-size:9px;color:var(--text3);text-transform:uppercase">Issues</div></div>
|
|
327
327
|
</div>
|
|
328
328
|
<div class="header-right">
|
|
329
|
-
<div class="search-bar" style="margin:0 16px 0 0;position:relative;display:flex;align-items:center;background:var(--
|
|
330
|
-
<span style="
|
|
331
|
-
<input type="text" id="globalSearch" class="search-input" placeholder="Search
|
|
332
|
-
<button class="btn" style="
|
|
329
|
+
<div class="search-bar" style="margin:0 16px 0 0;position:relative;display:flex;align-items:center;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:4px 8px">
|
|
330
|
+
<span style="color:var(--text3);font-size:14px;margin-right:8px">🔍</span>
|
|
331
|
+
<input type="text" id="globalSearch" class="search-input" placeholder="Search conversations..." style="width:220px;padding:6px 8px;font-size:13px;border:none;background:transparent" onkeyup="if(event.key==='Enter') doGlobalSearch(this.value)" onfocus="this.parentElement.style.borderColor='var(--accent)'" onblur="this.parentElement.style.borderColor='var(--border)'">
|
|
332
|
+
<button class="btn btn-primary" style="padding:6px 14px;font-size:12px;margin-left:4px" onclick="doGlobalSearch(document.getElementById('globalSearch').value)">Go</button>
|
|
333
333
|
</div>
|
|
334
334
|
<div class="auto-refresh">
|
|
335
335
|
<label>Auto-refresh</label>
|
|
@@ -346,7 +346,8 @@ table tbody tr:hover td { background: var(--accent-glow); }
|
|
|
346
346
|
</div>
|
|
347
347
|
<div class="nav" id="nav">
|
|
348
348
|
<div class="nav-item active" data-tab="overview">Overview</div>
|
|
349
|
-
<div class="nav-item" data-tab="search"
|
|
349
|
+
<div class="nav-item" data-tab="search">🔍 Search</div>
|
|
350
|
+
<div class="nav-item" data-tab="bookmarks">⭐ Starred</div>
|
|
350
351
|
<div class="nav-item" data-tab="storage">Storage</div>
|
|
351
352
|
<div class="nav-item" data-tab="sessions">Sessions</div>
|
|
352
353
|
<div class="nav-item" data-tab="security">Security</div>
|
|
@@ -389,6 +390,7 @@ table tbody tr:hover td { background: var(--accent-glow); }
|
|
|
389
390
|
</div>
|
|
390
391
|
<div class="section" id="sec-about"><div class="loading"><div class="spinner"></div><div>Loading...</div></div></div>
|
|
391
392
|
<div class="section" id="sec-search"><div class="loading"><div class="spinner"></div><div>Searching...</div></div></div>
|
|
393
|
+
<div class="section" id="sec-bookmarks"><div class="loading"><div class="spinner"></div><div>Loading bookmarks...</div></div></div>
|
|
392
394
|
</div>
|
|
393
395
|
<div class="footer">
|
|
394
396
|
<div class="footer-inner">
|
|
@@ -733,7 +735,7 @@ function toggleTheme() {
|
|
|
733
735
|
})();
|
|
734
736
|
|
|
735
737
|
let currentTab = 'overview';
|
|
736
|
-
const tabOrder=['overview','search','storage','sessions','security','traces','mcp','logs','config','analytics','backups','context','maintenance','snapshots','about'];
|
|
738
|
+
const tabOrder=['overview','search','bookmarks','storage','sessions','security','traces','mcp','logs','config','analytics','backups','context','maintenance','snapshots','about'];
|
|
737
739
|
$('#nav').addEventListener('click', e => {
|
|
738
740
|
const t = e.target.dataset?.tab; if(!t) return;
|
|
739
741
|
$$('.nav-item').forEach(n=>n.classList.remove('active'));
|
|
@@ -849,6 +851,196 @@ async function loadSearch() {
|
|
|
849
851
|
loadSearchTab();
|
|
850
852
|
}
|
|
851
853
|
|
|
854
|
+
async function loadBookmarks() {
|
|
855
|
+
const el = $('#sec-bookmarks');
|
|
856
|
+
const [bookmarksData, sessionsData] = await Promise.all([api('bookmarks'), api('sessions')]);
|
|
857
|
+
if (!bookmarksData) { set(el, emptyState('⭐', 'No bookmarks data', 'Could not load bookmarks. Try again later.', 'Retry', 'refreshCurrent()')); return; }
|
|
858
|
+
|
|
859
|
+
const starred = bookmarksData.starred || [];
|
|
860
|
+
const allTags = bookmarksData.allTags || [];
|
|
861
|
+
const totalBookmarks = bookmarksData.totalBookmarks || 0;
|
|
862
|
+
const sessionsMap = new Map((sessionsData || []).map(s => [s.id, s]));
|
|
863
|
+
|
|
864
|
+
let h = '<h2>⭐ Starred Sessions & Bookmarks</h2>';
|
|
865
|
+
h += '<p style="color:var(--text2);margin-bottom:20px">Star important sessions to keep them safe from bulk operations and easy to find.</p>';
|
|
866
|
+
|
|
867
|
+
h += '<div class="grid" style="grid-template-columns:repeat(auto-fit,minmax(300px,1fr))">';
|
|
868
|
+
h += '<div class="card"><div class="stat-icon">⭐</div><h3>Starred</h3><div class="value">' + starred.length + '</div><div class="sub">protected sessions</div></div>';
|
|
869
|
+
h += '<div class="card"><div class="stat-icon">#</div><h3>Tags</h3><div class="value">' + allTags.length + '</div><div class="sub">unique tags</div></div>';
|
|
870
|
+
h += '<div class="card"><div class="stat-icon">🔖</div><h3>Bookmarks</h3><div class="value">' + totalBookmarks + '</div><div class="sub">saved locations</div></div>';
|
|
871
|
+
h += '</div>';
|
|
872
|
+
|
|
873
|
+
if (allTags.length > 0) {
|
|
874
|
+
h += '<h3 style="margin-top:24px">Tags</h3>';
|
|
875
|
+
h += '<div style="display:flex;flex-wrap:wrap;gap:8px;margin-bottom:24px">';
|
|
876
|
+
for (const t of allTags.slice(0, 20)) {
|
|
877
|
+
h += '<span style="background:var(--accent);color:white;padding:4px 12px;border-radius:16px;font-size:13px;cursor:pointer" onclick="filterByTag(\\'' + esc(t.tag) + '\\')">#' + esc(t.tag) + ' <span style="opacity:0.8">(' + t.count + ')</span></span>';
|
|
878
|
+
}
|
|
879
|
+
h += '</div>';
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
h += '<h3 style="margin-top:24px">Starred Sessions</h3>';
|
|
883
|
+
if (starred.length === 0) {
|
|
884
|
+
h += '<div class="card" style="text-align:center;padding:40px;color:var(--text3)">';
|
|
885
|
+
h += '<div style="font-size:48px;margin-bottom:12px">⭐</div>';
|
|
886
|
+
h += '<div style="font-size:16px;margin-bottom:8px">No starred sessions yet</div>';
|
|
887
|
+
h += '<div>Go to Sessions tab and click the star icon to star a session</div>';
|
|
888
|
+
h += '</div>';
|
|
889
|
+
} else {
|
|
890
|
+
h += '<div class="table-wrap"><table><thead><tr><th style="width:40px"></th><th>Name / Project</th><th>Tags</th><th>Messages</th><th>Size</th><th>Actions</th></tr></thead><tbody>';
|
|
891
|
+
for (const s of starred) {
|
|
892
|
+
const session = Array.from(sessionsMap.values()).find(sess => sess.id === s.sessionId || sess.id.startsWith(s.sessionId));
|
|
893
|
+
const name = s.name || (session?.project || s.sessionId.slice(0, 8));
|
|
894
|
+
const tags = (s.tags || []).map(t => '<span style="background:var(--bg2);padding:2px 8px;border-radius:8px;font-size:11px;margin-right:4px">#' + esc(t) + '</span>').join('');
|
|
895
|
+
const messages = session?.messageCount || '?';
|
|
896
|
+
const size = session ? fmtB(session.sizeBytes) : '?';
|
|
897
|
+
h += '<tr>';
|
|
898
|
+
h += '<td style="color:var(--yellow);font-size:20px;cursor:pointer" onclick="unstarSession(\\'' + s.sessionId + '\\')">⭐</td>';
|
|
899
|
+
h += '<td><strong>' + esc(name) + '</strong><br><span style="font-size:11px;color:var(--text3)">' + s.sessionId.slice(0, 12) + '...</span></td>';
|
|
900
|
+
h += '<td>' + (tags || '<span style="color:var(--text3)">-</span>') + '</td>';
|
|
901
|
+
h += '<td>' + messages + '</td>';
|
|
902
|
+
h += '<td>' + size + '</td>';
|
|
903
|
+
h += '<td>';
|
|
904
|
+
h += '<button class="btn btn-sm" onclick="renameSessionDialog(\\'' + s.sessionId + '\\', \\'' + esc(name) + '\\')">Rename</button> ';
|
|
905
|
+
h += '<button class="btn btn-sm" onclick="tagSessionDialog(\\'' + s.sessionId + '\\')">Tag</button> ';
|
|
906
|
+
h += '<button class="btn btn-sm btn-primary" onclick="exportSessionHtml(\\'' + s.sessionId + '\\')">Export</button>';
|
|
907
|
+
h += '</td>';
|
|
908
|
+
h += '</tr>';
|
|
909
|
+
}
|
|
910
|
+
h += '</tbody></table></div>';
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
h += '<h3 style="margin-top:32px">Bulk Operations</h3>';
|
|
914
|
+
h += '<div class="card" style="padding:16px">';
|
|
915
|
+
const bulkProjects = getUniqueProjects(sessionsData || allSessions || []);
|
|
916
|
+
h += '<div style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-bottom:12px">';
|
|
917
|
+
h += '<label style="font-size:13px;font-weight:600;color:var(--text2)">Target:</label>';
|
|
918
|
+
h += '<select id="bulkProjectSelect" style="padding:8px 12px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--bg);color:var(--text);font-size:13px;min-width:240px;cursor:pointer">';
|
|
919
|
+
h += '<option value="">All Projects</option>';
|
|
920
|
+
bulkProjects.forEach(p => {
|
|
921
|
+
h += '<option value="' + esc(p.fullPath ? p.fullPath.replace(/\\//g, '-').replace(/^-/, '-') : p.name) + '">' + esc(p.name) + ' (' + p.count + ' sessions)</option>';
|
|
922
|
+
});
|
|
923
|
+
h += '</select>';
|
|
924
|
+
h += '<label style="font-size:13px;font-weight:600;color:var(--text2);margin-left:8px">Days inactive:</label>';
|
|
925
|
+
h += '<input type="number" id="bulkDaysInput" value="30" min="0" style="width:70px;padding:8px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--bg);color:var(--text);font-size:13px">';
|
|
926
|
+
h += '</div>';
|
|
927
|
+
h += '<div class="action-bar">';
|
|
928
|
+
h += '<button class="btn" onclick="bulkExportDialog()">Export</button>';
|
|
929
|
+
h += '<button class="btn btn-primary" onclick="bulkArchiveDialog()">Archive</button>';
|
|
930
|
+
h += '<button class="btn btn-warn" onclick="bulkDeleteDialog()">Delete Sessions</button>';
|
|
931
|
+
h += '<button class="btn btn-danger" onclick="bulkPurgeDialog()">Purge Project</button>';
|
|
932
|
+
h += '</div>';
|
|
933
|
+
h += '<p style="color:var(--text3);font-size:12px;margin-top:8px">Starred sessions are protected from bulk delete and archive. Purge removes the entire project directory.</p>';
|
|
934
|
+
h += '</div>';
|
|
935
|
+
|
|
936
|
+
set(el, h);
|
|
937
|
+
staggerCards(el);
|
|
938
|
+
refreshTime();
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
async function unstarSession(sessionId) {
|
|
942
|
+
const r = await post('action/star-session', { sessionId, starred: false });
|
|
943
|
+
if (r?.success) { toast('Session unstarred', 'success'); loaded['bookmarks'] = false; loadTab('bookmarks'); }
|
|
944
|
+
else toast('Failed to unstar session', 'error');
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
async function renameSessionDialog(sessionId, currentName) {
|
|
948
|
+
const name = prompt('Enter new name for session:', currentName);
|
|
949
|
+
if (!name) return;
|
|
950
|
+
const r = await post('action/rename-session', { sessionId, name });
|
|
951
|
+
if (r?.success) { toast('Session renamed', 'success'); loaded['bookmarks'] = false; loadTab('bookmarks'); }
|
|
952
|
+
else toast('Failed to rename session', 'error');
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
async function tagSessionDialog(sessionId) {
|
|
956
|
+
const tag = prompt('Enter tag to add (or comma-separated tags):');
|
|
957
|
+
if (!tag) return;
|
|
958
|
+
const tags = tag.split(',').map(t => t.trim()).filter(Boolean);
|
|
959
|
+
for (const t of tags) {
|
|
960
|
+
await post('action/add-tag', { sessionId, tag: t });
|
|
961
|
+
}
|
|
962
|
+
toast('Tags added', 'success');
|
|
963
|
+
loaded['bookmarks'] = false;
|
|
964
|
+
loadTab('bookmarks');
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
async function exportSessionHtml(sessionId) {
|
|
968
|
+
const r = await post('action/export-html', { sessionId, theme: 'light' });
|
|
969
|
+
if (r?.success) { toast('Exported to ' + r.file, 'success'); }
|
|
970
|
+
else toast('Failed to export', 'error');
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function getBulkParams() {
|
|
974
|
+
const projEl = document.getElementById('bulkProjectSelect');
|
|
975
|
+
const daysEl = document.getElementById('bulkDaysInput');
|
|
976
|
+
const projectFilter = projEl ? projEl.value : '';
|
|
977
|
+
const days = daysEl ? parseInt(daysEl.value) || 0 : 0;
|
|
978
|
+
return { projectFilter: projectFilter || undefined, days };
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
async function bulkExportDialog() {
|
|
982
|
+
const { projectFilter } = getBulkParams();
|
|
983
|
+
showProgress('Exporting sessions...');
|
|
984
|
+
const r = await post('action/bulk-export', { projectFilter, format: 'html' });
|
|
985
|
+
hideProgress();
|
|
986
|
+
if (r?.success) { toast('Exported ' + r.exported + ' sessions', 'success'); }
|
|
987
|
+
else toast('Export failed', 'error');
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
async function bulkArchiveDialog() {
|
|
991
|
+
const { projectFilter, days } = getBulkParams();
|
|
992
|
+
const label = projectFilter ? projectFilter.replace(/^-/, '/').replace(/-/g, '/') : 'all projects';
|
|
993
|
+
const dryRun = confirm('Preview first?\\n\\nTarget: ' + label + '\\nDays inactive: ' + (days || 'any') + '\\n\\nOK = dry run, Cancel = archive now');
|
|
994
|
+
showProgress('Archiving sessions...');
|
|
995
|
+
const r = await post('action/bulk-archive', { days, projectFilter, dryRun });
|
|
996
|
+
hideProgress();
|
|
997
|
+
if (r?.success) {
|
|
998
|
+
if (r.dryRun) {
|
|
999
|
+
toast('Would archive ' + r.archived + ' sessions (' + r.skipped + ' skipped). Run again without preview to execute.', 'info');
|
|
1000
|
+
} else {
|
|
1001
|
+
toast('Archived ' + r.archived + ' sessions', 'success');
|
|
1002
|
+
}
|
|
1003
|
+
loaded['bookmarks'] = false; loadTab('bookmarks');
|
|
1004
|
+
} else toast('Archive failed', 'error');
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
async function bulkDeleteDialog() {
|
|
1008
|
+
const { projectFilter, days } = getBulkParams();
|
|
1009
|
+
const label = projectFilter ? projectFilter.replace(/^-/, '/').replace(/-/g, '/') : 'all projects';
|
|
1010
|
+
const dryRun = confirm('Preview first?\\n\\nTarget: ' + label + '\\nDays inactive: ' + (days || 'any') + '\\n\\nOK = dry run, Cancel = delete now');
|
|
1011
|
+
showProgress('Processing...');
|
|
1012
|
+
const r = await post('action/bulk-delete', { days, projectFilter, dryRun });
|
|
1013
|
+
hideProgress();
|
|
1014
|
+
if (r?.success) {
|
|
1015
|
+
if (r.dryRun) {
|
|
1016
|
+
toast('Would delete ' + r.deleted + ' sessions (' + r.skipped + ' skipped). Starred sessions are protected.', 'info');
|
|
1017
|
+
} else {
|
|
1018
|
+
toast('Deleted ' + r.deleted + ' sessions', 'success');
|
|
1019
|
+
}
|
|
1020
|
+
loaded['sessions'] = false;
|
|
1021
|
+
loaded['bookmarks'] = false;
|
|
1022
|
+
} else toast('Delete failed', 'error');
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
async function bulkPurgeDialog() {
|
|
1026
|
+
const { projectFilter } = getBulkParams();
|
|
1027
|
+
if (!projectFilter) { toast('Select a specific project to purge', 'error'); return; }
|
|
1028
|
+
const projName = projectFilter.replace(/^-/, '/').replace(/-/g, '/');
|
|
1029
|
+
if (!confirm('PERMANENTLY delete ALL files for project:\\n\\n' + projName + '\\n\\nThis removes the entire project directory. This cannot be undone.\\n\\nContinue?')) return;
|
|
1030
|
+
showProgress('Purging project...');
|
|
1031
|
+
const r = await post('action/purge-project', { projectFilter });
|
|
1032
|
+
hideProgress();
|
|
1033
|
+
if (r?.success) {
|
|
1034
|
+
toast('Purged: ' + r.sessionsDeleted + ' sessions, ' + r.dirsRemoved + ' dirs removed (' + fmtB(r.freedBytes) + ' freed)', 'success');
|
|
1035
|
+
loaded['sessions'] = false; loaded['bookmarks'] = false; loaded['storage'] = false;
|
|
1036
|
+
loadTab('bookmarks');
|
|
1037
|
+
} else toast('Purge failed: ' + (r?.error || 'unknown'), 'error');
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function filterByTag(tag) {
|
|
1041
|
+
toast('Filtering by #' + tag + ' - this feature is coming soon', 'info');
|
|
1042
|
+
}
|
|
1043
|
+
|
|
852
1044
|
function healthRing(pct, color) {
|
|
853
1045
|
const r=47, c=2*Math.PI*r, off=c-(pct/100)*c;
|
|
854
1046
|
return '<div class="health-ring"><svg viewBox="0 0 110 110"><circle class="ring-bg" cx="55" cy="55" r="'+r+'"/><circle class="ring-fg" cx="55" cy="55" r="'+r+'" stroke="'+color+'" stroke-dasharray="'+c+'" stroke-dashoffset="'+off+'"/></svg><div class="score"><div class="score-val" style="color:'+color+'">'+pct+'</div><div class="score-lbl">Health</div></div></div>';
|
|
@@ -983,7 +1175,7 @@ function renderSessionTable() {
|
|
|
983
1175
|
});
|
|
984
1176
|
const sb = s => { if (s === 'healthy') return badge('healthy', 'green'); if (s === 'corrupted') return badge('corrupted', 'red'); if (s === 'empty') return badge('empty', 'yellow'); return badge(s, 'blue'); };
|
|
985
1177
|
const sc = (c) => c === col ? (dir === 'asc' ? ' asc' : ' desc') : '';
|
|
986
|
-
let h = '<tr><th>ID</th><th class="sort-header' + sc('status') + '" onclick="sortSessions(\\'status\\')">Status</th><th class="sort-header' + sc('messages') + '" onclick="sortSessions(\\'messages\\')">Messages</th><th class="sort-header' + sc('size') + '" onclick="sortSessions(\\'size\\')">Size</th><th style="min-width:200px">Project</th><th class="sort-header' + sc('modified') + '" onclick="sortSessions(\\'modified\\')">Last Active</th><th>Actions</th></tr>';
|
|
1178
|
+
let h = '<tr><th style="width:32px">⭐</th><th>ID</th><th class="sort-header' + sc('status') + '" onclick="sortSessions(\\'status\\')">Status</th><th class="sort-header' + sc('messages') + '" onclick="sortSessions(\\'messages\\')">Messages</th><th class="sort-header' + sc('size') + '" onclick="sortSessions(\\'size\\')">Size</th><th style="min-width:200px">Project</th><th class="sort-header' + sc('modified') + '" onclick="sortSessions(\\'modified\\')">Last Active</th><th>Actions</th></tr>';
|
|
987
1179
|
h += '<tbody id="sessionTableBody">';
|
|
988
1180
|
d.slice(0, 100).forEach(s => {
|
|
989
1181
|
const sid = esc(s.id.slice(0, 12));
|
|
@@ -993,24 +1185,162 @@ function renderSessionTable() {
|
|
|
993
1185
|
if (s.status === 'corrupted') acts += '<button class="btn btn-sm btn-danger" onclick="doRepair(\\''+esc(s.id)+'\\')">Repair</button> ';
|
|
994
1186
|
acts += '<button class="btn btn-sm" onclick="doExtract(\\''+esc(s.id)+'\\')">Extract</button> ';
|
|
995
1187
|
acts += '<button class="btn btn-sm btn-primary" onclick="doAudit(\\''+esc(s.id)+'\\')">Audit</button>';
|
|
996
|
-
|
|
1188
|
+
const starIcon = '<span class="star-icon" style="cursor:pointer;font-size:18px;opacity:0.3" onclick="toggleStar(event,\\''+esc(s.id)+'\\')">☆</span>';
|
|
1189
|
+
h += '<tr data-sid="' + esc(s.id) + '" data-proj="' + esc(s.project || '') + '" data-status="' + esc(s.status) + '"><td>' + starIcon + '</td><td class="mono">' + sid + '</td><td>' + sb(s.status) + '</td><td>' + s.messageCount + '</td><td>' + fmtB(s.sizeBytes) + '</td><td>' + projDisplay + '</td><td>' + ago(s.modified) + '</td><td>' + acts + '</td></tr>';
|
|
997
1190
|
});
|
|
998
|
-
if (d.length > 100) h += '<tr><td colspan="
|
|
1191
|
+
if (d.length > 100) h += '<tr><td colspan="8" style="text-align:center;color:var(--text3)">...and ' + (d.length - 100) + ' more</td></tr>';
|
|
999
1192
|
h += '</tbody>';
|
|
1000
1193
|
const tbl = $('#sessionTable'); if (tbl) set(tbl, h);
|
|
1194
|
+
loadSessionStars();
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
async function loadSessionStars() {
|
|
1198
|
+
try {
|
|
1199
|
+
const bookmarks = await api('bookmarks');
|
|
1200
|
+
const starredIds = new Set((bookmarks?.starred || []).map(s => s.sessionId));
|
|
1201
|
+
$$('.star-icon').forEach(el => {
|
|
1202
|
+
const row = el.closest('tr');
|
|
1203
|
+
const sid = row?.dataset?.sid;
|
|
1204
|
+
if (sid && starredIds.has(sid)) {
|
|
1205
|
+
el.textContent = '⭐';
|
|
1206
|
+
el.style.opacity = '1';
|
|
1207
|
+
el.style.color = 'var(--yellow)';
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
} catch { }
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
async function toggleStar(event, sessionId) {
|
|
1214
|
+
event.stopPropagation();
|
|
1215
|
+
const el = event.target;
|
|
1216
|
+
const isStarred = el.textContent === '⭐';
|
|
1217
|
+
const r = await post('action/star-session', { sessionId, starred: !isStarred });
|
|
1218
|
+
if (r?.success) {
|
|
1219
|
+
if (isStarred) {
|
|
1220
|
+
el.textContent = '☆';
|
|
1221
|
+
el.style.opacity = '0.3';
|
|
1222
|
+
el.style.color = '';
|
|
1223
|
+
toast('Session unstarred', 'success');
|
|
1224
|
+
} else {
|
|
1225
|
+
el.textContent = '⭐';
|
|
1226
|
+
el.style.opacity = '1';
|
|
1227
|
+
el.style.color = 'var(--yellow)';
|
|
1228
|
+
toast('Session starred', 'success');
|
|
1229
|
+
}
|
|
1230
|
+
loaded['bookmarks'] = false;
|
|
1231
|
+
} else {
|
|
1232
|
+
toast('Failed to update star', 'error');
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
function getUniqueProjects(sessions) {
|
|
1236
|
+
const projMap = {};
|
|
1237
|
+
sessions.forEach(s => {
|
|
1238
|
+
const p = parseProjectPath(s.projectPath || s.project);
|
|
1239
|
+
const key = p.fullPath || p.name;
|
|
1240
|
+
if (!projMap[key]) projMap[key] = { name: p.name, fullPath: p.fullPath, count: 0, totalSize: 0 };
|
|
1241
|
+
projMap[key].count++;
|
|
1242
|
+
projMap[key].totalSize += (s.sizeBytes || 0);
|
|
1243
|
+
});
|
|
1244
|
+
return Object.values(projMap).sort((a, b) => b.count - a.count);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
let selectedProjectFilter = '';
|
|
1248
|
+
|
|
1249
|
+
function filterSessionsByProject(val) {
|
|
1250
|
+
selectedProjectFilter = val;
|
|
1251
|
+
const el = $('#sessionTableBody');
|
|
1252
|
+
if (!el) return;
|
|
1253
|
+
const rows = el.querySelectorAll('tr[data-sid]');
|
|
1254
|
+
rows.forEach(r => {
|
|
1255
|
+
const proj = (r.getAttribute('data-proj') || '').toLowerCase();
|
|
1256
|
+
if (!val || proj.includes(val.toLowerCase())) r.style.display = '';
|
|
1257
|
+
else r.style.display = 'none';
|
|
1258
|
+
});
|
|
1259
|
+
const bar = $('#projectActionBar');
|
|
1260
|
+
if (bar) bar.style.display = val ? 'flex' : 'none';
|
|
1261
|
+
const label = $('#selectedProjectLabel');
|
|
1262
|
+
if (label) label.textContent = val ? val.replace(/^-/, '/').replace(/-/g, '/') : '';
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
async function purgeProject() {
|
|
1266
|
+
const pf = selectedProjectFilter;
|
|
1267
|
+
if (!pf) { toast('Select a project first', 'error'); return; }
|
|
1268
|
+
const projName = pf.replace(/^-/, '/').replace(/-/g, '/');
|
|
1269
|
+
if (!confirm('PERMANENTLY delete ALL sessions and files for project:\\n\\n' + projName + '\\n\\nThis removes the entire project directory from ~/.claude/projects/. Backups will be created first.\\n\\nContinue?')) return;
|
|
1270
|
+
showProgress('Purging project...');
|
|
1271
|
+
const r = await post('action/purge-project', { projectFilter: pf });
|
|
1272
|
+
hideProgress();
|
|
1273
|
+
if (r?.success) {
|
|
1274
|
+
toast('Purged project: ' + r.sessionsDeleted + ' sessions, ' + r.dirsRemoved + ' dirs removed (' + fmtB(r.freedBytes) + ' freed)', 'success');
|
|
1275
|
+
selectedProjectFilter = '';
|
|
1276
|
+
loaded['sessions'] = false; loaded['bookmarks'] = false; loaded['storage'] = false;
|
|
1277
|
+
loadTab('sessions');
|
|
1278
|
+
} else toast('Purge failed: ' + (r?.error || 'unknown'), 'error');
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
async function deleteProjectSessions() {
|
|
1282
|
+
const pf = selectedProjectFilter;
|
|
1283
|
+
if (!pf) { toast('Select a project first', 'error'); return; }
|
|
1284
|
+
const projName = pf.replace(/^-/, '/').replace(/-/g, '/');
|
|
1285
|
+
if (!confirm('Delete all session files for project:\\n\\n' + projName + '\\n\\nBackups will be created. Starred sessions are protected.\\n\\nContinue?')) return;
|
|
1286
|
+
showProgress('Deleting sessions...');
|
|
1287
|
+
const r = await post('action/bulk-delete', { projectFilter: pf, dryRun: false });
|
|
1288
|
+
hideProgress();
|
|
1289
|
+
if (r?.success) {
|
|
1290
|
+
toast('Deleted ' + r.deleted + ' sessions (' + r.skipped + ' skipped)', 'success');
|
|
1291
|
+
loaded['sessions'] = false; loaded['bookmarks'] = false;
|
|
1292
|
+
loadTab('sessions');
|
|
1293
|
+
} else toast('Delete failed', 'error');
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
async function archiveProjectSessions() {
|
|
1297
|
+
const pf = selectedProjectFilter;
|
|
1298
|
+
if (!pf) { toast('Select a project first', 'error'); return; }
|
|
1299
|
+
const projName = pf.replace(/^-/, '/').replace(/-/g, '/');
|
|
1300
|
+
if (!confirm('Archive all sessions for project:\\n\\n' + projName + '\\n\\nSessions will be moved to ~/.claude/archive/. Starred sessions are protected.\\n\\nContinue?')) return;
|
|
1301
|
+
showProgress('Archiving sessions...');
|
|
1302
|
+
const r = await post('action/bulk-archive', { projectFilter: pf, dryRun: false });
|
|
1303
|
+
hideProgress();
|
|
1304
|
+
if (r?.success) {
|
|
1305
|
+
toast('Archived ' + r.archived + ' sessions', 'success');
|
|
1306
|
+
loaded['sessions'] = false; loaded['bookmarks'] = false;
|
|
1307
|
+
loadTab('sessions');
|
|
1308
|
+
} else toast('Archive failed', 'error');
|
|
1001
1309
|
}
|
|
1310
|
+
|
|
1002
1311
|
async function loadSessions() {
|
|
1003
1312
|
const d = await api('sessions');
|
|
1004
1313
|
const el = $('#sec-sessions');
|
|
1005
1314
|
if (!d || !d.length) { set(el, emptyState('💬', 'No sessions found', 'No Claude Code session files were found in the .claude directory.', 'Refresh', 'refreshCurrent()')); return; }
|
|
1006
1315
|
allSessions = d;
|
|
1007
1316
|
const sm = { healthy: 0, corrupted: 0, empty: 0, orphaned: 0 }; d.forEach(s => { sm[s.status] = (sm[s.status] || 0) + 1; });
|
|
1317
|
+
const projects = getUniqueProjects(d);
|
|
1008
1318
|
let h = '<h2>Sessions (' + d.length + ')</h2>';
|
|
1009
1319
|
h += '<div class="grid"><div class="card"><div class="stat-icon">✅</div><h3>Healthy</h3><div class="value c-green">' + sm.healthy + '</div></div>';
|
|
1010
1320
|
h += '<div class="card"><div class="stat-icon">⚠</div><h3>Corrupted</h3><div class="value c-red">' + sm.corrupted + '</div></div>';
|
|
1011
1321
|
h += '<div class="card"><div class="stat-icon">📄</div><h3>Empty</h3><div class="value c-yellow">' + sm.empty + '</div></div>';
|
|
1012
1322
|
h += '<div class="card"><div class="stat-icon">🔗</div><h3>Orphaned</h3><div class="value c-orange">' + sm.orphaned + '</div></div></div>';
|
|
1013
|
-
|
|
1323
|
+
|
|
1324
|
+
h += '<div class="card mt" style="padding:16px">';
|
|
1325
|
+
h += '<div style="display:flex;gap:12px;align-items:center;flex-wrap:wrap">';
|
|
1326
|
+
h += '<input type="text" class="search-input" style="flex:1;min-width:200px" placeholder="Search sessions by ID, project, or status..." oninput="filterSessions(this.value)">';
|
|
1327
|
+
h += '<select id="projectSelect" style="padding:8px 12px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--bg);color:var(--text);font-size:13px;min-width:220px;cursor:pointer" onchange="filterSessionsByProject(this.value)">';
|
|
1328
|
+
h += '<option value="">All Projects (' + projects.length + ')</option>';
|
|
1329
|
+
projects.forEach(p => {
|
|
1330
|
+
h += '<option value="' + esc(p.fullPath ? p.fullPath.replace(/\\//g, '-').replace(/^-/, '-') : p.name) + '">' + esc(p.name) + ' (' + p.count + ' sessions, ' + fmtB(p.totalSize) + ')</option>';
|
|
1331
|
+
});
|
|
1332
|
+
h += '</select>';
|
|
1333
|
+
h += '</div>';
|
|
1334
|
+
|
|
1335
|
+
h += '<div id="projectActionBar" style="display:none;gap:10px;align-items:center;margin-top:12px;padding:12px;background:var(--bg3);border-radius:var(--radius-sm);border:1px solid var(--border)">';
|
|
1336
|
+
h += '<span style="font-weight:600;font-size:13px;color:var(--text2)">Project:</span> ';
|
|
1337
|
+
h += '<span id="selectedProjectLabel" style="font-weight:700;color:var(--accent);font-size:13px;flex:1"></span>';
|
|
1338
|
+
h += '<button class="btn btn-sm btn-primary" onclick="archiveProjectSessions()">Archive All</button> ';
|
|
1339
|
+
h += '<button class="btn btn-sm btn-warn" onclick="deleteProjectSessions()">Delete Sessions</button> ';
|
|
1340
|
+
h += '<button class="btn btn-sm btn-danger" onclick="purgeProject()">Purge Project</button>';
|
|
1341
|
+
h += '</div>';
|
|
1342
|
+
h += '</div>';
|
|
1343
|
+
|
|
1014
1344
|
h += '<div class="card mt"><table id="sessionTable"></table></div>';
|
|
1015
1345
|
h += '<div id="sessionDetail"></div>';
|
|
1016
1346
|
set(el, h); staggerCards(el); renderSessionTable(); refreshTime();
|
|
@@ -1782,20 +2112,80 @@ function showTraceCategoryFiles(categoryName) {
|
|
|
1782
2112
|
const categories = window._traceCategories || [];
|
|
1783
2113
|
const cat = categories.find(c => c.name === categoryName);
|
|
1784
2114
|
if (!cat || !cat.allFiles?.length) { toast('No files found for this category', 'info'); return; }
|
|
1785
|
-
|
|
1786
|
-
body
|
|
1787
|
-
body += '<
|
|
1788
|
-
|
|
2115
|
+
window._currentTraceCat = categoryName;
|
|
2116
|
+
let body = '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;padding-bottom:12px;border-bottom:1px solid var(--border)">';
|
|
2117
|
+
body += '<div style="font-size:13px;color:var(--text2)">' + cat.fileCount + ' files, ' + fmtB(cat.totalSize) + ' total</div>';
|
|
2118
|
+
body += '<div style="display:flex;gap:8px">';
|
|
2119
|
+
body += '<button class="btn btn-sm" onclick="selectAllTraceFiles()"><input type="checkbox" id="selectAllTrace" style="margin-right:6px">Select All</button>';
|
|
2120
|
+
body += '<button class="btn btn-sm btn-danger" onclick="cleanSelectedTraceFiles()" id="cleanSelectedBtn" disabled>Clean Selected (0)</button>';
|
|
2121
|
+
body += '<button class="btn btn-sm btn-danger" onclick="cleanTraceCategory(\\'' + esc(categoryName) + '\\')">Clean All ' + cat.fileCount + ' Files</button>';
|
|
2122
|
+
body += '</div></div>';
|
|
2123
|
+
body += '<div style="max-height:450px;overflow-y:auto">';
|
|
2124
|
+
body += '<table style="width:100%"><tr><th style="width:30px"></th><th style="text-align:left">Project/File</th><th>Size</th><th>Modified</th></tr>';
|
|
2125
|
+
cat.allFiles.forEach((f, idx) => {
|
|
1789
2126
|
body += '<tr>';
|
|
1790
|
-
body += '<td
|
|
2127
|
+
body += '<td><input type="checkbox" class="trace-file-cb" data-path="' + esc(f.fullPath) + '" data-size="' + f.size + '" onchange="updateTraceSelection()"></td>';
|
|
2128
|
+
body += '<td style="font-size:11px;max-width:250px;overflow:hidden;text-overflow:ellipsis" title="' + esc(f.fullPath) + '">' + esc(f.projectName || f.path) + '</td>';
|
|
1791
2129
|
body += '<td style="font-size:11px;white-space:nowrap">' + fmtB(f.size) + '</td>';
|
|
1792
2130
|
body += '<td style="font-size:11px;white-space:nowrap;color:var(--text3)">' + ago(f.modified) + '</td>';
|
|
1793
|
-
body += '<td style="font-size:9px;max-width:300px;word-break:break-all;color:var(--text3)">' + esc(f.fullPath) + '</td>';
|
|
1794
2131
|
body += '</tr>';
|
|
1795
2132
|
});
|
|
1796
2133
|
body += '</table></div>';
|
|
1797
2134
|
showModal('Files in ' + categoryName + ' (' + cat.fileCount + ')', body, 'Close', null, true);
|
|
1798
2135
|
}
|
|
2136
|
+
function selectAllTraceFiles() {
|
|
2137
|
+
const cb = $('#selectAllTrace');
|
|
2138
|
+
const checked = !cb.checked;
|
|
2139
|
+
cb.checked = checked;
|
|
2140
|
+
document.querySelectorAll('.trace-file-cb').forEach(c => c.checked = checked);
|
|
2141
|
+
updateTraceSelection();
|
|
2142
|
+
}
|
|
2143
|
+
function updateTraceSelection() {
|
|
2144
|
+
const checked = document.querySelectorAll('.trace-file-cb:checked');
|
|
2145
|
+
const btn = $('#cleanSelectedBtn');
|
|
2146
|
+
if (btn) {
|
|
2147
|
+
btn.disabled = checked.length === 0;
|
|
2148
|
+
let totalSize = 0;
|
|
2149
|
+
checked.forEach(c => totalSize += parseInt(c.dataset.size || '0'));
|
|
2150
|
+
btn.textContent = 'Clean Selected (' + checked.length + ', ' + fmtB(totalSize) + ')';
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
async function cleanSelectedTraceFiles() {
|
|
2154
|
+
const checked = document.querySelectorAll('.trace-file-cb:checked');
|
|
2155
|
+
if (checked.length === 0) return;
|
|
2156
|
+
const files = Array.from(checked).map(c => c.dataset.path);
|
|
2157
|
+
if (!confirm('Delete ' + files.length + ' selected files? This cannot be undone.')) return;
|
|
2158
|
+
showProgress('Deleting ' + files.length + ' files...');
|
|
2159
|
+
try {
|
|
2160
|
+
const result = await post('delete-files', { files });
|
|
2161
|
+
hideProgress();
|
|
2162
|
+
if (result.success) {
|
|
2163
|
+
toast(result.deleted + ' files deleted (' + fmtB(result.freedBytes) + ' freed)', 'success');
|
|
2164
|
+
hideModal();
|
|
2165
|
+
loadTraces();
|
|
2166
|
+
} else {
|
|
2167
|
+
toast('Error: ' + (result.error || 'Delete failed'), 'error');
|
|
2168
|
+
}
|
|
2169
|
+
} catch (e) { hideProgress(); toast('Error: ' + e.message, 'error'); }
|
|
2170
|
+
}
|
|
2171
|
+
async function cleanTraceCategory(categoryName) {
|
|
2172
|
+
const categories = window._traceCategories || [];
|
|
2173
|
+
const cat = categories.find(c => c.name === categoryName);
|
|
2174
|
+
if (!cat) return;
|
|
2175
|
+
if (!confirm('Delete ALL ' + cat.fileCount + ' files in "' + categoryName + '" (' + fmtB(cat.totalSize) + ')? This cannot be undone.')) return;
|
|
2176
|
+
showProgress('Cleaning ' + categoryName + '...');
|
|
2177
|
+
try {
|
|
2178
|
+
const result = await post('clean-category', { category: categoryName });
|
|
2179
|
+
hideProgress();
|
|
2180
|
+
if (result.success) {
|
|
2181
|
+
toast(result.deleted + ' files deleted (' + fmtB(result.freedBytes) + ' freed)', 'success');
|
|
2182
|
+
hideModal();
|
|
2183
|
+
loadTraces();
|
|
2184
|
+
} else {
|
|
2185
|
+
toast('Error: ' + (result.error || 'Clean failed'), 'error');
|
|
2186
|
+
}
|
|
2187
|
+
} catch (e) { hideProgress(); toast('Error: ' + e.message, 'error'); }
|
|
2188
|
+
}
|
|
1799
2189
|
async function doCleanTracesPreview() {
|
|
1800
2190
|
showProgress('Analyzing traces...');
|
|
1801
2191
|
let preview;
|
|
@@ -1917,7 +2307,7 @@ async function doRedactAllPII() {
|
|
|
1917
2307
|
}
|
|
1918
2308
|
function renderPIITable(findings, showDetails) {
|
|
1919
2309
|
let h = '<div style="overflow-x:auto"><table style="width:100%;min-width:900px">';
|
|
1920
|
-
h += '<tr><th style="width:80px">Severity</th><th style="width:100px">Type</th><th style="width:200px">Exact Value</th><th>Full Project Path</th><th style="width:320px">File Location</th><th style="width:
|
|
2310
|
+
h += '<tr><th style="width:80px">Severity</th><th style="width:100px">Type</th><th style="width:200px">Exact Value</th><th>Full Project Path</th><th style="width:320px">File Location</th><th style="width:140px">Actions</th></tr>';
|
|
1921
2311
|
findings.forEach(f => {
|
|
1922
2312
|
const sevBadge = f.sensitivity === 'high' ? badge('high', 'red') : f.sensitivity === 'medium' ? badge('medium', 'orange') : badge('low', 'green');
|
|
1923
2313
|
const fullFilePath = f.file || '';
|
|
@@ -1927,18 +2317,34 @@ function renderPIITable(findings, showDetails) {
|
|
|
1927
2317
|
const projectPath = projectEncoded.replace(/-/g, '/');
|
|
1928
2318
|
const exactValue = f.fullValue || f.maskedValue || '';
|
|
1929
2319
|
const fp = esc(f.file);
|
|
2320
|
+
const valueForIgnore = btoa(exactValue).replace(/[+/=]/g, '_');
|
|
1930
2321
|
h += '<tr>';
|
|
1931
2322
|
h += '<td>' + sevBadge + '</td>';
|
|
1932
2323
|
h += '<td style="font-size:12px">' + esc(f.type) + '</td>';
|
|
1933
2324
|
h += '<td class="mono" style="color:var(--red);font-weight:600;word-break:break-all">' + esc(exactValue) + '</td>';
|
|
1934
2325
|
h += '<td class="mono" style="word-break:break-all;font-size:11px;color:var(--text2)">' + esc(projectPath) + '</td>';
|
|
1935
2326
|
h += '<td class="mono" style="font-size:11px"><div style="word-break:break-all;color:var(--accent)">' + esc(fileName) + '</div><div style="color:var(--text3)">Line <strong>' + f.line + '</strong> • <span style="font-size:10px;opacity:0.7">' + esc(fullFilePath) + '</span></div></td>';
|
|
1936
|
-
h += '<td><button class="btn btn-sm btn-danger" onclick="doRedactPII(\\''+fp+'\\',' + f.line + ',\\''+esc(f.type||'')+'\\')">Redact</button
|
|
2327
|
+
h += '<td style="white-space:nowrap"><button class="btn btn-sm btn-danger" onclick="doRedactPII(\\''+fp+'\\',' + f.line + ',\\''+esc(f.type||'')+'\\')">Redact</button> ';
|
|
2328
|
+
h += '<button class="btn btn-sm" onclick="doIgnorePII(\\''+fp+'\\',' + f.line + ',\\''+esc(f.type||'')+'\\',\\''+valueForIgnore+'\\')" title="False positive? Add to ignore list">Ignore</button></td>';
|
|
1937
2329
|
h += '</tr>';
|
|
1938
2330
|
});
|
|
1939
2331
|
h += '</table></div>';
|
|
1940
2332
|
return h;
|
|
1941
2333
|
}
|
|
2334
|
+
async function doIgnorePII(file, line, type, valueB64) {
|
|
2335
|
+
const value = atob(valueB64.replace(/_/g, '+'));
|
|
2336
|
+
showProgress('Adding to ignore list...');
|
|
2337
|
+
try {
|
|
2338
|
+
const result = await post('ignore-pii', { finding: { file, line, type, value } });
|
|
2339
|
+
hideProgress();
|
|
2340
|
+
if (result.success) {
|
|
2341
|
+
toast('Added to ignore list: ' + result.ignored.slice(0, 30) + '...', 'success');
|
|
2342
|
+
loadSecurity();
|
|
2343
|
+
} else {
|
|
2344
|
+
toast('Error: ' + (result.error || 'Failed to ignore'), 'error');
|
|
2345
|
+
}
|
|
2346
|
+
} catch (e) { hideProgress(); toast('Error: ' + e.message, 'error'); }
|
|
2347
|
+
}
|
|
1942
2348
|
async function loadMorePII(count) {
|
|
1943
2349
|
if (!window._piiState) return;
|
|
1944
2350
|
showProgress('Loading more PII findings...');
|
|
@@ -2258,7 +2664,7 @@ async function doCompareSnapshots(id1, id2) {
|
|
|
2258
2664
|
showModal('Snapshot Comparison', html, 'Close', null, true);
|
|
2259
2665
|
}
|
|
2260
2666
|
|
|
2261
|
-
const loaders = { overview: loadOverview, search: loadSearchTab, storage: loadStorage, sessions: loadSessions, security: loadSecurity, traces: loadTraces, mcp: loadMcp, logs: loadLogs, config: loadConfig, analytics: loadAnalytics, backups: loadBackups, context: loadContext, maintenance: loadMaintenance, snapshots: loadSnapshots, about: loadAbout };
|
|
2667
|
+
const loaders = { overview: loadOverview, search: loadSearchTab, bookmarks: loadBookmarks, storage: loadStorage, sessions: loadSessions, security: loadSecurity, traces: loadTraces, mcp: loadMcp, logs: loadLogs, config: loadConfig, analytics: loadAnalytics, backups: loadBackups, context: loadContext, maintenance: loadMaintenance, snapshots: loadSnapshots, about: loadAbout };
|
|
2262
2668
|
loadTab('overview');
|
|
2263
2669
|
</script>
|
|
2264
2670
|
</body>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard-ui.js","sourceRoot":"","sources":["../../src/lib/dashboard-ui.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,qBAAqB;IACnC,OAAO
|
|
1
|
+
{"version":3,"file":"dashboard-ui.js","sourceRoot":"","sources":["../../src/lib/dashboard-ui.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,qBAAqB;IACnC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA6mFC,CAAC;AACX,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/lib/dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/lib/dashboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AA2E7B,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAsiDD,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CAoNrE;AAED,wBAAsB,cAAc,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CA6CrF;AAED,wBAAgB,aAAa,IAAI,OAAO,CAWvC;AAED,wBAAgB,kBAAkB,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAStF"}
|