This page lists files in the current directory. You can view content, get download/execute commands for Wget, Curl, or PowerShell, or filter the list using wildcards (e.g., `*.sh`).
wget 'https://lists2.roe3.org/ECCM.html'
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Ethernet Cable Connection Manager</title>
<style>
:root {
--bg:#0f1115; --panel:#171a21; --ink:#e8eaf1; --muted:#a6adbb; --line:#262a33; --accent:#7cc4ff;
--portGapFull:9px; /* full-width (12 cols) */
--portGapHalf:8px; /* half-width (6 cols) */
--portW12:60px; --portW6:60px;
}
html,body{height:100%}
body{margin:0;background:var(--bg);color:var(--ink);font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif}
*,*::before,*::after{box-sizing:border-box}
header{padding:14px 18px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:12px;flex-wrap:wrap;background:linear-gradient(180deg,#12151b,#10131a)}
header h1{font-size:18px;margin:0;font-weight:650}
.badge{color:var(--muted);font-size:12px;border:1px solid var(--line);padding:2px 8px;border-radius:999px}
.wrap{position:relative;display:grid;grid-template-columns:340px 1fr;gap:16px;height:calc(100% - 62px)}
@media (max-width:1024px){.wrap{grid-template-columns:1fr;height:auto}}
aside,main{padding:16px}
aside{border-right:1px solid var(--line);background:var(--panel)}
main{overflow:auto;position:relative}
.card{background:#141821;border:1px solid var(--line);border-radius:10px;padding:12px;margin-bottom:12px}
.card h3{margin:0 0 8px 0;font-size:14px;font-weight:650}
label{display:block;font-size:12px;color:var(--muted);margin:8px 0 4px}
input[type=text],input[type=number],select{width:100%;max-width:100%;background:#0f131b;color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:8px 10px;outline:none}
button,.btn{background:#11151d;color:var(--ink);border:1px solid var(--line);padding:8px 10px;border-radius:8px;cursor:pointer;font-weight:600}
button:hover{border-color:#2a2f3b}
.btn-danger{border-color:#3b2222}
.muted{color:var(--muted)} .small{font-size:12px}
.mini{padding:4px 8px;font-size:12px;border-radius:6px}
/* Devices layout (two-column grid) */
.dev-rows{display:flex;flex-direction:column;gap:12px}
.dev-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
.device-wrap{display:block;width:100%}
.device-wrap.full{grid-column:1 / -1}
.device{background:#131722;border:1px solid var(--line);border-radius:12px;padding:12px;position:relative;width:100%}
.device-head{display:flex;align-items:baseline;justify-content:space-between;gap:8px}
.device-title{font-size:15px;font-weight:650;display:flex;align-items:center;gap:8px}
.swatch{width:12px;height:12px;border-radius:3px;border:1px solid #0006;display:inline-block}
.device-actions{display:flex;gap:6px;flex-wrap:wrap}
.meta{font-size:12px;color:var(--muted);margin-top:2px}
.meta .inline-controls{margin-left:8px;display:inline-flex;gap:6px}
/* Ports */
.ports-rows{display:flex;flex-direction:column;gap:8px;margin-top:10px}
.port-row{display:grid;gap:var(--portGapHalf)} /* default; overridden per row */
.port{position:relative;border:1px solid var(--line);background:#0f141d;border-radius:10px;padding:8px 6px 6px;cursor:pointer;text-align:center;user-select:none;transition:background .12s}
.port .num{font-size:12px}
.port .alias{font-size:11px;margin-top:2px;min-height:1.2em}
.port .peer{margin-top:4px;font-size:12px;font-weight:700;min-height:1.4em}
.port .tip{position:absolute;top:6px;right:6px;font-size:10px;color:#7a8191}
.port.selected{outline-offset:2px}
/* Connections table */
.grid-slim{width:100%;border-collapse:collapse;border:1px solid var(--line);border-radius:8px;overflow:hidden; table-layout: fixed;}
.grid-slim th,.grid-slim td{font-size:13px;text-align:left;padding:8px 10px;border-bottom:1px solid var(--line); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
.grid-slim th{background:#121722;color:var(--muted);font-weight:600}
.conn-row.highlight td{background:#0f1522}
/* Device name colouring in table */
.conn-row td.deviceA .devName,
.conn-row td.deviceB .devName { color:#fff; font-weight:400; }
.conn-row.highlight td.deviceA .devName,
.conn-row.highlight td.deviceB .devName { color:var(--devColor); font-weight:700; }
/* Colour picker */
.color-picker-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.swatch-lg{width:24px;height:24px;border-radius:6px;border:1px solid #0006;display:inline-block}
.color-btn{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;background:#0f141d;border:1px solid var(--line);border-radius:8px;cursor:pointer;font-weight:600;color:var(--ink)}
.color-btn:hover{border-color:#2a2f3b}
.palette-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.5);display:none;align-items:center;justify-content:center;z-index:50}
.palette{background:#141821;border:1px solid var(--line);border-radius:12px;padding:12px;min-width:280px;max-width:90vw}
.palette h4{margin:0 0 10px 0;font-size:14px}
.palette-grid{display:grid;grid-template-columns:repeat(6, 36px);gap:8px;justify-content:center}
.chip{width:36px;height:36px;border-radius:8px;border:1px solid rgba(0,0,0,.35);cursor:pointer}
.palette .actions{display:flex;justify-content:flex-end;margin-top:12px}
/* Layout modal */
.modal-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.5);display:none;align-items:center;justify-content:center;z-index:60}
.modal{background:#141821;border:1px solid var(--line);border-radius:12px;padding:14px;min-width:300px;max-width:90vw}
.modal h4{margin:0 0 10px 0;font-size:14px}
.modal .row{display:flex;gap:8px;align-items:center;margin:6px 0}
.modal label{margin:0;color:var(--ink);font-size:13px}
.modal select{background:#0f131b;color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:6px 10px}
.modal .actions{display:flex;gap:8px;justify-content:flex-end;margin-top:12px}
</style>
</head>
<body>
<header>
<h1>Ethernet Cable Connection Manager</h1>
<span class="badge">Offline • LocalStorage • Profiles • JSON import/export • Print</span>
<div style="margin-left:auto;display:flex;gap:8px"><button id="printSheet">Print layout</button></div>
</header>
<div class="wrap">
<aside>
<!-- Profiles -->
<div class="card">
<h3>Profiles</h3>
<label for="profileSelect">Active profile</label>
<select id="profileSelect"></select>
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:8px">
<button id="newProfileBtn">New</button>
<button id="renameProfileBtn">Rename</button>
<button id="duplicateProfileBtn">Duplicate</button>
<button id="deleteProfileBtn" class="btn-danger">Delete</button>
<!-- Export/Import near profile ops -->
<button id="exportProfileBtn" class="btn">Export</button>
<button id="importProfileBtn" class="btn">Import</button>
<input id="importProfileFile" type="file" accept=".json,application/json" style="display:none">
</div>
<div class="small muted" style="margin-top:6px">Profiles are separate layouts (e.g., different customers). Everything autosaves.</div>
</div>
<div class="card">
<h3>Add device</h3>
<label for="devName">Name</label>
<input id="devName" type="text" placeholder="e.g. Core Switch A" />
<div class="row">
<div>
<label for="devPorts">Ports</label>
<input id="devPorts" type="number" inputmode="numeric" min="1" max="512" value="24" />
</div>
</div>
<label>Colour</label>
<div class="color-picker-row">
<button type="button" class="color-btn" id="openPalette">Select colour</button>
<span id="devColorPreview" class="swatch swatch-lg" title="Selected colour"></span>
</div>
<div style="margin-top:10px;display:flex;gap:8px">
<button id="addBtn">Add device</button>
<button id="clearAll" class="btn-danger" title="Delete everything">Clear all</button>
</div>
<div class="small" style="margin-top:6px">
Click two free ports to connect • <strong>Unlink</strong> in Connections • <span class="muted">Alt-click</span> a port to set an alias
</div>
</div>
<!-- Backup/restore all profiles -->
<div class="card">
<h3>Backup / Restore (all profiles)</h3>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button id="backupAllBtn" class="btn">Backup all</button>
<button id="restoreAllBtn" class="btn">Restore all</button>
<input id="restoreAllFile" type="file" accept=".json,application/json" style="display:none">
</div>
<div class="small muted" style="margin-top:6px">Saves or restores every profile on this device.</div>
</div>
<div class="card">
<h3>Find connection</h3>
<input id="searchBox" type="text" placeholder="Filter by device or port…" />
<div class="small muted">Filters the table below.</div>
</div>
</aside>
<main>
<div class="card">
<h3>Devices</h3>
<div id="devRows" class="dev-rows"></div>
</div>
<div class="card">
<h3>Connections</h3>
<table class="grid-slim" id="connTable">
<thead><tr><th>#</th><th>Device A</th><th>Port</th><th>Alias</th><th>⇄</th><th>Device B</th><th>Port</th><th>Alias</th><th></th></tr></thead>
<tbody id="connBody"></tbody>
</table>
<div class="small muted">Click a row (or a connected port) to highlight both ends.</div>
</div>
</main>
</div>
<!-- Palette -->
<div class="palette-backdrop" id="paletteModal">
<div class="palette" role="dialog" aria-modal="true" aria-labelledby="paletteTitle">
<h4 id="paletteTitle">Select colour</h4>
<div class="palette-grid" id="paletteGrid"></div>
<div class="actions"><button type="button" id="paletteClose">Close</button></div>
</div>
</div>
<!-- Layout modal -->
<div class="modal-backdrop" id="layoutModal">
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="layoutTitle">
<h4 id="layoutTitle">Device layout options</h4>
<div class="row"><label style="min-width:110px">Device</label><span id="layoutDeviceName" class="small muted"></span></div>
<div class="row">
<label style="min-width:110px">Row width</label>
<select id="layoutFullRow">
<option value="auto">Auto (follow rules)</option>
<option value="full">Force full row</option>
</select>
</div>
<div class="row" id="optMidWrap">
<label style="min-width:110px">13–24 ports</label>
<select id="layoutMidWrap">
<option value="balanced">Balanced (½ + ½)</option>
<option value="twelve">12 + remainder</option>
</select>
</div>
<div class="row" id="optSmallWrap">
<label style="min-width:110px">≤12 ports</label>
<select id="layoutSmallWrap">
<option value="single">Single row</option>
<option value="split">Split into 2 rows</option>
</select>
</div>
<div class="row" id="optDualLink">
<label style="min-width:110px">Dual link</label>
<select id="layoutDualLink">
<option value="off">Normal</option>
<option value="on">Dual link</option>
</select>
</div>
<div class="actions">
<button type="button" id="layoutCancel">Cancel</button>
<button type="button" id="layoutSave">Save</button>
</div>
</div>
</div>
<script>
/* ========= PROFILES STORAGE ========= */
var STORE_KEY = 'ethcm_profiles_v1';
var OLD_SINGLE_KEY = 'ethcm_v12';
var defaultState = { devices: [], links: [], portAliases: {} };
function deepClone(o){ return JSON.parse(JSON.stringify(o)); }
function uid(){ return 'id_' + Math.random().toString(36).slice(2,10); }
var store = (function(){
try{
var raw = localStorage.getItem(STORE_KEY);
if(raw){
var parsed = JSON.parse(raw);
if(parsed && parsed.current && parsed.profiles) return parsed;
}
var oldRaw = localStorage.getItem(OLD_SINGLE_KEY);
if(oldRaw){
var one = JSON.parse(oldRaw);
normalizeState(one);
return { current:'Migrated', profiles:{ 'Migrated': one } };
}
}catch(e){}
return { current:'Default', profiles:{ 'Default': deepClone(defaultState) } };
})();
function normalizeState(st){
st.devices = Array.isArray(st.devices) ? st.devices : [];
st.links = Array.isArray(st.links) ? st.links : [];
st.portAliases = st.portAliases || {};
st.devices.forEach(function(d){
if (d.forceFullRow === undefined) d.forceFullRow = false;
if (d.midWrapMode === undefined) d.midWrapMode = 'balanced';
if (d.smallWrap === undefined) d.smallWrap = false; // <=12 split flag
if (d.dualLink === undefined) d.dualLink = false; // NEW
});
}
function saveStore(){ try{ localStorage.setItem(STORE_KEY, JSON.stringify(store)); }catch(e){} }
/* Active profile reference */
var state = store.profiles[store.current];
/* ========= DOM HELPERS ========= */
function $(s){ return document.querySelector(s); }
function $all(s){ return Array.prototype.slice.call(document.querySelectorAll(s)); }
function el(tag, attrs){
var node = document.createElement(tag); attrs = attrs||{};
Object.keys(attrs).forEach(function(k){
var v = attrs[k];
if(k === 'dataset'){ Object.keys(v).forEach(function(dk){ node.dataset[dk] = v[dk]; }); }
else if(k === 'class'){ node.className = v; }
else if(k.indexOf('on') === 0 && typeof v === 'function'){ node.addEventListener(k.slice(2), v); }
else { node.setAttribute(k, v); }
});
for(var i=2;i<arguments.length;i++){ var c = arguments[i]; if(c==null) continue; node.appendChild(c.nodeType ? c : document.createTextNode(c)); }
return node;
}
function deviceById(id){ for(var i=0;i<state.devices.length;i++){ if(state.devices[i].id===id) return state.devices[i]; } return null; }
function indexById(id){ return state.devices.findIndex(function(d){return d.id===id;}); }
function linkForPort(deviceId, port, sub){
for(var i=0;i<state.links.length;i++){
var L = state.links[i];
if((L.a.deviceId===deviceId && L.a.port===port && (L.a.sub||null)=== (sub||null)) ||
(L.b.deviceId===deviceId && L.b.port===port && (L.b.sub||null)=== (sub||null))) return L;
}
return null;
}
function keyFor(deviceId, port, sub){
return deviceId + ':' + port + ':' + (sub==null ? '' : sub);
}
function aliasFor(deviceId, port, sub){
return state.portAliases[keyFor(deviceId,port,sub)] || '';
}
function getPeer(deviceId, port, sub){
var L = linkForPort(deviceId, port, sub); if(!L) return null;
if (L.a.deviceId===deviceId && L.a.port===port && (L.a.sub||null)===(sub||null)) return L.b;
return L.a;
}
/* ========= COLOUR / SELECTION ========= */
function hexToRgb(hex){ var h = String(hex||'').replace('#','').trim(); if (h.length===3) h = h.split('').map(function(c){return c+c}).join(''); var n = parseInt(h,16); if (isNaN(n)||h.length!==6) return {r:15,g:20,b:29}; return { r:(n>>16)&255, g:(n>>8)&255, b:n&255 }; }
function bestTextColorFor(bgHex){ var c = hexToRgb(bgHex); var L = 0.2126*(c.r/255) + 0.7152*(c.g/255) + 0.0722*(c.b/255); return (L > 0.6) ? '#000' : '#fff'; }
function paintPortBase(node, bgHex){
var bg = bgHex || '#0f141d';
node.style.background = bg;
var txt = bestTextColorFor(bg);
node.style.color = txt;
var alias = node.querySelector('.alias'); if (alias) alias.style.color = txt;
var num = node.querySelector('.num'); if (num) num.style.color = txt;
var peer = node.querySelector('.peer'); if (peer) peer.style.color = txt;
}
function clearSelectionOutlines(){ $all('.port.selected').forEach(function(n){ n.classList.remove('selected'); n.style.outline='none'; }); }
function outlineForSelection(node, thick){ var bg = node.style.background || '#0f141d'; var border = bestTextColorFor(bg); node.classList.add('selected'); node.style.outline = (thick?'5px':'3px')+' solid '+border; }
/* ========= UNIFORM PORT WIDTHS (two gaps) ========= */
function updateUniformPortWidth(){
var host = document.getElementById('devRows'); if(!host) return;
var GAP_COL = 12; /* .dev-row column gap */
var PAD_BRD = 26; /* .device L/R padding+border */
var gapFull = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--portGapFull')) || 9;
var gapHalf = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--portGapHalf')) || 8;
var Wrows = host.getBoundingClientRect().width || 800;
var WfullInner = Wrows - PAD_BRD; // 12 cols -> 11 gaps
var WhalfInner = ((Wrows - GAP_COL)/2) - PAD_BRD; // 6 cols -> 5 gaps
var portW12 = (WfullInner - (11*gapFull)) / 12;
var portW6 = (WhalfInner - (5*gapHalf)) / 6;
portW12 = Math.max(40, portW12);
portW6 = Math.max(40, portW6);
document.documentElement.style.setProperty('--portW12', portW12 + 'px');
document.documentElement.style.setProperty('--portW6', portW6 + 'px');
}
var __pw_timer=null;
window.addEventListener('resize', function(){ clearTimeout(__pw_timer); __pw_timer=setTimeout(updateUniformPortWidth, 60); });
/* ========= LAYOUT RULES ========= */
function isFullWidthDevice(d){
if (d.forceFullRow) return true;
if ((d.ports||1) >= 13) return true; // 13+ always full
if ((d.ports||1) >= 7 && !d.smallWrap) return true; // 7–12 full only if NOT split
return false; // 1–6 or 7–12 split → half
}
function splitPortsIntoRows(n, dev){
if (n >= 12){
if (n >= 13 && n <= 24 && dev.midWrapMode === 'balanced'){
var a = Math.ceil(n/2); return [Math.min(12,a), Math.min(12,n-a)];
}
var rows=[], rem=n; while(rem>0){ rows.push(Math.min(12, rem)); rem -= 12; } return rows;
}
if (n >= 7){
if (dev.smallWrap){
var a = Math.ceil(n/2);
return [a, n - a]; // e.g., 9 → 5+4, 12 → 6+6
}
return [n]; // default single row
}
return [n];
}
/* ========= CONNECT / UNLINK ========= */
var pendingPort = null;
var highlightedLinkId = null;
function connectPorts(a,b){
if (linkForPort(a.deviceId,a.port) || linkForPort(b.deviceId,b.port)) return false;
var id = uid(); state.links.push({ id:id, a:a, b:b }); saveStore(); render(); highlightLink(id); return true;
}
function unlink(linkId){
state.links = state.links.filter(function(L){ return L.id!==linkId; });
if(highlightedLinkId===linkId) highlightedLinkId=null;
saveStore(); render();
}
/* ========= RENDER DEVICES ========= */
function renderDevices(){
var host = $('#devRows'); host.innerHTML='';
if(!state.devices.length){ host.appendChild(el('div',{class:'muted'},'No devices yet — add one on the left.')); updateUniformPortWidth(); return; }
var i=0;
while(i < state.devices.length){
var rowEl = el('div',{class:'dev-row'});
var d = state.devices[i];
if (isFullWidthDevice(d)){
rowEl.appendChild(renderDeviceWrap(d, true)); i += 1;
} else {
rowEl.appendChild(renderDeviceWrap(d, false));
if (i+1 < state.devices.length && !isFullWidthDevice(state.devices[i+1])){
rowEl.appendChild(renderDeviceWrap(state.devices[i+1], false)); i += 2;
} else {
rowEl.appendChild(el('div',{class:'device-wrap'}, el('div',{class:'device', style:'visibility:hidden'}, ' '))); i += 1;
}
}
host.appendChild(rowEl);
}
updateUniformPortWidth();
}
function renderDeviceWrap(d, fullWidth){
var wrap = el('div',{class:'device-wrap ' + (fullWidth ? 'full' : ''), dataset:{id:d.id}});
wrap.draggable = true;
wrap.addEventListener('dragstart', function(e){
wrap.classList.add('dragging');
e.dataTransfer.setData('text/plain', d.id);
e.dataTransfer.effectAllowed='move';
});
wrap.addEventListener('dragend', function(){ wrap.classList.remove('dragging'); });
wrap.addEventListener('dragover', function(e){ e.preventDefault(); e.dataTransfer.dropEffect='move'; });
wrap.addEventListener('drop', function(e){
e.preventDefault();
var draggedId = e.dataTransfer.getData('text/plain'); if (!draggedId || draggedId === d.id) return;
var from = indexById(draggedId), to = indexById(d.id); if (from<0 || to<0) return;
var item = state.devices.splice(from,1)[0]; if (from < to) to -= 1; state.devices.splice(to,0,item);
saveStore(); render();
});
var head = el('div',{class:'device-head'},
el('div',{class:'device-title'},
el('span',{class:'swatch', style:'background:'+(d.color||'#888')}),
document.createTextNode(d.name||'Unnamed')
),
el('div',{class:'device-actions'},
el('button',{title:'Layout options', onclick:function(ev){ ev.stopPropagation(); openLayoutModal(d.id); }}, 'Layout'),
el('button',{title:'Edit', onclick:function(ev){
ev.stopPropagation();
var newName = prompt('Device name:', d.name||''); if(newName==null || !newName.trim()) return;
var np = prompt('Number of ports (>=1):', String(d.ports||1)); if(np==null) return;
changePorts(d, Math.max(1, Math.min(512, Number(np)||1)));
d.name = newName.trim();
var newColor = prompt('Hex colour (e.g. #E74C3C):', d.color||'#888'); if(newColor) d.color = newColor;
saveStore(); render();
}}, 'Edit'),
el('button',{class:'btn-danger',title:'Delete', onclick:function(ev){
ev.stopPropagation();
if(!confirm('Delete "'+(d.name||'Unnamed')+'" and its links?')) return;
state.links = state.links.filter(function(L){
return String(L.a.deviceId)!==String(d.id) && String(L.b.deviceId)!==String(d.id);
});
state.devices = state.devices.filter(function(x){ return String(x.id)!==String(d.id); });
Object.keys(state.portAliases).forEach(function(k){
if(k.split(':')[0]===String(d.id)) delete state.portAliases[k];
});
saveStore(); render();
}}, 'Delete')
)
);
var meta = el('div',{class:'meta'},
document.createTextNode((d.ports||1)+' ports'),
(function(){
var c = el('span',{class:'inline-controls'},
el('button',{class:'mini', title:'Add one port', onclick:function(e){ e.stopPropagation(); changePorts(d, (d.ports||1)+1); saveStore(); render(); }}, '+ Port'),
el('button',{class:'mini', title:'Remove one port', onclick:function(e){ e.stopPropagation(); if ((d.ports||1) <= 1) return; changePorts(d, (d.ports||1)-1); saveStore(); render(); }}, '– Port')
);
return c;
})()
);
var portsContainer = el('div',{class:'ports-rows'});
var rows = splitPortsIntoRows(d.ports||1, d);
var nextPortNum = 1;
// --- Helper for rendering a single port (normal or dual-link sub-port) ---
function renderPort(d, p, sub){
var linked = !!linkForPort(d.id, p, sub);
var peer = linked ? getPeer(d.id,p,sub) : null;
var peerDev = peer ? deviceById(peer.deviceId) : null;
var peerPort = peer ? peer.port : null;
var alias = aliasFor(d.id,p,sub);
var node = el('div',{class:'port'+(linked?' connected':''), dataset:{deviceId:d.id, port:p, sub:(sub==null?'':sub)}},
el('div',{class:'tip'}, linked?'⇄':'+'),
el('div',{class:'num'}, 'Port '+p+(sub!=null ? '/'+(sub+1) : '')),
el('div',{class:'alias'}, alias||''),
el('div',{class:'peer'}, peerPort?String(peerPort):'')
);
if (linked && peerDev) paintPortBase(node, peerDev.color || '#22354a');
else paintPortBase(node, '#0f141d');
node.addEventListener('click', function(ev){
var isAlt = ev.altKey;
if (isAlt){
var curr = aliasFor(d.id,p,sub);
var next = prompt('Optional port label (blank to clear):', curr||'');
if(next===null) return;
var k = keyFor(d.id,p,sub);
if(!next.trim()) delete state.portAliases[k]; else state.portAliases[k] = next.trim();
saveStore(); render(); return;
}
if (linkForPort(d.id, p, sub)) {
clearSelectionOutlines();
var L1 = linkForPort(d.id, p, sub);
highlightLink(L1.id, { from:{deviceId:d.id, port:p, sub:sub} });
pendingPort = null; return;
}
if (!pendingPort){
pendingPort = {deviceId:d.id, port:p, sub:sub};
clearSelectionOutlines(); outlineForSelection(node, true);
highlightedLinkId = null; applyRowHighlight();
return;
}
if (linkForPort(d.id, p, sub) || linkForPort(pendingPort.deviceId, pendingPort.port, pendingPort.sub)){
alert('Already linked. Unlink first.');
pendingPort = null; clearSelectionOutlines(); highlightLink(null); return;
}
var devA = deviceById(pendingPort.deviceId);
var devB = deviceById(d.id);
var msg = 'Create link:\n' + (devA?devA.name:'A') + ' Port ' + pendingPort.port + (pendingPort.sub!=null?'/'+(pendingPort.sub+1):'')
+ ' ⇄ ' + (devB?devB.name:'B') + ' Port ' + p + (sub!=null?'/'+(sub+1):'');
if (!confirm(msg)){ pendingPort = null; clearSelectionOutlines(); return; }
var target = {deviceId:d.id, port:p, sub:sub};
var ok = connectPorts(pendingPort, target);
pendingPort = null;
if (!ok){ alert('Could not connect.'); clearSelectionOutlines(); highlightLink(null); }
});
return node;
}
// --- end helper ---
rows.forEach(function(count){
var row = el('div',{class:'port-row'});
row.style.gridTemplateColumns = fullWidth ? 'repeat(12, var(--portW12))' : 'repeat(6, var(--portW6))';
row.style.gap = fullWidth ? 'var(--portGapFull)' : 'var(--portGapHalf)';
for (var i=0;i<count;i++){
var p = nextPortNum++;
if (d.dualLink){
for (var sub=0; sub<2; sub++){
row.appendChild(renderPort(d, p, sub));
}
} else {
row.appendChild(renderPort(d, p, null));
}
}
portsContainer.appendChild(row);
});
var card = el('div',{class:'device', id:'dev-'+d.id}, head, meta, portsContainer);
wrap.appendChild(card);
return wrap;
}
function changePorts(d, newCount){
newCount = Math.max(1, Math.min(512, Number(newCount)||1));
if (newCount === d.ports) return;
if (newCount < d.ports){
state.links = state.links.filter(function(L){
var aOk = !(L.a.deviceId===d.id && L.a.port>newCount);
var bOk = !(L.b.deviceId===d.id && L.b.port>newCount);
return aOk && bOk;
});
Object.keys(state.portAliases).forEach(function(k){
var parts = k.split(':'); if(parts[0]===d.id && Number(parts[1])>newCount) delete state.portAliases[k];
});
}
d.ports = newCount;
}
/* ========= CONNECTIONS TABLE ========= */
function renderConnections(){
var tb = $('#connBody'); tb.innerHTML='';
var q = ($('#searchBox').value||'').toLowerCase().trim();
var rows = [];
state.links.forEach(function(L, i){
var da = deviceById(L.a.deviceId), db = deviceById(L.b.deviceId);
var aName = (da&&da.name)||'Unknown', bName = (db&&db.name)||'Unknown';
var aAlias = aliasFor(L.a.deviceId,L.a.port), bAlias = aliasFor(L.b.deviceId,L.b.port);
// Flexible search terms for ports (4 / port4 / port 4)
var parts = [
aName, 'port'+L.a.port, 'port '+L.a.port, String(L.a.port),
bName, 'port'+L.b.port, 'port '+L.b.port, String(L.b.port),
aAlias, bAlias
];
var text = parts.join(' ').toLowerCase();
if(q && text.indexOf(q)===-1) return;
var aColor = da ? (da.color || '#ccc') : '#ccc';
var bColor = db ? (db.color || '#ccc') : '#ccc';
var tr = el('tr',{class:'conn-row', dataset:{linkId:L.id}},
el('td',{}, String(i+1)),
el('td',{class:'deviceA'}, el('span',{class:'devName', style:'--devColor:'+aColor, title:aName}, aName)),
el('td',{}, 'Port '+L.a.port),
el('td',{}, aAlias),
el('td',{}, '⇄'),
el('td',{class:'deviceB'}, el('span',{class:'devName', style:'--devColor:'+bColor, title:bName}, bName)),
el('td',{}, 'Port '+L.b.port),
el('td',{}, bAlias),
el('td',{}, el('button',{class:'btn-danger',onclick:function(){ unlink(L.id); }},'Unlink'))
);
// Toggle select/deselect on row click
tr.addEventListener('click', function(e){
if(e.target.tagName.toLowerCase()==='button') return;
if (highlightedLinkId === L.id){ highlightLink(null); clearSelectionOutlines(); return; }
highlightLink(L.id);
var portA = document.querySelector('.port[data-device-id="'+L.a.deviceId+'"][data-port="'+L.a.port+'"]');
var portB = document.querySelector('.port[data-device-id="'+L.b.deviceId+'"][data-port="'+L.b.port+'"]');
clearSelectionOutlines(); if(portA) outlineForSelection(portA); if(portB) outlineForSelection(portB);
});
rows.push(tr);
});
if(!rows.length){
tb.appendChild(el('tr',{}, el('td',{colspan:'9',class:'muted'},'No connections found.')));
} else rows.forEach(function(r){ tb.appendChild(r); });
applyRowHighlight();
}
function applyRowHighlight(){
$all('.conn-row').forEach(function(r){
if(highlightedLinkId && r.dataset.linkId===highlightedLinkId) r.classList.add('highlight');
else r.classList.remove('highlight');
});
}
function highlightLink(linkId, opts){
highlightedLinkId = linkId; applyRowHighlight(); clearSelectionOutlines();
if (!linkId) return;
var L = state.links.find(function(x){ return x.id===linkId; }); if (!L) return;
var portA = document.querySelector('.port[data-device-id="'+L.a.deviceId+'"][data-port="'+L.a.port+'"]');
var portB = document.querySelector('.port[data-device-id="'+L.b.deviceId+'"][data-port="'+L.b.port+'"]');
if (portA) outlineForSelection(portA);
if (portB) outlineForSelection(portB);
if (opts && opts.from){
var originSel = '.port[data-device-id="'+opts.from.deviceId+'"][data-port="'+opts.from.port+'"]';
var origin = document.querySelector(originSel);
if (origin){ var border = bestTextColorFor(origin.style.background || '#0f141d'); origin.style.outline = '5px solid ' + border; }
}
}
/* ========= PRINT ========= */
function openPrintSheet(includeTable){
var devicesHTML = document.getElementById('devRows').innerHTML;
var tableEl = document.getElementById('connTable');
var tableHTML = (includeTable && tableEl) ? tableEl.outerHTML : '';
var when = new Date().toLocaleString();
var css = ''
+ ':root{--ink:#111;--line:#ddd;--portGapFull:9px;--portGapHalf:8px;--portW12:60px;--portW6:60px}\n'
+ '*{box-sizing:border-box}\n'
+ 'body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;color:var(--ink);background:#fff;margin:16px}\n'
+ 'header{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:12px}\n'
+ 'h1{font-size:18px;margin:0}\n'
+ '.meta{font-size:12px;color:#555}\n'
+ '.dev-rows{display:flex;flex-direction:column;gap:12px}\n'
+ '.dev-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}\n'
+ '.device-wrap{width:100%}\n'
+ '.device-wrap.full{grid-column:1 / -1}\n'
+ '.device{background:#fff;border:1px solid var(--line);border-radius:10px;padding:12px}\n'
+ '.device-head{display:flex;align-items:baseline;justify-content:space-between;gap:8px}\n'
+ '.device-title{font-size:14px;font-weight:700;display:flex;align-items:center;gap:8px}\n'
+ '.device-actions{display:none!important}\n'
+ '.inline-controls{display:none!important}\n'
+ '.swatch{width:12px;height:12px;border-radius:3px;border:1px solid #0003;display:inline-block}\n'
+ '.ports-rows{display:flex;flex-direction:column;gap:8px;margin-top:10px}\n'
+ '.port-row{display:grid;gap:var(--portGapHalf)}\n'
+ '.port{border:1px solid var(--line);border-radius:8px;padding:6px 6px 4px;text-align:center}\n'
+ '.port .tip{display:none}\n'
+ '.port .num{font-size:12px;font-weight:600}\n'
+ '.port .alias{font-size:11px;margin-top:2px;min-height:1.1em}\n'
+ '.port .peer{font-size:12px;font-weight:700;margin-top:4px;min-height:1.2em}\n'
+ 'table{width:100%;border-collapse:collapse;margin-top:16px}\n'
+ 'th,td{border:1px solid var(--line);padding:6px 8px;font-size:12px;text-align:left}\n'
+ 'th{background:#f6f7f9;font-weight:700}\n'
+ 'th:last-child, td:last-child{display:none}\n'
+ '@media print{.controls{display:none}body{margin:0;padding:16px}}\n';
var html = ''
+ '<!doctype html><html><head><meta charset="utf-8">'
+ '<title>Port Map – Print</title><style>' + css + '</style></head><body>'
+ '<header><h1>Ethernet Port Map</h1><div class="meta">' + when + '</div></header>'
+ '<section id="printDevices" class="dev-rows">' + devicesHTML + '</section>'
+ (tableHTML ? '<h2 style="font-size:16px;margin:18px 0 8px">Connections</h2>' + tableHTML : '')
+ '<div class="controls" style="margin-top:12px"><button onclick="window.print()">Print</button></div>'
+ '<script>(function(){'
+ 'var host=document.getElementById("printDevices");'
+ 'var Wrows=(host&&host.getBoundingClientRect?host.getBoundingClientRect().width:800)||800;'
+ 'var GAP_COL=12, PAD_BRD=26; var gapFull=9, gapHalf=8;'
+ 'var WfullInner=Wrows - PAD_BRD;'
+ 'var WhalfInner=(Wrows - GAP_COL)/2 - PAD_BRD;'
+ 'var portW12=(WfullInner - (11*gapFull))/12;'
+ 'var portW6 =(WhalfInner - (5*gapHalf))/6;'
+ 'portW12=Math.max(40,portW12); portW6=Math.max(40,portW6);'
+ 'document.documentElement.style.setProperty("--portW12", portW12+"px");'
+ 'document.documentElement.style.setProperty("--portW6", portW6 +"px");'
+ 'document.documentElement.style.setProperty("--portGapFull", gapFull+"px");'
+ 'document.documentElement.style.setProperty("--portGapHalf", gapHalf+"px");'
+ 'var rows=document.querySelectorAll(".port-row");'
+ 'for(var i=0;i<rows.length;i++){var row=rows[i];var wrap=row.closest(".device-wrap");var isFull=wrap&&wrap.classList.contains("full");'
+ 'row.style.gridTemplateColumns=isFull? "repeat(12, var(--portW12))" : "repeat(6, var(--portW6))";'
+ 'row.style.gap=isFull? "var(--portGapFull)" : "var(--portGapHalf)";}'
+ 'Array.prototype.forEach.call(document.querySelectorAll("[draggable]"),function(el){el.removeAttribute("draggable");});'
+ '})();<\/script>'
+ '</body></html>';
var win = window.open('', '_blank'); win.document.open(); win.document.write(html); win.document.close();
}
/* ========= ORCHESTRATION ========= */
function render(){ renderDevices(); renderConnections(); }
/* ========= PALETTE ========= */
(function(){
var palette = [
'#ff3b30','#ff9500','#ffcc00','#34c759','#30d158','#5ac8fa','#007aff','#5856d6','#af52de',
'#ff2d55','#ff6b6b','#ffd166','#06d6a0','#4cd964','#64d2ff','#0a84ff','#5e5ce6','#bf5af2',
'#e74c3c','#e67e22','#f1c40f','#2ecc71','#1abc9c','#3498db','#2d7cf6','#9b59b6','#8e44ad',
'#d0021b','#f5a623','#f8e71c','#7ed321','#50e3c2','#4a90e2','#007aff','#9013fe','#b8e986'
];
var modal = document.getElementById('paletteModal');
var grid = document.getElementById('paletteGrid');
var openBtn = document.getElementById('openPalette');
var closeBtn = document.getElementById('paletteClose');
var previewSidebar = document.getElementById('devColorPreview');
var chosenColor = '#E74C3C';
function open(){ modal.style.display='flex'; }
function close(){ modal.style.display='none'; }
function updatePreview(){ previewSidebar.style.background = chosenColor; }
grid.innerHTML = '';
palette.forEach(function(hex){
var chip = document.createElement('button');
chip.className = 'chip'; chip.style.background = hex; chip.title = hex;
chip.addEventListener('click', function(){ chosenColor = hex; updatePreview(); close(); });
grid.appendChild(chip);
});
openBtn.addEventListener('click', open); closeBtn.addEventListener('click', close); updatePreview();
document.getElementById('addBtn').addEventListener('click', function(){
var name = document.getElementById('devName').value.trim();
var ports = Math.max(1, Math.min(512, Number(document.getElementById('devPorts').value)||1));
if(!name){ alert('Please enter a device name.'); return; }
state.devices.push({ id:uid(), name:name, ports:ports, color:chosenColor, forceFullRow:false, midWrapMode:'balanced', smallWrap:false });
document.getElementById('devName').value=''; saveStore(); render();
});
})();
</script>
<script>
/* ========= LAYOUT MODAL ========= */
function openLayoutModal(deviceId){
var d = deviceById(deviceId); if (!d) return;
var backdrop = $('#layoutModal');
var nameEl = $('#layoutDeviceName');
var selFull = $('#layoutFullRow');
var selMid = $('#layoutMidWrap');
var selSmall = $('#layoutSmallWrap');
var rowMid = $('#optMidWrap');
var rowSmall = $('#optSmallWrap');
var selDual = $('#layoutDualLink');
nameEl.textContent = d.name + ' ('+d.ports+' ports)';
selFull.value = d.forceFullRow ? 'full' : 'auto';
selMid.value = (d.midWrapMode === 'twelve') ? 'twelve' : 'balanced';
selSmall.value = d.smallWrap ? 'split' : 'single';
selDual.value = d.dualLink ? 'on' : 'off';
// Show/hide rows according to port count
rowMid.style.display = (d.ports>=13 && d.ports<=24) ? 'flex' : 'none';
rowSmall.style.display = (d.ports>=7 && d.ports<=12) ? 'flex' : 'none';
$('#layoutCancel').onclick = function(){ backdrop.style.display='none'; };
$('#layoutSave').onclick = function(){
d.forceFullRow = (selFull.value === 'full');
d.dualLink = (selDual.value === 'on');
if (d.ports>=13 && d.ports<=24) d.midWrapMode = (selMid.value === 'twelve') ? 'twelve' : 'balanced';
if (d.ports>=7 && d.ports<=12) d.smallWrap = (selSmall.value === 'split');
saveStore(); backdrop.style.display='none'; render();
};
backdrop.style.display = 'flex';
}
/* ========= PROFILES UI (combined refresh & select) ========= */
function refreshProfileSelect(){
var sel = document.getElementById('profileSelect');
sel.innerHTML = '';
Object.keys(store.profiles).forEach(function(name){
var opt = document.createElement('option');
opt.value = name; opt.textContent = name;
if (name === store.current) opt.selected = true;
sel.appendChild(opt);
});
sel.value = store.current; // ensure visible selection
}
function switchProfile(name){
if (!store.profiles[name]) return;
store.current = name;
state = store.profiles[name];
normalizeState(state);
saveStore();
refreshProfileSelect(); // rebuild & select
render();
}
document.getElementById('profileSelect').addEventListener('change', function(){ switchProfile(this.value); });
document.getElementById('newProfileBtn').addEventListener('click', function(){
var name = prompt('New profile name:', 'Customer '+(Object.keys(store.profiles).length+1));
if(!name) return; name = name.trim(); if(!name) return;
if(store.profiles[name]){ alert('A profile with that name already exists.'); return; }
store.profiles[name] = deepClone(defaultState); saveStore();
refreshProfileSelect(); switchProfile(name);
});
document.getElementById('renameProfileBtn').addEventListener('click', function(){
var current = store.current;
var name = prompt('Rename profile "'+current+'" to:', current);
if(!name || !name.trim()) return; name = name.trim();
if(name === current) return;
if(store.profiles[name]){ alert('A profile with that name already exists.'); return; }
store.profiles[name] = state; delete store.profiles[current];
store.current = name; saveStore(); refreshProfileSelect(); render();
});
document.getElementById('duplicateProfileBtn').addEventListener('click', function(){
var base = store.current;
var name = prompt('Duplicate profile as:', base+' (copy)');
if(!name || !name.trim()) return; name = name.trim();
if(store.profiles[name]){ alert('A profile with that name already exists.'); return; }
store.profiles[name] = deepClone(state); saveStore();
refreshProfileSelect(); switchProfile(name);
});
document.getElementById('deleteProfileBtn').addEventListener('click', function(){
var names = Object.keys(store.profiles);
if(names.length<=1){ alert('You must keep at least one profile.'); return; }
var current = store.current;
if(!confirm('Delete profile "'+current+'"? This cannot be undone.')) return;
delete store.profiles[current];
var next = Object.keys(store.profiles)[0];
store.current = next; state = store.profiles[next];
saveStore(); refreshProfileSelect(); render();
});
/* Export profile (includes profileName) */
document.getElementById('exportProfileBtn').addEventListener('click', function(){
var data = { profileName: store.current, devices: state.devices, links: state.links, portAliases: state.portAliases, exportedAt: new Date().toISOString() };
var blob = new Blob([JSON.stringify(data,null,2)], {type:'application/json'});
var url = URL.createObjectURL(blob); var a = document.createElement('a');
a.href = url; a.download = 'ethernet-profile-' + store.current.replace(/[^a-z0-9-_]+/gi,'_') + '.json';
document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
});
/* Import profile (suggest name, switch active, dropdown syncs) */
document.getElementById('importProfileBtn').addEventListener('click', function(){
document.getElementById('importProfileFile').click();
});
document.getElementById('importProfileFile').addEventListener('change', function(e){
var file = e.target.files && e.target.files[0]; if(!file) return;
var reader = new FileReader();
reader.onload = function(){
try{
var parsed = JSON.parse(reader.result);
if(!Array.isArray(parsed.devices) || !Array.isArray(parsed.links)) throw new Error('Invalid schema');
parsed.portAliases = parsed.portAliases || {};
var devices = parsed.devices.map(function(d){
return {
id: d.id || uid(),
name: String(d.name || 'Unnamed'),
ports: Math.max(1, Math.min(512, Number(d.ports)||1)),
color: d.color || '#888',
forceFullRow: !!d.forceFullRow,
midWrapMode: (d.midWrapMode === 'twelve' ? 'twelve' : 'balanced'),
smallWrap: !!d.smallWrap
};
});
var idSet = Object.create(null); devices.forEach(function(d){ idSet[d.id]=true; });
var links = (parsed.links || []).map(function(L){
return { id: L.id || uid(), a:{deviceId:String(L.a.deviceId), port:Number(L.a.port)}, b:{deviceId:String(L.b.deviceId), port:Number(L.b.port)} };
}).filter(function(L){ return idSet[L.a.deviceId] && idSet[L.b.deviceId] && L.a.port>=1 && L.b.port>=1; });
var suggested = (parsed.profileName || '').toString().trim() || ('Imported ' + new Date().toLocaleString());
var name = prompt('Name for imported profile:', suggested);
if(!name || !name.trim()) return; name = name.trim();
if(store.profiles[name]){ alert('A profile with that name already exists.'); return; }
store.profiles[name] = { devices: devices, links: links, portAliases: parsed.portAliases };
saveStore();
refreshProfileSelect(); // show it in the dropdown
switchProfile(name); // switch + re-render + dropdown select
}catch(err){ alert('Import failed: ' + err.message); }
finally{ e.target.value=''; }
};
reader.readAsText(file);
});
/* ========= BACKUP / RESTORE (all profiles) ========= */
document.getElementById('backupAllBtn').addEventListener('click', function(){
var blob = new Blob([JSON.stringify(store,null,2)], {type:'application/json'});
var url = URL.createObjectURL(blob); var a = document.createElement('a');
a.href = url; a.download = 'ethernet-all-profiles-backup.json';
document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url);
});
document.getElementById('restoreAllBtn').addEventListener('click', function(){
document.getElementById('restoreAllFile').click();
});
document.getElementById('restoreAllFile').addEventListener('change', function(e){
var file = e.target.files && e.target.files[0]; if(!file) return;
var reader = new FileReader();
reader.onload = function(){
try{
var parsed = JSON.parse(reader.result);
if(!parsed || !parsed.current || !parsed.profiles) throw new Error('Invalid backup file (missing profiles).');
Object.keys(parsed.profiles).forEach(function(name){ normalizeState(parsed.profiles[name]); });
store = parsed; state = store.profiles[store.current] || deepClone(defaultState);
saveStore(); refreshProfileSelect(); render();
}catch(err){ alert('Restore failed: ' + err.message); }
finally{ e.target.value=''; }
};
reader.readAsText(file);
});
/* ========= SEARCH + PRINT ========= */
document.getElementById('searchBox').addEventListener('input', renderConnections);
document.getElementById('printSheet').addEventListener('click', function(){ openPrintSheet(true); });
/* ========= CLEAR ALL (current profile only) ========= */
document.getElementById('clearAll').addEventListener('click', function(){
if(!confirm('Delete ALL devices and connections in profile "'+store.current+'"?')) return;
store.profiles[store.current] = deepClone(defaultState);
state = store.profiles[store.current];
saveStore(); render();
});
/* ========= GLOBAL BLANK-SPACE CLICK: CLEAR SELECTION ========= */
document.addEventListener('click', function(e){
if (
e.target.closest('.port') ||
e.target.closest('.conn-row') ||
e.target.closest('.palette-backdrop') ||
e.target.closest('.modal-backdrop') ||
e.target.closest('.device-actions') ||
e.target.closest('button, input, select, label')
){ return; }
pendingPort = null; highlightLink(null); clearSelectionOutlines();
}, true);
/* ========= INIT ========= */
function initProfilesUI(){ refreshProfileSelect(); }
initProfilesUI();
render();
updateUniformPortWidth();
</script>
</body>
</html>
wget 'https://lists2.roe3.org/_index.html'
<!--DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"-->
<HTML>
<HEAD><TITLE>Under construction</TITLE></HEAD>
<BODY BGCOLOR="#FFFFFF"><H1>This web site is under construction</H1></BODY>
</HTML>
wget 'https://lists2.roe3.org/adminer-psql.php'
wget 'https://lists2.roe3.org/admsnippets.zip'
wget 'https://lists2.roe3.org/check-email.php'
<!DOCTYPE html>
<html>
<head>
<title>Check Email Validity</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css" integrity="sha384-X38yfunGUhNzHpBaEBsWLO+A0HDYOQi8ufWDkZ0k9e0eXz/tH3II7uKZ9msv++Ls" crossorigin="anonymous">
<link
rel="stylesheet"
href="//fonts.googleapis.com/css?family=Arial,Roboto,San-Serif"
type="text/css"
/>
<style>
body {
margin-top: 2em;
margin-left: 2em;
}
fieldset {
padding: 3em;
width: fit-content;
}
legend {
font-size: 16pt;
}
</style>
</head>
<body>
<fieldset>
<legend><b>Check Email Address Validity</b></legend>
<form action="" method="POST">
<label>Email Address:</label>
<input type="text" name="email" length="70">
<input type="submit">
</form>
<?php
//$email = "mdrone@roe3.org";
$email = $_POST["email"];
echo "<h2>Method 1: Using filter_var()</h2>";
if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo "✅ Valid email: $email";
} else {
echo "❌ Invalid email: $email";
}
echo "<h2>Method 2: Using Regular Expressions</h2>";
if (preg_match("/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/", $email)) {
echo "✅ Valid email: $email";
} else {
echo "❌ Invalid email: $email";
}
echo "<h2>Method 3: Checking MX Records</h2>";
list($user, $domain) = explode('@', $email);
if (checkdnsrr($domain, "MX")) {
echo "✅ Domain accepts emails: $domain";
} else {
echo "❌ Domain does not accept emails: $domain";
}
?>
</fieldset>
</body>
</html>
wget 'https://lists2.roe3.org/feedsmith.html'
<html>
<script type="module">
import { generateRssFeed } from 'https://cdn.jsdelivr.net/npm/feedsmith@latest/dist/index.js'
const rss = generateRssFeed({
title: 'My Feed',
link: 'https://lists2.roe3.org/mdrone/',
description: 'A test',
items: [{
title: 'My Feed',
link: 'https://lists2.roe3.org/mdrone/anniversary/',
description: 'Swornes Anniversary',
pubDate: new Date()
}]
})
console.log(rss) // Complete RSS XML
</script>
</html>
wget 'https://lists2.roe3.org/guppy60020.zip'
wget 'https://lists2.roe3.org/mailarchiva.service.txt'
[Unit]
Description=MailArchiva
After=network.target
RequiresMountsFor=/var/opt/mailarchiva /opt/mailarchiva /var/log/mailarchiva
[Service]
Environment="JAVA_OPTS=-Djava.awt.headless=true"
# Lifecycle
Type=forking
WorkingDirectory=/opt/mailarchiva/server/
PIDFile=/opt/mailarchiva/mailarchiva.pid
ExecStart=/bin/sh /opt/mailarchiva/server/startserver
ExecStop=/bin/sh /opt/mailarchiva/server/stopserver
SuccessExitStatus=143
TimeoutStopSec=10m
Environment=RACK_ENV=production
OOMScoreAdjust=-800
# Logging
SyslogIdentifier=mailarchiva
# Security
User=root
ReadWritePaths=/var/opt/mailarchiva/
ReadWritePaths=/opt/mailarchiva/
ReadWritePaths=/var/log/mailarchiva/
# Auto restart
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
wget 'https://lists2.roe3.org/phpinfo.php'
<?php
echo phpversion();
phpinfo();
?>
wget 'https://lists2.roe3.org/swapshop.zip'