This commit is contained in:
lavafroth
2026-02-03 11:39:14 +00:00
parent f597d318ce
commit 8c11df6084
7 changed files with 47 additions and 34 deletions

View File

@@ -23,15 +23,16 @@
⧉ Copy
'><meta name=author content><link rel="preload stylesheet" as=style href=https://lavafroth.is-a.dev/app.min.css><link rel=preload as=image href=../header.svg><link as=font href=https://lavafroth.is-a.dev/latinmodern-math.otf><link rel=preload as=image href=https://lavafroth.is-a.dev/github.svg><link rel=preload as=image href=https://lavafroth.is-a.dev/about.svg><link rel=preload as=image href=https://lavafroth.is-a.dev/art.svg><link rel=preload as=image href=https://lavafroth.is-a.dev/rss.svg><link rel=icon href=https://lavafroth.is-a.dev/favicon.png><link rel=blog-icon href=https://lavafroth.is-a.dev/icon.png></head><body><header><a class=site-name href=https://lavafroth.is-a.dev/><svg viewBox="0 0 8790 2080"><path d="M80 1935V465h216v1270h286v2e2zm853 0 222-1470h264l222 1470h-210l-40-3e2h-208l-40 3e2zm280-528h148l-62-494-6-78h-12l-6 78zm1025 528L2014 465h210l108 868 8 142h12l8-142 108-868h210l-224 1470zm813 0 222-1470h264l222 1470h-210l-40-3e2h-208l-40 3e2zm280-528h148l-62-494-6-78h-12l-6 78zm851 528V465h514v222h-298v386h2e2v222h-2e2v640zm910 0V465h216q194 0 286 108 92 107 92 316 0 124-43 215-44 90-106 132l147 699h-216l-122-620h-38v620zm216-820q60 0 95-26 35-27 50-76t15-116q0-105-34-161-35-57-126-57zm1084 836q-90 0-154-42-65-42-99-114-35-72-35-162V767q0-91 35-162 34-72 99-114 64-42 154-42t155 42q64 42 99 114 34 72 34 162v866q0 90-34 162-35 72-99 114-65 42-155 42zm0-210q40 0 56-33 16-34 16-75V767q0-41-17-74-17-34-55-34-37 0-54 34-18 33-18 74v866q0 41 17 75 17 33 55 33zm890 194V687h-204V465h624v222h-204v1248zm828 0V465h216v608h168V465h216v1470h-216v-640h-168v640z"/></svg></a><nav><a style=--url:url(./github.svg) href=https://github.com/lavafroth aria-label=github target=_blank></a><a href=../about/ aria-label=about style=--url:url(./about.svg)></a><a href=../art/ aria-label=art style=--url:url(./art.svg)></a><a href=../index.xml aria-label=rss style=--url:url(./rss.svg)></a><nav></header><main><hgroup data-pagefind-body><p data-pagefind-ignore><time>Jan 1, 1 | 1 minute read</time></p><h1 data-pagefind-meta=title></h1></hgroup><section class=post-content data-pagefind-body><!doctype html><html lang=en><head><meta charset=UTF-8><title>Easy SSH tunnel</title><meta name=viewport content="width=device-width,initial-scale=1"><meta name=description content><style>*{font-family:monospace}body{background:oklch(29.3% .136 325.661)}input{outline:none;color:#eee;border:none;background:oklch(29.3% .136 325.661);&:invalid { background:oklch(40.8% 0.153 2.432); }}.post-content>div{margin:1rem auto;label:not([for=direction]) { padding:.5rem; background:lab(78.5378% 39.3533 -32.9615); color:oklch(29.3% 0.136 325.661); }}.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));grid-template-rows:repeat(3,minmax(0,1fr));justify-content:space-between;align-items:center; *{ text-align: center; } label[for=direction] { user-select: none; grid-row: span 2 / span 2; grid-column-start: 2; color: #eee; &::after { display: block; content: '<'; } } input[type="checkbox"] { opacity: 0; position: absolute; pointer-events: none; } &:has(input:checked) { label[for=direction]::after { content: '>'; } }}#remotehost{grid-column:span 2/span 2}.post-content:has(input:invalid){ #command { display: none; }}#command{background:oklch(45.2% .211 324.591);margin-top:1rem;padding:.5rem;color:#eee}</style></head><body><div class=grid><label for=localport>Local port</label>
'><meta name=author content><link rel="preload stylesheet" as=style href=https://lavafroth.is-a.dev/app.min.css><link rel=preload as=image href=../header.svg><link as=font href=https://lavafroth.is-a.dev/latinmodern-math.otf><link rel=preload as=image href=https://lavafroth.is-a.dev/github.svg><link rel=preload as=image href=https://lavafroth.is-a.dev/about.svg><link rel=preload as=image href=https://lavafroth.is-a.dev/art.svg><link rel=preload as=image href=https://lavafroth.is-a.dev/rss.svg><link rel=icon href=https://lavafroth.is-a.dev/favicon.png><link rel=blog-icon href=https://lavafroth.is-a.dev/icon.png></head><body><header><a class=site-name href=https://lavafroth.is-a.dev/><svg viewBox="0 0 8790 2080"><path d="M80 1935V465h216v1270h286v2e2zm853 0 222-1470h264l222 1470h-210l-40-3e2h-208l-40 3e2zm280-528h148l-62-494-6-78h-12l-6 78zm1025 528L2014 465h210l108 868 8 142h12l8-142 108-868h210l-224 1470zm813 0 222-1470h264l222 1470h-210l-40-3e2h-208l-40 3e2zm280-528h148l-62-494-6-78h-12l-6 78zm851 528V465h514v222h-298v386h2e2v222h-2e2v640zm910 0V465h216q194 0 286 108 92 107 92 316 0 124-43 215-44 90-106 132l147 699h-216l-122-620h-38v620zm216-820q60 0 95-26 35-27 50-76t15-116q0-105-34-161-35-57-126-57zm1084 836q-90 0-154-42-65-42-99-114-35-72-35-162V767q0-91 35-162 34-72 99-114 64-42 154-42t155 42q64 42 99 114 34 72 34 162v866q0 90-34 162-35 72-99 114-65 42-155 42zm0-210q40 0 56-33 16-34 16-75V767q0-41-17-74-17-34-55-34-37 0-54 34-18 33-18 74v866q0 41 17 75 17 33 55 33zm890 194V687h-204V465h624v222h-204v1248zm828 0V465h216v608h168V465h216v1470h-216v-640h-168v640z"/></svg></a><nav><a style=--url:url(./github.svg) href=https://github.com/lavafroth aria-label=github target=_blank></a><a href=../about/ aria-label=about style=--url:url(./about.svg)></a><a href=../art/ aria-label=art style=--url:url(./art.svg)></a><a href=../index.xml aria-label=rss style=--url:url(./rss.svg)></a><nav></header><main><hgroup data-pagefind-body><p data-pagefind-ignore><time>Jan 1, 1 | 1 minute read</time></p><h1 data-pagefind-meta=title></h1></hgroup><section class=post-content data-pagefind-body><!doctype html><html lang=en><head><meta charset=UTF-8><title>Easy SSH tunnel</title><meta name=viewport content="width=device-width,initial-scale=1"><meta name=description content><style>*{font-family:monospace}body{--purple-dark:oklch(29.3% 0.136 325.661);--purple-light:lab(78.5378% 39.3533 -32.9615);background:var(--purple-dark)}input{outline:none;color:#eee;border:none;background:var(--purple-dark)}.post-content>div{margin:1rem auto;label:not([for=direction]) { padding:.5rem; background:var(--purple-light); color:var(--purple-dark); }}.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));grid-template-rows:repeat(3,minmax(0,1fr)); *{ text-align: center; } label[for=direction] { user-select: none; grid-row: span 2 / span 2; grid-column-start: 2; color: #eee; &::after { display: block; content: '<'; } } input[type="checkbox"] { opacity: 0; position: absolute; pointer-events: none; } &:has(input:checked) { label[for=direction]::after { content: '>'; } }}#remotehost{grid-column:span 2/span 2}#command{background:oklch(45.2% .211 324.591);margin-top:1rem;padding-inline:1rem;color:#eee}#copy{background:var(--purple-light);color:var(--purple-dark);padding-inline:1rem;float:right;&:active { background:oklch(45.2% 0.211 324.591); color:#eee; }}</style></head><body><div class=grid><label for=localport>Local port</label>
<label for=direction>Traffic direction</label>
<label for=remoteport>Remote port</label>
<input id=localport type=number min=0 max=65535 step=1 value=8888>
<input id=localport type=number min=0 max=65535 step=1 placeholder=8888>
<input type=checkbox id=direction>
<input id=remoteport type=number min=0 max=65535 step=1 value=8888>
<input id=remoteport type=number min=0 max=65535 step=1 placeholder=8888>
<label for=remotehost>Connect to</label>
<input id=remotehost type=text placeholder=user@remote.host></div><div id=command></div></div></body><script>const localPort=document.querySelector("#localport"),remotePort=document.querySelector("#remoteport"),remoteHost=document.querySelector("#remotehost"),direction=document.querySelector("#direction"),command=document.querySelector("#command");function updateCommand(){const e=localPort.value,t=remotePort.value,n=direction.checked?"R":"L",s=remoteHost.value||remoteHost.placeholder;command.textContent=`ssh -f -N -${n} ${e}:localhost:${t} ${s}`}localPort.addEventListener("input",updateCommand),direction.addEventListener("change",updateCommand),remotePort.addEventListener("input",updateCommand),remoteHost.addEventListener("input",updateCommand),updateCommand()</script></html></section><footer class=post-tags data-pagefind-meta=tags></footer></main><footer class=footer><p>&copy; 2026 <a href=https://lavafroth.is-a.dev/>lavafroth</a></p><p><a href=https://github.com/lavafroth/lavafroth.github.io/issues/new/choose>Report an issue</a></p><p><a href=https://github.com/lavafroth/lavafroth.github.io/discussions/>Discuss</a></p><p><a href=https://lavafroth.is-a.dev/privacy>Privacy</a></p><p><a href=https://creativecommons.org/licenses/by-sa/4.0/legalcode>License</a></p></footer></body></html>
<input id=remotehost type=text placeholder=user@remote.host></div><div id=command></div><button id=copy>⧉ Copy</button></div></body><script>const localPort=document.querySelector("#localport"),remotePort=document.querySelector("#remoteport"),remoteHost=document.querySelector("#remotehost"),direction=document.querySelector("#direction"),command=document.querySelector("#command"),copy=document.querySelector("#copy"),valueOrPlaceholder=e=>e.value||e.placeholder;function updateCommand(){const e=direction.checked?"R":"L";command.textContent=`ssh -f -N -${e} ${valueOrPlaceholder(localPort)}:localhost:${valueOrPlaceholder(remotePort)} ${valueOrPlaceholder(remoteHost)}`}function validatePort(e){const t=e.keyCode;(t<48||t>57||e.target.value*10+(t-48)>65535)&&e.preventDefault()}for(const e of[localPort,direction,remoteHost,remotePort])e.addEventListener("input",updateCommand);for(const e of[localPort,remotePort])e.addEventListener("keypress",validatePort);copy.addEventListener("click",e=>navigator.clipboard.writeText(command.textContent)),updateCommand()</script></html></section><footer class=post-tags data-pagefind-meta=tags></footer></main><footer class=footer><p>&copy; 2026 <a href=https://lavafroth.is-a.dev/>lavafroth</a></p><p><a href=https://github.com/lavafroth/lavafroth.github.io/issues/new/choose>Report an issue</a></p><p><a href=https://github.com/lavafroth/lavafroth.github.io/discussions/>Discuss</a></p><p><a href=https://lavafroth.is-a.dev/privacy>Privacy</a></p><p><a href=https://creativecommons.org/licenses/by-sa/4.0/legalcode>License</a></p></footer></body></html>

