Skip to content

Commit 3d44a38

Browse files
committed
feat: add option to load sample data
refactor: group source files under src folder and publish it
1 parent ac663d2 commit 3d44a38

14 files changed

+315
-289
lines changed

.github/workflows/release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,5 @@ jobs:
2828
with:
2929
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
3030
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
31-
command: pages deploy . --project-name=truffleshow
31+
command: pages deploy src/ --project-name=truffleshow
3232
gitHubToken: ${{ secrets.GITHUB_TOKEN }}

release-please-config.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"bump-minor-pre-major": true,
88
"bump-patch-for-minor-pre-major": false,
99
"extra-files": [
10-
"index.html"
10+
"src/index.html"
1111
]
1212
}
1313
}

src/core.js

+267
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
document.addEventListener("alpine:init", () => {
2+
Alpine.data("TruffleShowApp", () => ({
3+
truffleHogData: [],
4+
displayedData: [],
5+
isFileUploaded: false,
6+
isLoading: false,
7+
error: null,
8+
expandedItems: {},
9+
isGeneratingReport: false,
10+
sortBy: "verification", // Default sort
11+
sortDirection: "desc", // Default direction
12+
supportedSources: ["Git", "Github"],
13+
source: undefined,
14+
sampleDataURL: "https://static.truffleshow.dev/github-sample.json",
15+
16+
init() { },
17+
18+
uploadFile(event) {
19+
const file = event.target.files[0];
20+
if (!file) return;
21+
22+
this.isLoading = true;
23+
this.error = null;
24+
25+
const reader = new FileReader();
26+
reader.onload = (e) => {
27+
this.loadFile(e.target.result);
28+
};
29+
reader.onerror = () => {
30+
this.error = "Error reading file. Please try again.";
31+
this.isLoading = false;
32+
};
33+
reader.readAsText(file);
34+
},
35+
36+
loadFile(data) {
37+
try {
38+
this.truffleHogData = JSON.parse(data);
39+
// Initialize all items as collapsed
40+
this.expandedItems = Object.fromEntries(
41+
this.truffleHogData.map((_, index) => [index, false]),
42+
);
43+
const sourceNames = this.truffleHogData.map(
44+
(finding) => Object.keys(finding.SourceMetadata.Data)[0],
45+
);
46+
this.source = sourceNames.length > 0 ? sourceNames[0] : undefined;
47+
console.log("Source is: ", this.source);
48+
this.applySorting();
49+
this.isFileUploaded = true;
50+
this.isLoading = false;
51+
} catch (error) {
52+
console.error(error);
53+
this.error =
54+
"Invalid JSON file. Please upload a valid TruffleHog JSON output.";
55+
this.isLoading = false;
56+
}
57+
},
58+
59+
loadSampleData() {
60+
this.isLoading = true;
61+
fetch(this.sampleDataURL)
62+
.then((response) => response.text())
63+
.then((data) => {
64+
this.loadFile(data);
65+
})
66+
.catch((error) => {
67+
console.error(error);
68+
this.error = "Error loading sample data. Please try again.";
69+
this.isLoading = false;
70+
})
71+
.finally(() => {
72+
this.isLoading = false;
73+
});
74+
},
75+
76+
resetApp() {
77+
this.truffleHogData = [];
78+
this.displayedData = [];
79+
this.isFileUploaded = false;
80+
this.error = null;
81+
this.expandedItems = {};
82+
document.getElementById("fileInput").value = "";
83+
},
84+
85+
toggleItem(index) {
86+
this.expandedItems[index] = !this.expandedItems[index];
87+
},
88+
89+
expandAll() {
90+
this.expandedItems = Object.fromEntries(
91+
this.displayedData.map((_, index) => [index, true]),
92+
);
93+
},
94+
95+
collapseAll() {
96+
this.expandedItems = Object.fromEntries(
97+
this.displayedData.map((_, index) => [index, false]),
98+
);
99+
},
100+
101+
// Sorting functions
102+
setSorting(sortType) {
103+
if (this.sortBy === sortType) {
104+
// Toggle direction if clicking the same sort type
105+
this.sortDirection = this.sortDirection === "asc" ? "desc" : "asc";
106+
} else {
107+
this.sortBy = sortType;
108+
// Set default direction based on sort type
109+
this.sortDirection = sortType === "date" ? "desc" : "desc";
110+
}
111+
this.applySorting();
112+
},
113+
114+
applySorting() {
115+
// Clone the array to avoid mutating the original
116+
let sortedData = [...this.truffleHogData];
117+
118+
// Sort based on current sort settings
119+
if (this.sortBy === "verification") {
120+
sortedData.sort((a, b) => {
121+
// Priority: Verified (highest), Not Verified, Failed (lowest)
122+
const scoreA = a.Verified ? 3 : a.VerificationError ? 1 : 2;
123+
const scoreB = b.Verified ? 3 : b.VerificationError ? 1 : 2;
124+
125+
return this.sortDirection === "asc"
126+
? scoreA - scoreB
127+
: scoreB - scoreA;
128+
});
129+
} else if (this.sortBy === "date") {
130+
sortedData.sort((a, b) => {
131+
const dateA = a.SourceMetadata?.Data?.[this.source]?.timestamp
132+
? new Date(a.SourceMetadata.Data[this.source].timestamp)
133+
: new Date(0);
134+
const dateB = b.SourceMetadata?.Data?.[this.source]?.timestamp
135+
? new Date(b.SourceMetadata.Data[this.source].timestamp)
136+
: new Date(0);
137+
138+
return this.sortDirection === "asc" ? dateA - dateB : dateB - dateA;
139+
});
140+
}
141+
142+
// Update the displayed data
143+
this.displayedData = sortedData;
144+
145+
// Reset expanded states when sorting
146+
this.expandedItems = Object.fromEntries(
147+
this.displayedData.map((_, index) => [index, false]),
148+
);
149+
},
150+
151+
getSortIcon(sortType) {
152+
if (this.sortBy !== sortType) return "fa-sort";
153+
return this.sortDirection === "asc" ? "fa-sort-up" : "fa-sort-down";
154+
},
155+
156+
formatTimestamp(timestamp) {
157+
if (!timestamp) return "N/A";
158+
return new Date(timestamp).toLocaleString();
159+
},
160+
161+
getVerificationStatus(finding) {
162+
if (finding.Verified) return "Verified";
163+
return finding.VerificationError ? "Failed" : "Not Verified";
164+
},
165+
166+
getStatusColor(finding) {
167+
if (finding.Verified)
168+
return "bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200";
169+
return finding.VerificationError
170+
? "bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200"
171+
: "bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200";
172+
},
173+
174+
getStatusIcon(finding) {
175+
if (finding.Verified) return "fa-check-circle";
176+
return finding.VerificationError
177+
? "fa-circle-exclamation"
178+
: "fa-circle-question";
179+
},
180+
181+
getDetectorIcon(finding) {
182+
const detector = finding.DetectorName?.toLowerCase() || "";
183+
184+
if (detector.includes("password") || detector.includes("secret")) {
185+
return "fa-key";
186+
} else if (detector.includes("token") || detector.includes("api")) {
187+
return "fa-key";
188+
} else if (detector.includes("aws")) {
189+
return "fab fa-aws";
190+
} else if (detector.includes("postgres") || detector.includes("sql")) {
191+
return "fa-database";
192+
} else if (detector.includes("github")) {
193+
return "fab fa-github";
194+
} else if (detector.includes("email") || detector.includes("mail")) {
195+
return "fa-envelope";
196+
} else {
197+
return "fa-shield-halved";
198+
}
199+
},
200+
201+
truncate(str, max = 30) {
202+
if (!str) return "N/A";
203+
return str.length > max ? str.substring(0, max) + "..." : str;
204+
},
205+
206+
// Summary statistics
207+
getTotalFindings() {
208+
return this.truffleHogData.length;
209+
},
210+
211+
getVerifiedCount() {
212+
return this.truffleHogData.filter((finding) => finding.Verified).length;
213+
},
214+
215+
getFailedCount() {
216+
return this.truffleHogData.filter((finding) => finding.VerificationError)
217+
.length;
218+
},
219+
220+
getNotVerifiedCount() {
221+
return this.truffleHogData.filter(
222+
(finding) => !finding.Verified && !finding.VerificationError,
223+
).length;
224+
},
225+
226+
getUniqueDetectors() {
227+
const detectors = this.truffleHogData.map(
228+
(finding) => finding.DetectorName,
229+
);
230+
return [...new Set(detectors)].length;
231+
},
232+
233+
getUniqueRepositories() {
234+
const repos = this.truffleHogData
235+
.filter(
236+
(finding) => finding.SourceMetadata?.Data?.[this.source]?.repository,
237+
)
238+
.map((finding) => finding.SourceMetadata.Data[this.source].repository);
239+
return [...new Set(repos)].length;
240+
},
241+
242+
renderTimestamp(timestamp) {
243+
return new Date(timestamp).toDateString();
244+
},
245+
generateReport() {
246+
// Show loading indicator
247+
this.isGeneratingReport = true;
248+
249+
// Prepare the data needed for the report
250+
const reportData = {
251+
stats: {
252+
totalFindings: this.getTotalFindings(),
253+
verifiedCount: this.getVerifiedCount(),
254+
failedCount: this.getFailedCount(),
255+
notVerifiedCount: this.getNotVerifiedCount(),
256+
uniqueDetectors: this.getUniqueDetectors(),
257+
uniqueRepositories: this.getUniqueRepositories(),
258+
},
259+
findings: this.displayedData,
260+
};
261+
262+
// Use the report service to generate the report
263+
ReportService.generatePDFReport(reportData, this.source);
264+
this.isGeneratingReport = false;
265+
},
266+
}));
267+
});

0 commit comments

Comments
 (0)