Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26c0a71292 | ||
|
|
41fcbfd9ca | ||
|
|
6d5079561a | ||
|
|
11f24766e5 | ||
|
|
f10ee66972 |
Submodule nostr_login_lite deleted from 3109a93163
@@ -418,6 +418,8 @@ small {
|
|||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
transition: filter 0.3s ease;
|
transition: filter 0.3s ease;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#thrower-banner:hover {
|
#thrower-banner:hover {
|
||||||
@@ -431,6 +433,8 @@ small {
|
|||||||
border: var(--border-width) solid var(--primary-color);
|
border: var(--border-width) solid var(--primary-color);
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
transition: filter 0.3s ease;
|
transition: filter 0.3s ease;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#thrower-icon:hover {
|
#thrower-icon:hover {
|
||||||
@@ -449,6 +453,8 @@ small {
|
|||||||
border: var(--border-width) solid var(--primary-color);
|
border: var(--border-width) solid var(--primary-color);
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
transition: filter 0.3s ease;
|
transition: filter 0.3s ease;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#profile-picture:hover {
|
#profile-picture:hover {
|
||||||
|
|||||||
@@ -542,7 +542,7 @@
|
|||||||
<div class="thrower-details-section collapsed" id="details-${index}">
|
<div class="thrower-details-section collapsed" id="details-${index}">
|
||||||
${thrower.icon ? `
|
${thrower.icon ? `
|
||||||
<div style="margin-bottom: 15px;">
|
<div style="margin-bottom: 15px;">
|
||||||
<img src="${thrower.icon}" style="width: 50px; height: 50px; border-radius: var(--border-radius); border: var(--border-width) solid var(--primary-color); filter: grayscale(100%); transition: filter 0.3s ease;" onmouseover="this.style.filter='grayscale(0%) saturate(50%)'" onmouseout="this.style.filter='grayscale(100%)'">
|
<img src="${thrower.icon}" style="width: 50px; height: 50px; border-radius: var(--border-radius); border: var(--border-width) solid var(--primary-color); filter: grayscale(100%); transition: filter 0.3s ease; object-fit: cover; object-position: center;" onmouseover="this.style.filter='grayscale(0%) saturate(50%)'" onmouseout="this.style.filter='grayscale(100%)'">
|
||||||
</div>
|
</div>
|
||||||
` : ''}
|
` : ''}
|
||||||
|
|
||||||
@@ -762,16 +762,17 @@
|
|||||||
select.removeChild(select.lastChild);
|
select.removeChild(select.lastChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add discovered throwers
|
// Add discovered throwers with real-time online status check
|
||||||
discoveredThrowers.forEach(thrower => {
|
discoveredThrowers.forEach(thrower => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = thrower.pubkey;
|
option.value = thrower.pubkey;
|
||||||
|
// Always check current online status when populating
|
||||||
const onlineStatus = isThrowerOnline(thrower) ? '🟢' : '🔴';
|
const onlineStatus = isThrowerOnline(thrower) ? '🟢' : '🔴';
|
||||||
option.textContent = `${onlineStatus} ${thrower.name} (${thrower.pubkey.substring(0, 8)}...)`;
|
option.textContent = `${onlineStatus} ${thrower.name} (${thrower.pubkey.substring(0, 8)}...)`;
|
||||||
select.appendChild(option);
|
select.appendChild(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('INFO', `Populated thrower dropdown for bounce ${bounceId} with ${discoveredThrowers.length} throwers`);
|
console.log('INFO', `Populated thrower dropdown for bounce ${bounceId} with ${discoveredThrowers.length} throwers (with current online status)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update all existing thrower dropdowns with current online status
|
// Update all existing thrower dropdowns with current online status
|
||||||
@@ -829,6 +830,35 @@
|
|||||||
input.value = '';
|
input.value = '';
|
||||||
clearRelayDropdown(bounceId);
|
clearRelayDropdown(bounceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if bounce inputs are valid and update button state
|
||||||
|
updateCreateBounceButtonState(bounceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bounce is ready to be created and update button state
|
||||||
|
function updateCreateBounceButtonState(bounceId) {
|
||||||
|
const button = document.getElementById(`create-bounce-btn-${bounceId}`);
|
||||||
|
if (!button) return;
|
||||||
|
|
||||||
|
const throwerPubkey = getThrowerPubkeyForBounce(bounceId);
|
||||||
|
const relayInput = document.getElementById(`bounce-relays-${bounceId}`);
|
||||||
|
const hasRelays = relayInput && relayInput.value.trim().length > 0;
|
||||||
|
|
||||||
|
const isValid = throwerPubkey && hasRelays;
|
||||||
|
|
||||||
|
if (isValid) {
|
||||||
|
// Enable button
|
||||||
|
button.disabled = false;
|
||||||
|
button.style.color = '';
|
||||||
|
button.style.borderColor = '';
|
||||||
|
button.style.cursor = '';
|
||||||
|
} else {
|
||||||
|
// Disable button
|
||||||
|
button.disabled = true;
|
||||||
|
button.style.color = 'var(--muted-color)';
|
||||||
|
button.style.borderColor = 'var(--muted-color)';
|
||||||
|
button.style.cursor = 'not-allowed';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle manual thrower pubkey input
|
// Handle manual thrower pubkey input
|
||||||
@@ -865,6 +895,9 @@
|
|||||||
} else {
|
} else {
|
||||||
clearRelayDropdown(bounceId);
|
clearRelayDropdown(bounceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update button state after thrower input change
|
||||||
|
updateCreateBounceButtonState(bounceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get thrower pubkey for a bounce (from dropdown or manual input)
|
// Get thrower pubkey for a bounce (from dropdown or manual input)
|
||||||
@@ -904,7 +937,7 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate relay dropdown with relays the selected thrower can read from
|
// Populate relay dropdown with relays the selected thrower can throw to (write to)
|
||||||
function populateRelayDropdown(bounceId, throwerPubkey) {
|
function populateRelayDropdown(bounceId, throwerPubkey) {
|
||||||
const relaySelect = document.getElementById(`relay-select-${bounceId}`);
|
const relaySelect = document.getElementById(`relay-select-${bounceId}`);
|
||||||
const relayInput = document.getElementById(`bounce-relays-${bounceId}`);
|
const relayInput = document.getElementById(`bounce-relays-${bounceId}`);
|
||||||
@@ -927,17 +960,15 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get relays the thrower can read from (read or both)
|
// Get relays the thrower can write to (write or both) - these are the relays it can throw to
|
||||||
const readableRelays = thrower.relayList.relays.filter(r => r.type === 'read' || r.type === 'both');
|
|
||||||
// Get relays the thrower can write to (write or both)
|
|
||||||
const writableRelays = thrower.relayList.relays.filter(r => r.type === 'write' || r.type === 'both');
|
const writableRelays = thrower.relayList.relays.filter(r => r.type === 'write' || r.type === 'both');
|
||||||
|
|
||||||
if (readableRelays.length === 0) {
|
if (writableRelays.length === 0) {
|
||||||
relaySelect.innerHTML = '<option value="">-- This thrower cannot read from any relays --</option><option value="__manual__">⊕ Add manually (enter relay URL)</option>';
|
relaySelect.innerHTML = '<option value="">-- This thrower cannot throw to any relays --</option><option value="__manual__">⊕ Add manually (enter relay URL)</option>';
|
||||||
// Show manual option since thrower has no readable relays
|
// Show manual option since thrower has no writable relays
|
||||||
const manualOption = relaySelect.querySelector('option[value="__manual__"]');
|
const manualOption = relaySelect.querySelector('option[value="__manual__"]');
|
||||||
manualOption.classList.remove('hidden');
|
manualOption.classList.remove('hidden');
|
||||||
console.log('WARN', `Thrower ${thrower.name} cannot read from any relays`);
|
console.log('WARN', `Thrower ${thrower.name} cannot throw to any relays`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -949,8 +980,8 @@
|
|||||||
relaySelect.appendChild(allRelaysOption);
|
relaySelect.appendChild(allRelaysOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add readable relays to dropdown
|
// Add writable relays to dropdown (these are the relays the thrower can throw to)
|
||||||
readableRelays.forEach(relay => {
|
writableRelays.forEach(relay => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = relay.url;
|
option.value = relay.url;
|
||||||
option.textContent = relay.url;
|
option.textContent = relay.url;
|
||||||
@@ -967,7 +998,7 @@
|
|||||||
relayInput.value = '';
|
relayInput.value = '';
|
||||||
relayManualDiv.classList.add('hidden');
|
relayManualDiv.classList.add('hidden');
|
||||||
|
|
||||||
console.log('INFO', `Populated relay dropdown for bounce ${bounceId} with ${readableRelays.length} readable relays and ${writableRelays.length} writable relays from ${thrower.name}`);
|
console.log('INFO', `Populated relay dropdown for bounce ${bounceId} with ${writableRelays.length} writable relays that ${thrower.name} can throw to`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear relay dropdown
|
// Clear relay dropdown
|
||||||
@@ -1023,6 +1054,9 @@
|
|||||||
manualDiv.classList.add('hidden');
|
manualDiv.classList.add('hidden');
|
||||||
input.value = '';
|
input.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update button state after relay selection change
|
||||||
|
updateCreateBounceButtonState(bounceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle manual relay input
|
// Handle manual relay input
|
||||||
@@ -1037,6 +1071,9 @@
|
|||||||
}
|
}
|
||||||
console.log('INFO', `Manual relay input for bounce ${bounceId}: ${input.value}`);
|
console.log('INFO', `Manual relay input for bounce ${bounceId}: ${input.value}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update button state after manual relay input change
|
||||||
|
updateCreateBounceButtonState(bounceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all writable relays for a thrower as comma-separated string
|
// Get all writable relays for a thrower as comma-separated string
|
||||||
@@ -1129,7 +1166,7 @@
|
|||||||
bounceSection.id = `bounce-${bounceId}`;
|
bounceSection.id = `bounce-${bounceId}`;
|
||||||
|
|
||||||
bounceSection.innerHTML = `
|
bounceSection.innerHTML = `
|
||||||
<h2 id="bounce-${bounceId}-header">Bounce ${bounceId} (Kind 22222 Routing Event)</h2>
|
<h2 id="bounce-${bounceId}-header">Add a bounce (Kind 22222 Routing Event)</h2>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="thrower-select-${bounceId}">Thrower:</label>
|
<label for="thrower-select-${bounceId}">Thrower:</label>
|
||||||
<select id="thrower-select-${bounceId}" onchange="onThrowerSelect(${bounceId})" style="width: 100%; margin-bottom: 10px;">
|
<select id="thrower-select-${bounceId}" onchange="onThrowerSelect(${bounceId})" style="width: 100%; margin-bottom: 10px;">
|
||||||
@@ -1142,7 +1179,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="relay-select-${bounceId}">Target Relay:</label>
|
<label for="relay-select-${bounceId}">Thrower throws to this relay(s):</label>
|
||||||
<select id="relay-select-${bounceId}" onchange="onRelaySelect(${bounceId})" style="width: 100%; margin-bottom: 10px;">
|
<select id="relay-select-${bounceId}" onchange="onRelaySelect(${bounceId})" style="width: 100%; margin-bottom: 10px;">
|
||||||
<option value="">-- Select a thrower first --</option>
|
<option value="">-- Select a thrower first --</option>
|
||||||
<option value="__manual__" class="hidden">⊕ Add manually (enter relay URL)</option>
|
<option value="__manual__" class="hidden">⊕ Add manually (enter relay URL)</option>
|
||||||
@@ -1173,7 +1210,7 @@
|
|||||||
<label for="audit-tag-${bounceId}">Audit Tag (auto-generated):</label>
|
<label for="audit-tag-${bounceId}">Audit Tag (auto-generated):</label>
|
||||||
<input type="text" id="audit-tag-${bounceId}" readonly style="background: #f5f5f5;">
|
<input type="text" id="audit-tag-${bounceId}" readonly style="background: #f5f5f5;">
|
||||||
</div>
|
</div>
|
||||||
<button onclick="createBounce(${bounceId})" id="create-bounce-btn-${bounceId}">Create Bounce ${bounceId}</button>
|
<button onclick="createBounce(${bounceId})" id="create-bounce-btn-${bounceId}" disabled style="color: var(--muted-color); border-color: var(--muted-color); cursor: not-allowed;">Create Bounce</button>
|
||||||
|
|
||||||
<div id="bounce-${bounceId}-display" class="json-display"></div>
|
<div id="bounce-${bounceId}-display" class="json-display"></div>
|
||||||
<div style="text-align: right; margin-top: 5px;">
|
<div style="text-align: right; margin-top: 5px;">
|
||||||
@@ -1204,36 +1241,10 @@
|
|||||||
// Populate thrower dropdown with discovered throwers
|
// Populate thrower dropdown with discovered throwers
|
||||||
populateThrowerDropdown(bounceId);
|
populateThrowerDropdown(bounceId);
|
||||||
|
|
||||||
// Update bounce labels to reflect execution order
|
// Labels are now generic, no need to update numbering
|
||||||
updateBounceLabels();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update bounce labels to reflect execution order (newest bounce is Bounce 1, oldest is Bounce N)
|
// Bounce labels are now generic - no numbering needed
|
||||||
function updateBounceLabels() {
|
|
||||||
// Get all existing bounce sections
|
|
||||||
const bounceContainer = document.getElementById('bounces-container');
|
|
||||||
const bounceSections = bounceContainer.querySelectorAll('.bounce-section');
|
|
||||||
|
|
||||||
// Update labels in reverse order (newest first gets Bounce 1)
|
|
||||||
bounceSections.forEach((section, index) => {
|
|
||||||
const bounceId = section.id.replace('bounce-', '');
|
|
||||||
const executionOrder = bounceSections.length - index; // Reverse the index
|
|
||||||
|
|
||||||
// Update the header
|
|
||||||
const header = document.getElementById(`bounce-${bounceId}-header`);
|
|
||||||
if (header) {
|
|
||||||
header.textContent = `Bounce ${executionOrder} (Kind 22222 Routing Event)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the create button text
|
|
||||||
const createBtn = document.getElementById(`create-bounce-btn-${bounceId}`);
|
|
||||||
if (createBtn) {
|
|
||||||
createBtn.textContent = `Create Bounce ${executionOrder}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('INFO: Updated bounce labels for execution order');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update all timeline absolute times continuously
|
// Update all timeline absolute times continuously
|
||||||
function updateAllTimelineTimes() {
|
function updateAllTimelineTimes() {
|
||||||
@@ -1432,8 +1443,7 @@
|
|||||||
// Show the Add Bounce button again now that this bounce is completed
|
// Show the Add Bounce button again now that this bounce is completed
|
||||||
document.getElementById('add-bounce-btn').classList.remove('hidden');
|
document.getElementById('add-bounce-btn').classList.remove('hidden');
|
||||||
|
|
||||||
// Update bounce labels after creation to reflect execution order
|
// Labels are generic, no update needed
|
||||||
updateBounceLabels();
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('ERROR', `Failed to create bounce ${bounceId}: ${error.message}`);
|
console.log('ERROR', `Failed to create bounce ${bounceId}: ${error.message}`);
|
||||||
@@ -1479,7 +1489,7 @@
|
|||||||
// Generate the "Throw the Superball" button HTML
|
// Generate the "Throw the Superball" button HTML
|
||||||
function generateThrowButtonHtml() {
|
function generateThrowButtonHtml() {
|
||||||
return `
|
return `
|
||||||
<div class="timeline-step throw-button" style="background: white; border: 4px solid #4a90e2; cursor: pointer;" onclick="throwSuperball()">
|
<div class="timeline-step throw-button align-left" style="background: white; border: 4px solid #4a90e2; cursor: pointer; width: 60%; margin-left: 0; margin-right: auto;" onclick="throwSuperball()">
|
||||||
<div class="step-time">
|
<div class="step-time">
|
||||||
<div class="step-time-relative">Ready</div>
|
<div class="step-time-relative">Ready</div>
|
||||||
<div class="step-time-absolute" style="font-size: 10px; color: #888;">Click to start</div>
|
<div class="step-time-absolute" style="font-size: 10px; color: #888;">Click to start</div>
|
||||||
@@ -1639,27 +1649,23 @@
|
|||||||
// Start with user sending the outermost bounce
|
// Start with user sending the outermost bounce
|
||||||
let currentTime = baseTime;
|
let currentTime = baseTime;
|
||||||
|
|
||||||
// Work forward through bounces (in order they were created)
|
// Work forward through bounces (in EXECUTION order - reverse of creation order)
|
||||||
bounces.forEach((bounce, index) => {
|
const reversedBounces = [...bounces].reverse();
|
||||||
const bounceNumber = index + 1;
|
reversedBounces.forEach((bounce, index) => {
|
||||||
const isFirst = (index === 0);
|
const bounceNumber = bounces.length - index; // Original bounce number
|
||||||
const isLast = (index === bounces.length - 1);
|
const isFirst = (index === 0); // First in execution (last created)
|
||||||
|
const isLast = (index === reversedBounces.length - 1); // Last in execution (first created)
|
||||||
const throwerName = getThrowerName(bounce, bounceNumber);
|
const throwerName = getThrowerName(bounce, bounceNumber);
|
||||||
|
|
||||||
if (isFirst) {
|
if (isFirst) {
|
||||||
// User sends the outermost routing event (first bounce created)
|
// For the first bounce, we skip the user publishing step since it's handled by the button
|
||||||
|
// Start directly with relay propagation after the button click
|
||||||
const routingEventSize = JSON.stringify(bounce.routingEvent).length;
|
const routingEventSize = JSON.stringify(bounce.routingEvent).length;
|
||||||
const relays = getRelaysForBounce(bounceNumber);
|
|
||||||
|
|
||||||
// Step 1: User sends to relay
|
// Get the relays from the current bounce's routing instructions (where user publishes to)
|
||||||
flow.push({
|
const relays = bounce.payload?.routing?.relays || [];
|
||||||
time: currentTime,
|
|
||||||
actor: userName,
|
|
||||||
action: `Publishes routing event`,
|
|
||||||
size: routingEventSize
|
|
||||||
});
|
|
||||||
|
|
||||||
// Step 2: Relay propagates (immediate)
|
// Step 1: Relay propagates (immediate after button click)
|
||||||
currentTime += 2000; // 2 seconds for relay propagation
|
currentTime += 2000; // 2 seconds for relay propagation
|
||||||
flow.push({
|
flow.push({
|
||||||
time: currentTime,
|
time: currentTime,
|
||||||
@@ -1679,7 +1685,10 @@
|
|||||||
if (isLast) {
|
if (isLast) {
|
||||||
// Last bounce - posts final event
|
// Last bounce - posts final event
|
||||||
const finalEventSize = JSON.stringify(finalEvent).length + paddingAdjustment;
|
const finalEventSize = JSON.stringify(finalEvent).length + paddingAdjustment;
|
||||||
const finalRelays = getRelaysForBounce(bounceNumber);
|
|
||||||
|
// Get the relays from the current bounce's routing instructions (where final event gets posted)
|
||||||
|
const finalRelays = bounce.payload?.routing?.relays || [];
|
||||||
|
|
||||||
const delaySeconds = getDelayForBounce(bounceNumber);
|
const delaySeconds = getDelayForBounce(bounceNumber);
|
||||||
const paddingAdded = getPaddingAdjustmentForBounce(bounceNumber);
|
const paddingAdded = getPaddingAdjustmentForBounce(bounceNumber);
|
||||||
|
|
||||||
@@ -1708,10 +1717,15 @@
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Intermediate bounce - forwards to next superball
|
// Intermediate bounce - forwards to next superball
|
||||||
const nextBounce = bounces[index + 1];
|
const nextBounce = reversedBounces[index + 1];
|
||||||
const nextThrowerName = getThrowerName(nextBounce, bounceNumber + 1);
|
const nextBounceNumber = bounces.length - (index + 1);
|
||||||
|
const nextThrowerName = getThrowerName(nextBounce, nextBounceNumber);
|
||||||
const nextRoutingSize = JSON.stringify(nextBounce.routingEvent).length + paddingAdjustment;
|
const nextRoutingSize = JSON.stringify(nextBounce.routingEvent).length + paddingAdjustment;
|
||||||
const nextRelays = getRelaysForBounce(bounceNumber + 1); // Next superball's relays
|
|
||||||
|
// Get the relays from the current bounce's routing instructions (where this thrower forwards to)
|
||||||
|
const currentBounce = reversedBounces[index];
|
||||||
|
const forwardingRelays = currentBounce.payload?.routing?.relays || [];
|
||||||
|
|
||||||
const delaySeconds = getDelayForBounce(bounceNumber);
|
const delaySeconds = getDelayForBounce(bounceNumber);
|
||||||
const paddingAdded = getPaddingAdjustmentForBounce(bounceNumber);
|
const paddingAdded = getPaddingAdjustmentForBounce(bounceNumber);
|
||||||
|
|
||||||
@@ -1720,7 +1734,7 @@
|
|||||||
if (paddingAdded > 0) {
|
if (paddingAdded > 0) {
|
||||||
actionDescription += `, adds ${paddingAdded} bytes of padding`;
|
actionDescription += `, adds ${paddingAdded} bytes of padding`;
|
||||||
}
|
}
|
||||||
actionDescription += `, and forwards to: ${nextRelays.join(', ')}`;
|
actionDescription += `, and forwards to: ${forwardingRelays.join(', ')}`;
|
||||||
|
|
||||||
// Step 3: Superball forwards to next relay
|
// Step 3: Superball forwards to next relay
|
||||||
flow.push({
|
flow.push({
|
||||||
@@ -1734,7 +1748,7 @@
|
|||||||
currentTime += 2000; // 2 seconds for relay propagation
|
currentTime += 2000; // 2 seconds for relay propagation
|
||||||
flow.push({
|
flow.push({
|
||||||
time: currentTime,
|
time: currentTime,
|
||||||
actor: `Relay (${nextRelays.join(', ')})`,
|
actor: `Relay (${forwardingRelays.join(', ')})`,
|
||||||
action: `Event available for ${nextThrowerName}`,
|
action: `Event available for ${nextThrowerName}`,
|
||||||
size: Math.max(nextRoutingSize, 0)
|
size: Math.max(nextRoutingSize, 0)
|
||||||
});
|
});
|
||||||
@@ -1746,8 +1760,16 @@
|
|||||||
|
|
||||||
// Generate HTML for timeline visualization
|
// Generate HTML for timeline visualization
|
||||||
function generateTimelineHtml(eventFlow) {
|
function generateTimelineHtml(eventFlow) {
|
||||||
return eventFlow.map((step, index) => `
|
return eventFlow.map((step, index) => {
|
||||||
<div class="timeline-step" id="timeline-step-${index}">
|
// Determine alignment based on actor type
|
||||||
|
const isRelay = step.actor.startsWith('Relay (');
|
||||||
|
const alignmentClass = isRelay ? 'align-right' : 'align-left';
|
||||||
|
const alignmentStyle = isRelay
|
||||||
|
? 'width: 60%; margin-left: auto; margin-right: 0;'
|
||||||
|
: 'width: 60%; margin-left: 0; margin-right: auto;';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="timeline-step ${alignmentClass}" id="timeline-step-${index}" style="${alignmentStyle}">
|
||||||
<div class="step-time">
|
<div class="step-time">
|
||||||
<div class="step-time-relative">${formatTime(step.time)}</div>
|
<div class="step-time-relative">${formatTime(step.time)}</div>
|
||||||
<div class="step-time-absolute" style="font-size: 10px; color: #888;">${formatAbsoluteTime(step.time)}</div>
|
<div class="step-time-absolute" style="font-size: 10px; color: #888;">${formatAbsoluteTime(step.time)}</div>
|
||||||
@@ -1756,7 +1778,8 @@
|
|||||||
<div class="step-action">${step.action}</div>
|
<div class="step-action">${step.action}</div>
|
||||||
<div class="step-size">${step.size} bytes</div>
|
<div class="step-size">${step.size} bytes</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`;
|
||||||
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format absolute time (HH:MM:SS)
|
// Format absolute time (HH:MM:SS)
|
||||||
@@ -1771,7 +1794,8 @@
|
|||||||
|
|
||||||
// Helper functions for bounce data extraction
|
// Helper functions for bounce data extraction
|
||||||
function getRelaysForBounce(bounceNumber) {
|
function getRelaysForBounce(bounceNumber) {
|
||||||
const bounceIndex = bounceNumber - 1;
|
// Convert execution order bounce number to array index (reverse lookup)
|
||||||
|
const bounceIndex = bounces.length - bounceNumber;
|
||||||
if (bounceIndex < 0 || bounceIndex >= bounces.length) return [];
|
if (bounceIndex < 0 || bounceIndex >= bounces.length) return [];
|
||||||
|
|
||||||
// Get relays from the bounce's routing instructions
|
// Get relays from the bounce's routing instructions
|
||||||
@@ -1783,7 +1807,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDelayForBounce(bounceNumber) {
|
function getDelayForBounce(bounceNumber) {
|
||||||
const bounceIndex = bounceNumber - 1;
|
// Convert execution order bounce number to array index (reverse lookup)
|
||||||
|
const bounceIndex = bounces.length - bounceNumber;
|
||||||
if (bounceIndex < 0 || bounceIndex >= bounces.length) return 30;
|
if (bounceIndex < 0 || bounceIndex >= bounces.length) return 30;
|
||||||
|
|
||||||
const bounce = bounces[bounceIndex];
|
const bounce = bounces[bounceIndex];
|
||||||
@@ -1794,7 +1819,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPaddingAdjustmentForBounce(bounceNumber) {
|
function getPaddingAdjustmentForBounce(bounceNumber) {
|
||||||
const bounceIndex = bounceNumber - 1;
|
// Convert execution order bounce number to array index (reverse lookup)
|
||||||
|
const bounceIndex = bounces.length - bounceNumber;
|
||||||
if (bounceIndex < 0 || bounceIndex >= bounces.length) return 0;
|
if (bounceIndex < 0 || bounceIndex >= bounces.length) return 0;
|
||||||
|
|
||||||
const bounce = bounces[bounceIndex];
|
const bounce = bounces[bounceIndex];
|
||||||
@@ -1858,6 +1884,11 @@
|
|||||||
existingViz.remove();
|
existingViz.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh thrower status after reset so new dropdowns have current availability
|
||||||
|
setTimeout(() => {
|
||||||
|
discoverThrowers();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
console.log('INFO: Builder reset successfully');
|
console.log('INFO: Builder reset successfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
<div><strong>Events in Queue:</strong> <span id="events-queued">0</span></div>
|
<div><strong>Events in Queue:</strong> <span id="events-queued">0</span></div>
|
||||||
<div><strong>Info Status:</strong> <span id="thrower-info-status">Loading...</span></div>
|
<div><strong>Info Status:</strong> <span id="thrower-info-status">Loading...</span></div>
|
||||||
<div><strong>Last Updated:</strong> <span id="thrower-info-updated">Never</span></div>
|
<div><strong>Last Updated:</strong> <span id="thrower-info-updated">Never</span></div>
|
||||||
<div><strong>Refresh Rate:</strong> <span id="thrower-info-refresh">60 seconds</span></div>
|
<div><strong>Refresh Rate:</strong> <span id="thrower-info-refresh">300 seconds</span></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label>Add New Relay:</label>
|
<label>Add New Relay:</label>
|
||||||
<div class="add-relay-form">
|
<div class="add-relay-form">
|
||||||
<input type="url" id="new-relay-url" placeholder="wss://relay.example.com">
|
<input type="url" id="new-relay-url" placeholder="wss://relay.example.com (or comma-separated list)">
|
||||||
<select id="new-relay-type">
|
<select id="new-relay-type">
|
||||||
<option value="">Both</option>
|
<option value="">Both</option>
|
||||||
<option value="read">Read</option>
|
<option value="read">Read</option>
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="edit-refresh-rate">Refresh Rate (seconds):</label>
|
<label for="edit-refresh-rate">Refresh Rate (seconds):</label>
|
||||||
<input type="number" id="edit-refresh-rate" placeholder="60" value="60" min="10" max="3600">
|
<input type="number" id="edit-refresh-rate" placeholder="300" value="300" min="10" max="3600">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="edit-thrower-content">Additional Content (optional):</label>
|
<label for="edit-thrower-content">Additional Content (optional):</label>
|
||||||
@@ -616,35 +616,95 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Add new relay
|
// Add new relay (supports comma-separated list)
|
||||||
function addRelay() {
|
function addRelay() {
|
||||||
const url = document.getElementById('new-relay-url').value.trim();
|
const input = document.getElementById('new-relay-url').value.trim();
|
||||||
const type = document.getElementById('new-relay-type').value;
|
const type = document.getElementById('new-relay-type').value;
|
||||||
|
|
||||||
if (!url) {
|
if (!input) {
|
||||||
showStatus('relay-status', 'Please enter a relay URL', 'error');
|
showStatus('relay-status', 'Please enter a relay URL', 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if input contains commas (multiple URLs)
|
||||||
|
const urls = input.includes(',') ?
|
||||||
|
input.split(',').map(url => url.trim()).filter(url => url.length > 0) :
|
||||||
|
[input];
|
||||||
|
|
||||||
|
const results = {
|
||||||
|
added: [],
|
||||||
|
failed: [],
|
||||||
|
duplicates: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Process each URL
|
||||||
|
urls.forEach(url => {
|
||||||
|
// Validate URL format
|
||||||
if (!url.startsWith('wss://') && !url.startsWith('ws://')) {
|
if (!url.startsWith('wss://') && !url.startsWith('ws://')) {
|
||||||
showStatus('relay-status', 'Relay URL must start with wss:// or ws://', 'error');
|
results.failed.push({ url, reason: 'Must start with wss:// or ws://' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicates
|
// Check for duplicates
|
||||||
if (currentRelays.some(r => r.url === url)) {
|
if (currentRelays.some(r => r.url === url)) {
|
||||||
showStatus('relay-status', 'Relay already exists', 'error');
|
results.duplicates.push(url);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentRelays.push({ url, type });
|
// Add relay
|
||||||
|
currentRelays.push({ url, type, authStatus: 'unknown', lastTested: null });
|
||||||
|
results.added.push(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update display
|
||||||
displayRelayList();
|
displayRelayList();
|
||||||
|
|
||||||
// Clear form
|
// Clear form
|
||||||
document.getElementById('new-relay-url').value = '';
|
document.getElementById('new-relay-url').value = '';
|
||||||
document.getElementById('new-relay-type').value = '';
|
document.getElementById('new-relay-type').value = '';
|
||||||
|
|
||||||
showStatus('relay-status', 'Relay added (remember to save)', 'info');
|
// Provide detailed feedback
|
||||||
|
const messages = [];
|
||||||
|
|
||||||
|
if (results.added.length > 0) {
|
||||||
|
const typeText = type ? ` (${type === 'read' ? 'Read only' : type === 'write' ? 'Write only' : 'Both'})` : ' (Both)';
|
||||||
|
if (results.added.length === 1) {
|
||||||
|
messages.push(`✅ Added: ${results.added[0]}${typeText}`);
|
||||||
|
} else {
|
||||||
|
messages.push(`✅ Added ${results.added.length} relays${typeText}:`);
|
||||||
|
results.added.forEach(url => messages.push(` • ${url}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.duplicates.length > 0) {
|
||||||
|
if (results.duplicates.length === 1) {
|
||||||
|
messages.push(`⚠️ Duplicate skipped: ${results.duplicates[0]}`);
|
||||||
|
} else {
|
||||||
|
messages.push(`⚠️ ${results.duplicates.length} duplicates skipped:`);
|
||||||
|
results.duplicates.forEach(url => messages.push(` • ${url}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.failed.length > 0) {
|
||||||
|
if (results.failed.length === 1) {
|
||||||
|
messages.push(`❌ Failed: ${results.failed[0].url} (${results.failed[0].reason})`);
|
||||||
|
} else {
|
||||||
|
messages.push(`❌ ${results.failed.length} failed:`);
|
||||||
|
results.failed.forEach(item => messages.push(` • ${item.url} (${item.reason})`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show comprehensive status
|
||||||
|
const statusMessage = messages.join('\n');
|
||||||
|
const statusType = results.added.length > 0 ? 'info' :
|
||||||
|
results.failed.length > 0 ? 'error' : 'info';
|
||||||
|
|
||||||
|
// Add reminder to save if any were added
|
||||||
|
const finalMessage = results.added.length > 0 ?
|
||||||
|
statusMessage + '\n\n💾 Remember to save your relay configuration!' :
|
||||||
|
statusMessage;
|
||||||
|
|
||||||
|
showStatus('relay-status', finalMessage, statusType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove relay
|
// Remove relay
|
||||||
@@ -1067,7 +1127,7 @@
|
|||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
privacyPolicy: '',
|
privacyPolicy: '',
|
||||||
termsOfService: '',
|
termsOfService: '',
|
||||||
refreshRate: 60,
|
refreshRate: 300,
|
||||||
content: event.content || ''
|
content: event.content || ''
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1084,7 +1144,7 @@
|
|||||||
else if (tag[0] === 'version') currentThrowerInfo.version = tag[1] || '1.0.0';
|
else if (tag[0] === 'version') currentThrowerInfo.version = tag[1] || '1.0.0';
|
||||||
else if (tag[0] === 'privacy_policy') currentThrowerInfo.privacyPolicy = tag[1] || '';
|
else if (tag[0] === 'privacy_policy') currentThrowerInfo.privacyPolicy = tag[1] || '';
|
||||||
else if (tag[0] === 'terms_of_service') currentThrowerInfo.termsOfService = tag[1] || '';
|
else if (tag[0] === 'terms_of_service') currentThrowerInfo.termsOfService = tag[1] || '';
|
||||||
else if (tag[0] === 'refresh_rate') currentThrowerInfo.refreshRate = parseInt(tag[1]) || 60;
|
else if (tag[0] === 'refresh_rate') currentThrowerInfo.refreshRate = parseInt(tag[1]) || 300;
|
||||||
});
|
});
|
||||||
|
|
||||||
lastThrowerInfoPublish = event.created_at;
|
lastThrowerInfoPublish = event.created_at;
|
||||||
@@ -1103,7 +1163,7 @@
|
|||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
privacyPolicy: '',
|
privacyPolicy: '',
|
||||||
termsOfService: '',
|
termsOfService: '',
|
||||||
refreshRate: 60,
|
refreshRate: 300,
|
||||||
content: ''
|
content: ''
|
||||||
};
|
};
|
||||||
displayThrowerInfo(currentThrowerInfo);
|
displayThrowerInfo(currentThrowerInfo);
|
||||||
@@ -1148,7 +1208,7 @@
|
|||||||
document.getElementById('edit-version').value = currentThrowerInfo.version || '1.0.0';
|
document.getElementById('edit-version').value = currentThrowerInfo.version || '1.0.0';
|
||||||
document.getElementById('edit-privacy-policy').value = currentThrowerInfo.privacyPolicy || '';
|
document.getElementById('edit-privacy-policy').value = currentThrowerInfo.privacyPolicy || '';
|
||||||
document.getElementById('edit-terms-service').value = currentThrowerInfo.termsOfService || '';
|
document.getElementById('edit-terms-service').value = currentThrowerInfo.termsOfService || '';
|
||||||
document.getElementById('edit-refresh-rate').value = currentThrowerInfo.refreshRate || 60;
|
document.getElementById('edit-refresh-rate').value = currentThrowerInfo.refreshRate || 300;
|
||||||
document.getElementById('edit-thrower-content').value = currentThrowerInfo.content || '';
|
document.getElementById('edit-thrower-content').value = currentThrowerInfo.content || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1172,7 +1232,7 @@
|
|||||||
const version = document.getElementById('edit-version').value.trim();
|
const version = document.getElementById('edit-version').value.trim();
|
||||||
const privacyPolicy = document.getElementById('edit-privacy-policy').value.trim();
|
const privacyPolicy = document.getElementById('edit-privacy-policy').value.trim();
|
||||||
const termsOfService = document.getElementById('edit-terms-service').value.trim();
|
const termsOfService = document.getElementById('edit-terms-service').value.trim();
|
||||||
const refreshRate = parseInt(document.getElementById('edit-refresh-rate').value) || 60;
|
const refreshRate = parseInt(document.getElementById('edit-refresh-rate').value) || 300;
|
||||||
const content = document.getElementById('edit-thrower-content').value.trim();
|
const content = document.getElementById('edit-thrower-content').value.trim();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -2053,8 +2113,8 @@
|
|||||||
item.status.charAt(0).toUpperCase() + item.status.slice(1);
|
item.status.charAt(0).toUpperCase() + item.status.slice(1);
|
||||||
|
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
|
<div style="font-size: 16px; font-weight: bold; margin-bottom: 8px; color: var(--accent-color);"><strong>Status:</strong> ${statusText}</div>
|
||||||
<div><strong>Event:</strong> ${item.id.substring(0, 32)}...</div>
|
<div><strong>Event:</strong> ${item.id.substring(0, 32)}...</div>
|
||||||
<div><strong>Status:</strong> ${statusText}</div>
|
|
||||||
<div><strong>Target Relays:</strong> ${item.routing.relays.length}</div>
|
<div><strong>Target Relays:</strong> ${item.routing.relays.length}</div>
|
||||||
<div><strong>Delay:</strong> ${item.routing.delay}s</div>
|
<div><strong>Delay:</strong> ${item.routing.delay}s</div>
|
||||||
${item.routing.padding ? `<div><strong>Padding:</strong> ${item.routing.padding}</div>` : ''}
|
${item.routing.padding ? `<div><strong>Padding:</strong> ${item.routing.padding}</div>` : ''}
|
||||||
|
|||||||
Reference in New Issue
Block a user