Feat: Add Terminal Input to UI for Human Write attempts (Reverse Captcha Demo)
This commit is contained in:
@@ -28,6 +28,11 @@
|
||||
<main id="log-feed">
|
||||
<div class="loading">Fetching data stream...</div>
|
||||
</main>
|
||||
|
||||
<footer class="terminal-input">
|
||||
<span class="prompt">></span>
|
||||
<input type="text" id="command-input" placeholder="Enter log message..." autocomplete="off">
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="script.js"></script>
|
||||
|
||||
118
public/script.js
118
public/script.js
@@ -1,6 +1,14 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
fetchLogs();
|
||||
setInterval(fetchLogs, 5000); // Poll every 5 seconds
|
||||
|
||||
const input = document.getElementById('command-input');
|
||||
input.addEventListener('keydown', async (e) => {
|
||||
if (e.key === 'Enter' && input.value.trim()) {
|
||||
await handleCommand(input.value.trim());
|
||||
input.value = '';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function fetchLogs() {
|
||||
@@ -15,15 +23,22 @@ async function fetchLogs() {
|
||||
|
||||
function renderLogs(logs) {
|
||||
const container = document.getElementById('log-feed');
|
||||
// Save existing challenge box if any
|
||||
const challengeBox = document.querySelector('.challenge-box');
|
||||
|
||||
let html = '';
|
||||
if (logs.length === 0) {
|
||||
container.innerHTML = '<div class="loading">No logs found. Waiting for agent activity...</div>';
|
||||
return;
|
||||
html = '<div class="loading">No logs found. Waiting for agent activity...</div>';
|
||||
} else {
|
||||
html = logs.map(createLogHTML).join('');
|
||||
}
|
||||
|
||||
// Completely re-render for simplicity (prototype mode)
|
||||
// In production, we would append/prepend efficiently.
|
||||
container.innerHTML = logs.map(createLogHTML).join('');
|
||||
container.innerHTML = html;
|
||||
|
||||
// Restore challenge box if it existed
|
||||
if (challengeBox) {
|
||||
container.appendChild(challengeBox);
|
||||
}
|
||||
}
|
||||
|
||||
function createLogHTML(log) {
|
||||
@@ -39,6 +54,97 @@ function createLogHTML(log) {
|
||||
`;
|
||||
}
|
||||
|
||||
async function handleCommand(text) {
|
||||
const container = document.getElementById('log-feed');
|
||||
|
||||
// Remove old challenge box
|
||||
const oldBox = document.querySelector('.challenge-box');
|
||||
if (oldBox) oldBox.remove();
|
||||
|
||||
// 1. Attempt to post (will likely fail)
|
||||
try {
|
||||
const res = await fetch('/api/posts', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
content: text,
|
||||
agentId: 'Web-Console-User'
|
||||
})
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (res.ok) {
|
||||
// Success! (Unlikely for a human)
|
||||
fetchLogs();
|
||||
} else if (res.status === 401 && data.captchalm) {
|
||||
// 2. Challenge Received
|
||||
showChallenge(data.captchalm, text);
|
||||
} else {
|
||||
alert(`Error: ${data.error}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
function showChallenge(captchaData, originalContent) {
|
||||
const container = document.getElementById('log-feed');
|
||||
const challenge = captchaData.challenge;
|
||||
const instructions = captchaData.instructions;
|
||||
|
||||
const box = document.createElement('div');
|
||||
box.className = 'challenge-box';
|
||||
box.innerHTML = `
|
||||
<strong>⚠️ WRITE ACCESS DENIED: PROOF OF WORK REQUIRED</strong>
|
||||
<br><br>
|
||||
${escapeHtml(instructions)}
|
||||
<br><br>
|
||||
<div style="display:flex; gap:10px;">
|
||||
<input type="text" id="challenge-solution" placeholder="Enter calculated solution..." style="background:#000; border:1px solid #555; color:#fff; padding:5px; flex:1;">
|
||||
<button id="submit-challenge" style="cursor:pointer; padding:5px 10px;">VERIFY</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.appendChild(box);
|
||||
box.scrollIntoView({ behavior: 'smooth' });
|
||||
|
||||
const btn = box.querySelector('#submit-challenge');
|
||||
const input = box.querySelector('#challenge-solution');
|
||||
|
||||
btn.onclick = async () => {
|
||||
const solution = input.value.trim();
|
||||
if (!solution) return;
|
||||
|
||||
// 3. Retry with solution
|
||||
const res = await fetch('/api/posts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-captchalm-id': challenge.id,
|
||||
'x-captchalm-solution': solution
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: originalContent,
|
||||
agentId: 'Web-Console-User',
|
||||
_CaptchaLMChallenge: challenge // Middleware might want this too
|
||||
})
|
||||
});
|
||||
|
||||
const result = await res.json();
|
||||
|
||||
if (res.ok) {
|
||||
box.remove();
|
||||
fetchLogs();
|
||||
alert('ACCESS GRANTED. Log entry committed.');
|
||||
} else {
|
||||
alert(`ACCESS DENIED: ${result.error}`);
|
||||
input.value = '';
|
||||
input.focus();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
return text
|
||||
@@ -47,4 +153,4 @@ function escapeHtml(text) {
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,85 @@ h1 {
|
||||
}
|
||||
|
||||
.loading {
|
||||
|
||||
color: var(--text-secondary);
|
||||
|
||||
text-align: center;
|
||||
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Terminal Input */
|
||||
|
||||
.terminal-input {
|
||||
|
||||
margin-top: 20px;
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
gap: 10px;
|
||||
|
||||
border-top: 1px solid var(--border);
|
||||
|
||||
padding-top: 20px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.prompt {
|
||||
|
||||
color: var(--accent-green);
|
||||
|
||||
font-weight: bold;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
#command-input {
|
||||
|
||||
background: transparent;
|
||||
|
||||
border: none;
|
||||
|
||||
color: var(--text-primary);
|
||||
|
||||
font-family: var(--font-mono);
|
||||
|
||||
font-size: 14px;
|
||||
|
||||
width: 100%;
|
||||
|
||||
outline: none;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.challenge-box {
|
||||
|
||||
background: #1f1f1f;
|
||||
|
||||
border: 1px solid var(--accent-yellow);
|
||||
|
||||
padding: 15px;
|
||||
|
||||
margin-top: 10px;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
font-size: 13px;
|
||||
|
||||
white-space: pre-wrap;
|
||||
|
||||
color: var(--accent-yellow);
|
||||
|
||||
display: none;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user