View File

@@ -1023,17 +1023,16 @@ injection wherein he mentions a way to &amp;ldquo;gadget&amp;rdquo; our way out
&lt;style&gt;
* { font-family: monospace; }
body {
background: oklch(29.3% 0.136 325.661);
--purple-dark: oklch(29.3% 0.136 325.661);
--purple-light: lab(78.5378% 39.3533 -32.9615);
background: var(--purple-dark);
}
input {
outline: none;
color: #eee;
border: none;
background: oklch(29.3% 0.136 325.661);
&amp;:invalid {
background: oklch(40.8% 0.153 2.432);
}
background: var(--purple-dark);
}
.post-content &gt; div {
@@ -1041,20 +1040,15 @@ injection wherein he mentions a way to &amp;ldquo;gadget&amp;rdquo; our way out
label:not([for=direction]) {
padding: .5rem;
background: lab(78.5378% 39.3533 -32.9615);
color: oklch(29.3% 0.136 325.661);
background: var(--purple-light);
color: var(--purple-dark);
}
}
.grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-rows: repeat(3, minmax(0, 1fr));
justify-content: space-between;
align-items: center;
* { text-align: center; }
label[for=direction] {
@@ -1085,18 +1079,23 @@ injection wherein he mentions a way to &amp;ldquo;gadget&amp;rdquo; our way out
grid-column: span 2 / span 2;
}
.post-content:has(input:invalid) {
#command {
display: none;
}
}
#command {
background: oklch(45.2% 0.211 324.591);
margin-top: 1rem;
padding: 0.5rem;
padding-inline: 1rem;
color: #eee;
}
#copy {
background: var(--purple-light);
color: var(--purple-dark);
padding-inline: 1rem;
float: right;
&amp;:active {
background: oklch(45.2% 0.211 324.591);
color: #eee;
}
}
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
@@ -1107,14 +1106,15 @@ injection wherein he mentions a way to &amp;ldquo;gadget&amp;rdquo; our way out
&lt;label for="direction"&gt;Traffic direction&lt;/label&gt;
&lt;label for="remoteport"&gt;Remote port&lt;/label&gt;
&lt;input id="localport" type="number" min="0" max="65535" step=1 value=8888&gt;
&lt;input id="localport" type="number" min="0" max="65535" step=1 placeholder=8888&gt;
&lt;input type=checkbox id="direction"&gt;
&lt;input id="remoteport" type="number" min="0" max="65535" step=1 value=8888&gt;
&lt;input id="remoteport" type="number" min="0" max="65535" step=1 placeholder=8888&gt;
&lt;label for="remotehost"&gt;Connect to&lt;/label&gt;
&lt;input id="remotehost" type="text" placeholder="user@remote.host"&gt;
&lt;/div&gt;
&lt;div id='command'&gt;&lt;/div&gt;
&lt;button id='copy'&gt;⧉ Copy&lt;/button&gt;
&lt;/div&gt;
&lt;/body&gt;
@@ -1124,19 +1124,31 @@ injection wherein he mentions a way to &amp;ldquo;gadget&amp;rdquo; our way out
const remoteHost = document.querySelector('#remotehost');
const direction = document.querySelector('#direction');
const command = document.querySelector('#command');
const copy = document.querySelector('#copy');
const valueOrPlaceholder = (v) =&gt; v.value || v.placeholder;
function updateCommand() {
const localPortValue = localPort.value;
const remotePortValue = remotePort.value;
const directionString = direction.checked ? "R" : "L";
const remoteHostValue = remoteHost.value || remoteHost.placeholder;
command.textContent = `ssh -f -N -${directionString} ${localPortValue}:localhost:${remotePortValue} ${remoteHostValue}`;
command.textContent = `ssh -f -N -${directionString} ${valueOrPlaceholder(localPort)}:localhost:${valueOrPlaceholder(remotePort)} ${valueOrPlaceholder(remoteHost)}`;
}
localPort.addEventListener('input', updateCommand);
direction.addEventListener('change', updateCommand);
remotePort.addEventListener('input', updateCommand);
remoteHost.addEventListener('input', updateCommand);
function validatePort(e) {
const code = e.keyCode;
if (code &lt; 48 || code &gt; 57 || e.target.value * 10 + (code - 48) &gt; 65535) {
e.preventDefault()
}
}
for (const variable of [localPort, direction, remoteHost, remotePort]) {
variable.addEventListener('input', updateCommand);
}
for (const portVariable of [localPort, remotePort]) {
portVariable.addEventListener('keypress', validatePort);
}
copy.addEventListener('click', (_) =&gt; navigator.clipboard.writeText(command.textContent))
updateCommand()
&lt;/script&gt;

Binary file not shown.

Binary file not shown.

View File

@@ -1 +1 @@
{"version":"1.0.3","languages":{"en-us":{"hash":"en-us_e3ed9fd3f8c16","wasm":"en-us","page_count":56}}}
{"version":"1.0.3","languages":{"en":{"hash":"en_f524472d1d","wasm":"en","page_count":1},"en-us":{"hash":"en-us_e3ed9fd3f8c16","wasm":"en-us","page_count":56}}}

Binary file not shown.

BIN
pagefind/wasm.en.pagefind Normal file

Binary file not shown.