Skip to content

Commit b14a162

Browse files
committed
Add single submission view
1 parent 8425475 commit b14a162

File tree

3 files changed

+123
-44
lines changed

3 files changed

+123
-44
lines changed

functions/secure/api/form.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export async function onRequest(context) {
1616
return row;
1717
});
1818

19-
return Response.json(submissionsQuery.results);
19+
return Response.json(rows);
2020
}
2121

2222
// TODO: this is bad practice, consider replacing (see https://www.reddit.com/r/learnjavascript/comments/qgtut6/comment/hi8jg6w/)
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export async function onRequest(context) {
2+
if (context.request.method !== "GET") {
3+
return new Response("Invalid request method", { status: 405 });
4+
}
5+
6+
const submissionsQuery = await context.env.DB_FORMS.prepare("SELECT * FROM submissions WHERE submission_id = ? ORDER BY submitted_ts DESC")
7+
.bind(
8+
context.params.submission,
9+
)
10+
.all();
11+
const rows = submissionsQuery
12+
.results
13+
.map((row) => {
14+
row.fields = JSON.parse(row.fields);
15+
row.spam_reasons = JSON.parse(row.spam_reasons);
16+
row.cf = JSON.parse(row.cf);
17+
row.headers = JSON.parse(row.headers);
18+
return row;
19+
});
20+
21+
if (rows.length === 0) {
22+
return new Response("Submission not found", { status: 404 });
23+
} else if (rows.length === 1) {
24+
return Response.json(rows);
25+
} else {
26+
return new Response(`Multiple submissions found for ID "${context.params.submission}"`, { status: 500 });
27+
}
28+
}

secure/form-submissions.md

+94-43
Original file line numberDiff line numberDiff line change
@@ -3,58 +3,109 @@ layout: default
33
title: Form Submissions
44
---
55

6-
This table shows form submissions over the last 30 days:
7-
<div id="form-submissions"><div class="lds-dual-ring"></div></div>
6+
<div id="description"></div>
7+
<div id="content"><div class="lds-dual-ring"></div></div>
88

99
<script>
10-
fetch("/secure/api/form", {
11-
method: "GET",
12-
headers: {
13-
"accept": "application/json",
14-
"content-type": "application/json",
15-
},
16-
})
17-
.then(res => res.json())
18-
.then(submissions => {
19-
const table = document.createElement("table");
20-
const thead = table.createTHead();
21-
const headings = thead.insertRow();
22-
[
23-
"Submission ID",
24-
"Form ID",
25-
"Submitted",
26-
"Spam Reasons",
27-
"IP Address",
28-
"ASN",
29-
].forEach(heading => {
30-
const th = document.createElement("th");
31-
th.innerHTML = heading;
32-
headings.appendChild(th);
33-
});
34-
const tbody = table.createTBody();
35-
submissions.forEach(row => {
36-
const tr = tbody.insertRow();
10+
const description = document.getElementById("description");
11+
const content = document.getElementById("content");
12+
if (window.location.hash) {
13+
displaySubmission(window.location.hash);
14+
} else {
15+
displaySubmissions();
16+
}
17+
18+
function displaySubmission(id) {
19+
description.innerHTML = `<h2>Form submission ${id}`;
20+
content.innerHTML = '<div class="lds-dual-ring"></div>';
21+
fetch(`/secure/api/form/${id}`, {
22+
method: "GET",
23+
headers: {
24+
"accept": "application/json",
25+
"content-type": "application/json",
26+
},
27+
})
28+
.then(res => res.json())
29+
.then(submission => {
30+
const submitted = new Date(submission.submitted_ts);
31+
content.innerHTML = `
32+
<h3>Form ID</h3>
33+
<p>${submission.form_id}</p>
34+
<h3>Submitted</h3>
35+
<p>${submitted.toLocaleDateString()} ${submitted.toLocaleTimeString()}</p>
36+
`;
37+
if (submission.reply_email) {
38+
content.innerHTML += `
39+
<h3>Email address (reply-to)</h3>
40+
<p>${submission.reply_email}</p>
41+
`;
42+
}
43+
if (submission.spam_reasons.length) {
44+
content.innerHTML += "<h3>Spam reasons</h3><ul>";
45+
submission.spam_reasons.forEach(reason => {
46+
content.innerHTML += `<li>${reason}</li>`;
47+
})
48+
content.innerHTML += "</ul>";
49+
}
50+
window.location.hash = id;
51+
})
52+
}
53+
54+
function displaySubmissions() {
55+
description.innerHTML = "<p>This table shows form submissions over the last 30 days:</p>";
56+
content.innerHTML = '<div class="lds-dual-ring"></div>';
57+
fetch("/secure/api/form", {
58+
method: "GET",
59+
headers: {
60+
"accept": "application/json",
61+
"content-type": "application/json",
62+
},
63+
})
64+
.then(res => res.json())
65+
.then(submissions => {
66+
const table = document.createElement("table");
67+
const thead = table.createTHead();
68+
const headings = thead.insertRow();
69+
[
70+
"Submission ID",
71+
"Form ID",
72+
"Submitted",
73+
"Spam Reasons",
74+
"IP Address",
75+
"ASN",
76+
].forEach(heading => {
77+
const th = document.createElement("th");
78+
th.innerHTML = heading;
79+
headings.appendChild(th);
80+
});
81+
const tbody = table.createTBody();
82+
submissions.forEach(row => {
83+
const tr = tbody.insertRow();
3784

38-
const tdSubmissionId = tr.insertCell();
39-
tdSubmissionId.innerHTML = row.submission_id;
85+
const tdSubmissionId = tr.insertCell();
86+
const id = row.submission_id;
87+
tdSubmissionId.innerHTML = `<a href="javascript:displaySubmission('${id}')">${id}</a>`;
4088

41-
const tdFormId = tr.insertCell();
42-
tdFormId.innerHTML = row.form_id;
89+
const tdFormId = tr.insertCell();
90+
tdFormId.innerHTML = row.form_id;
4391

44-
const tdSubmitted = tr.insertCell();
45-
tdSubmitted.innerHTML = row.submitted_ts;
92+
const tdSubmitted = tr.insertCell();
93+
const submitted = new Date(row.submitted_ts);
94+
tdSubmitted.innerHTML = `${submitted.toLocaleDateString()} ${submitted.toLocaleTimeString()}`;
4695

47-
const tdSpamReasons = tr.insertCell();
48-
tdSpamReasons.innerHTML = row.spam_reasons.join([separator = ', ']);
96+
const tdSpamReasons = tr.insertCell();
97+
tdSpamReasons.innerHTML = row.spam_reasons.join([separator = ', ']);
4998

50-
const tdIpAddress = tr.insertCell();
51-
tdIpAddress.innerHTML = `${getFlagEmoji(row.country)} <a href="https://cleantalk.org/blacklists/${row.ip}">${row.ip}</a>`;
99+
const tdIpAddress = tr.insertCell();
100+
tdIpAddress.innerHTML = `${getFlagEmoji(row.country)} <a href="https://cleantalk.org/blacklists/${row.ip}">${row.ip}</a>`;
52101

53-
const tdAsn = tr.insertCell();
54-
tdAsn.innerHTML = `<a href="https://bgp.tools/as/${row.asn}">AS${row.asn}</a>`;
102+
const tdAsn = tr.insertCell();
103+
const asn = row.asn;
104+
tdAsn.innerHTML = `<a href="https://bgp.tools/as/${asn}">AS${asn}</a>`;
105+
});
106+
content.innerHTML = table.outerHTML;
55107
});
56-
document.getElementById("form-submissions").innerHTML = table.outerHTML;
57-
});
108+
}
58109

59110
// https://www.bqst.fr/country-code-to-flag-emoji/
60111
const getFlagEmoji = countryCode=>String.fromCodePoint(...[...countryCode.toUpperCase()].map(x=>0x1f1a5+x.charCodeAt()));

0 commit comments

Comments
 (0)