mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-10-13 04:38:23 +00:00
* docs(site): rewrite the product page in plain language The page lists what kportal does with the flags, keys and config keys that exist in the code. It drops the CDN assets, gradients and icon grids, and keeps the light and dark themes. It now covers tui.columns, typed namespace entry, completion, the log format flag and the --context flag. The claim about a 100ms file watcher is removed because the code has no such debounce. * docs: rewrite the README and update the feature list The README now has a feature table, install steps, a flag table, a config reference and troubleshooting. It documents tui.columns, typed namespace entry, shell completion and the --context flag. It drops the comparison table with other tools, which the repository cannot back up, and corrects the supported resource types and the Go version.
418 lines
21 KiB
HTML
418 lines
21 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>kportal: run Kubernetes port-forwards from one YAML file</title>
|
|
<meta name="description" content="kportal runs many Kubernetes port-forwards from one YAML file, shows their status in a terminal UI, reconnects dropped forwards and reloads the config when it changes.">
|
|
<meta name="color-scheme" content="light dark">
|
|
<style>
|
|
:root {
|
|
--bg: #ffffff;
|
|
--fg: #1a1a1a;
|
|
--muted: #565656;
|
|
--line: #d4d4d4;
|
|
--code-bg: #f3f3f3;
|
|
--accent: #0a58ca;
|
|
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
|
--sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
}
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--bg: #141414;
|
|
--fg: #e8e8e8;
|
|
--muted: #a6a6a6;
|
|
--line: #3a3a3a;
|
|
--code-bg: #1f1f1f;
|
|
--accent: #6ea8fe;
|
|
}
|
|
}
|
|
:root[data-theme="light"] { --bg:#ffffff; --fg:#1a1a1a; --muted:#565656; --line:#d4d4d4; --code-bg:#f3f3f3; --accent:#0a58ca; }
|
|
:root[data-theme="dark"] { --bg:#141414; --fg:#e8e8e8; --muted:#a6a6a6; --line:#3a3a3a; --code-bg:#1f1f1f; --accent:#6ea8fe; }
|
|
|
|
* { box-sizing: border-box; }
|
|
html { scroll-behavior: smooth; }
|
|
body {
|
|
margin: 0;
|
|
background: var(--bg);
|
|
color: var(--fg);
|
|
font: 16px/1.55 var(--sans);
|
|
overflow-wrap: break-word;
|
|
}
|
|
a { color: var(--accent); }
|
|
a:focus-visible, button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
img { max-width: 100%; height: auto; }
|
|
|
|
.wrap { max-width: 56rem; margin: 0 auto; padding: 0 1rem; }
|
|
|
|
header { border-bottom: 1px solid var(--line); }
|
|
header .wrap { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1.25rem; padding-top: 0.75rem; padding-bottom: 0.75rem; }
|
|
header img { height: 2rem; width: auto; display: block; }
|
|
nav { display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; flex: 1; }
|
|
nav a { color: var(--muted); text-decoration: none; padding: 0.25rem 0; }
|
|
nav a:hover { color: var(--fg); text-decoration: underline; }
|
|
#theme { font: inherit; background: none; color: var(--muted); border: 1px solid var(--line); padding: 0.25rem 0.6rem; cursor: pointer; }
|
|
#theme:hover { color: var(--fg); }
|
|
|
|
.logo-dark { display: none; }
|
|
@media (prefers-color-scheme: dark) { .logo-light { display: none; } .logo-dark { display: block; } }
|
|
:root[data-theme="light"] .logo-light { display: block; }
|
|
:root[data-theme="light"] .logo-dark { display: none; }
|
|
:root[data-theme="dark"] .logo-light { display: none; }
|
|
:root[data-theme="dark"] .logo-dark { display: block; }
|
|
|
|
h1 { font-size: clamp(1.75rem, 5vw, 2.5rem); line-height: 1.15; margin: 2.5rem 0 1rem; max-width: 22ch; }
|
|
h2 { font-size: 1.4rem; margin: 0 0 1rem; padding-top: 0.5rem; border-top: 1px solid var(--line); }
|
|
h3 { font-size: 1.05rem; margin: 1.5rem 0 0.5rem; }
|
|
section { padding: 2rem 0 0.5rem; }
|
|
p { margin: 0 0 1rem; max-width: 42rem; }
|
|
.lead { font-size: 1.1rem; color: var(--muted); }
|
|
ul, ol { margin: 0 0 1rem; padding-left: 1.25rem; max-width: 42rem; }
|
|
|
|
code, pre, kbd { font-family: var(--mono); font-size: 0.9em; }
|
|
code, kbd { background: var(--code-bg); border: 1px solid var(--line); padding: 0.05em 0.35em; }
|
|
pre {
|
|
background: var(--code-bg);
|
|
border: 1px solid var(--line);
|
|
padding: 0.75rem 1rem;
|
|
margin: 0 0 1rem;
|
|
overflow-x: auto;
|
|
line-height: 1.45;
|
|
}
|
|
pre code { background: none; border: 0; padding: 0; font-size: 1em; }
|
|
.cmd { position: relative; }
|
|
.cmd button { position: absolute; top: 0.4rem; right: 0.4rem; font: 0.8rem var(--sans); background: var(--bg); color: var(--muted); border: 1px solid var(--line); padding: 0.15rem 0.5rem; cursor: pointer; }
|
|
.cmd pre { padding-right: 4.5rem; }
|
|
|
|
.tablewrap { overflow-x: auto; margin: 0 0 1rem; border: 1px solid var(--line); }
|
|
table { border-collapse: collapse; width: 100%; font-size: 0.95rem; }
|
|
th, td { text-align: left; vertical-align: top; padding: 0.45rem 0.75rem; border-bottom: 1px solid var(--line); }
|
|
tr:last-child td { border-bottom: 0; }
|
|
th { font-weight: 600; background: var(--code-bg); white-space: nowrap; }
|
|
td:first-child { white-space: nowrap; }
|
|
.tablewrap.wrapfirst td:first-child { white-space: normal; }
|
|
|
|
.shot { margin: 1.5rem 0 0; border: 1px solid var(--line); display: block; }
|
|
.cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 2rem; }
|
|
@media (min-width: 48rem) { .cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
|
|
|
|
footer { margin-top: 3rem; border-top: 1px solid var(--line); padding: 1.25rem 0 2rem; color: var(--muted); font-size: 0.9rem; }
|
|
footer .wrap { display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem; }
|
|
.badges { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0 0 1.5rem; }
|
|
.badges img { height: 20px; width: auto; }
|
|
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
|
|
</style>
|
|
<script>
|
|
(function () {
|
|
try { var t = localStorage.getItem("theme"); if (t) document.documentElement.setAttribute("data-theme", t); } catch (e) {}
|
|
})();
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<div class="wrap">
|
|
<a href="#" aria-label="kportal home">
|
|
<img class="logo-light" src="kportal-logo-light.svg" alt="kportal">
|
|
<img class="logo-dark" src="kportal-logo-dark.svg" alt="kportal">
|
|
</a>
|
|
<nav aria-label="Sections">
|
|
<a href="#features">Features</a>
|
|
<a href="#install">Install</a>
|
|
<a href="#usage">Usage</a>
|
|
<a href="#keys">Keys</a>
|
|
<a href="#config">Configuration</a>
|
|
<a href="#http-log">HTTP log</a>
|
|
<a href="https://github.com/lukaszraczylo/kportal">GitHub</a>
|
|
</nav>
|
|
<button id="theme" type="button" aria-label="Switch between light and dark theme">Theme</button>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="wrap">
|
|
<h1>Run Kubernetes port-forwards from one YAML file</h1>
|
|
<p class="lead">kportal starts every forward listed in <code>.kportal.yaml</code>, shows their status in a terminal UI, reconnects dropped forwards and reloads the file when it changes. It also runs without the UI.</p>
|
|
|
|
<div class="cmd"><pre><code>brew install --cask lukaszraczylo/taps/kportal</code></pre><button type="button" data-copy="brew install --cask lukaszraczylo/taps/kportal">Copy</button></div>
|
|
|
|
<div class="badges">
|
|
<a href="https://github.com/lukaszraczylo/kportal/releases"><img src="https://img.shields.io/github/v/release/lukaszraczylo/kportal" alt="Release"></a>
|
|
<a href="https://github.com/lukaszraczylo/kportal/blob/main/LICENSE"><img src="https://img.shields.io/github/license/lukaszraczylo/kportal" alt="License"></a>
|
|
<a href="https://goreportcard.com/report/github.com/lukaszraczylo/kportal"><img src="https://goreportcard.com/badge/github.com/lukaszraczylo/kportal" alt="Go Report Card"></a>
|
|
</div>
|
|
|
|
<img class="shot" src="kportal-screenshot.png" alt="kportal terminal UI listing port-forwards with their status">
|
|
|
|
<section id="features">
|
|
<h2>Features</h2>
|
|
<div class="tablewrap wrapfirst">
|
|
<table>
|
|
<tbody>
|
|
<tr><th scope="row">Forwards</th><td><code>service/<name></code>, <code>pod/<name></code> (exact name or prefix), or <code>pod</code> with a label <code>selector</code>. TCP only.</td></tr>
|
|
<tr><th scope="row">Terminal UI</th><td>Add, edit, delete and toggle forwards without a restart. The add wizard writes the config file.</td></tr>
|
|
<tr><th scope="row">Table columns</th><td><code>tui.columns</code> sets which columns the forwards table shows, their order and width. It applies to the UI and the <code>-verbose</code> table.</td></tr>
|
|
<tr><th scope="row">Add wizard</th><td>If the cluster does not allow listing namespaces, the wizard asks you to type the namespace.</td></tr>
|
|
<tr><th scope="row">Reconnect</th><td>Exponential backoff: 1s, 2s, 4s, 8s, then 10s, with 10% jitter. It does not stop retrying.</td></tr>
|
|
<tr><th scope="row">Pod restarts</th><td>Forwards by pod prefix or selector connect to the new pod.</td></tr>
|
|
<tr><th scope="row">Health checks</th><td><code>tcp-dial</code> or <code>data-transfer</code>, every 3s by default. Stale connections are reconnected by age and idle time.</td></tr>
|
|
<tr><th scope="row">Hot reload</th><td>File watcher and <code>SIGHUP</code>. An invalid config is rejected and the previous one stays active.</td></tr>
|
|
<tr><th scope="row">Contexts</th><td>Several contexts and namespaces in one file. <code>--context</code> selects which contexts run.</td></tr>
|
|
<tr><th scope="row">Port conflicts</th><td>Startup and reload checks report a busy local port and the process that holds it.</td></tr>
|
|
<tr><th scope="row">mDNS</th><td>With <code>mdns.enabled: true</code>, each forward is reachable as <code><alias>.local</code>.</td></tr>
|
|
<tr><th scope="row">HTTP log</th><td>Per-forward request log with details, filter, search, JSON formatting, gzip decoding and body copy. Sensitive header values are redacted.</td></tr>
|
|
<tr><th scope="row">Benchmark</th><td>HTTP requests through a forward. Reports min, max and average latency, P50, P95, P99, requests per second and status codes.</td></tr>
|
|
<tr><th scope="row">Generate</th><td><code>kportal generate</code> lists cluster services and appends the ones you pick, with consecutive local ports.</td></tr>
|
|
<tr><th scope="row">Headless</th><td><code>-headless</code> runs without the UI and logs to stderr. <code>-log-format json</code> switches the log format.</td></tr>
|
|
<tr><th scope="row">Import</th><td><code>--convert</code> turns a kftray JSON file into kportal YAML.</td></tr>
|
|
<tr><th scope="row">Shell completion</th><td><code>kportal completion</code> prints or installs bash, zsh and fish completions.</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="install">
|
|
<h2>Install</h2>
|
|
<h3>Homebrew (macOS)</h3>
|
|
<div class="cmd"><pre><code>brew install --cask lukaszraczylo/taps/kportal</code></pre><button type="button" data-copy="brew install --cask lukaszraczylo/taps/kportal">Copy</button></div>
|
|
<h3>Install script</h3>
|
|
<div class="cmd"><pre><code>curl -fsSL https://raw.githubusercontent.com/lukaszraczylo/kportal/main/install.sh | bash</code></pre><button type="button" data-copy="curl -fsSL https://raw.githubusercontent.com/lukaszraczylo/kportal/main/install.sh | bash">Copy</button></div>
|
|
<p>The script verifies the archive SHA-256 against the release checksums file. If <code>cosign</code> is on <code>PATH</code>, it also verifies the keyless signature. Set <code>DRY_RUN=1</code> to download and verify without installing. Set <code>SKIP_COSIGN=1</code> to skip the cosign check.</p>
|
|
<h3>Manual download</h3>
|
|
<p>The <a href="https://github.com/lukaszraczylo/kportal/releases">releases page</a> has archives for Linux, macOS and Windows on amd64 and arm64.</p>
|
|
<h3>Build from source</h3>
|
|
<pre><code>git clone https://github.com/lukaszraczylo/kportal.git
|
|
cd kportal
|
|
make build && make install</code></pre>
|
|
<p>The build needs Go 1.26 or later.</p>
|
|
</section>
|
|
|
|
<section id="usage">
|
|
<h2>Usage</h2>
|
|
<h3>Quick start</h3>
|
|
<p>Create <code>.kportal.yaml</code>, then run <code>kportal</code>.</p>
|
|
<pre><code>contexts:
|
|
- name: production
|
|
namespaces:
|
|
- name: backend
|
|
forwards:
|
|
- resource: service/postgres
|
|
protocol: tcp
|
|
port: 5432
|
|
localPort: 5432
|
|
alias: prod-db
|
|
|
|
- resource: service/api
|
|
protocol: tcp
|
|
port: 8080
|
|
localPort: 8080
|
|
alias: api
|
|
httpLog: true</code></pre>
|
|
|
|
<h3>Commands and flags</h3>
|
|
<div class="tablewrap">
|
|
<table>
|
|
<thead><tr><th>Command or flag</th><th>Effect</th></tr></thead>
|
|
<tbody>
|
|
<tr><td><code>kportal</code></td><td>Start the terminal UI.</td></tr>
|
|
<tr><td><code>-c <file></code></td><td>Config file. Default <code>.kportal.yaml</code>.</td></tr>
|
|
<tr><td><code>-v</code></td><td>Verbose logging.</td></tr>
|
|
<tr><td><code>-headless</code></td><td>No UI. Logs go to stderr.</td></tr>
|
|
<tr><td><code>-check</code></td><td>Validate the config and exit.</td></tr>
|
|
<tr><td><code>-context <name></code></td><td>Forward only the named contexts. Repeatable, or comma-separated.</td></tr>
|
|
<tr><td><code>-log-format text|json</code></td><td>Log format. Default <code>text</code>.</td></tr>
|
|
<tr><td><code>-convert <file></code></td><td>Convert a kftray JSON file. <code>-convert-output</code> sets the output, default <code>.kportal.yaml</code>.</td></tr>
|
|
<tr><td><code>-update</code></td><td>Check for updates and exit.</td></tr>
|
|
<tr><td><code>-version</code></td><td>Print the version and exit.</td></tr>
|
|
<tr><td><code>kportal generate</code></td><td>Add forwards from a cluster. Needs <code>--context</code>. <code>--config</code> picks the file and <code>--dry-run</code> only prints.</td></tr>
|
|
<tr><td><code>kportal completion</code></td><td>Print a completion script. <code>--shell bash|zsh|fish</code>, <code>--install</code>, <code>--uninstall</code>.</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<h3>Examples</h3>
|
|
<pre><code>kportal -check
|
|
kportal -c /path/to/config.yaml
|
|
kportal --context development-team-a
|
|
kportal -headless -v 2>kportal.log &
|
|
kportal generate --context=my-cluster --dry-run
|
|
kportal completion --shell zsh --install</code></pre>
|
|
<p>With <code>--context</code>, contexts you never select together can reuse a <code>localPort</code>. Without it, a duplicate local port is an error.</p>
|
|
</section>
|
|
|
|
<section id="keys">
|
|
<h2>Keys</h2>
|
|
<div class="cols">
|
|
<div>
|
|
<h3>Forwards list</h3>
|
|
<div class="tablewrap">
|
|
<table>
|
|
<tbody>
|
|
<tr><td><kbd>Up</kbd> <kbd>Down</kbd> / <kbd>j</kbd> <kbd>k</kbd></td><td>Move</td></tr>
|
|
<tr><td><kbd>PgUp</kbd> <kbd>PgDn</kbd></td><td>Move by 10 rows</td></tr>
|
|
<tr><td><kbd>Space</kbd> / <kbd>Enter</kbd></td><td>Toggle forward</td></tr>
|
|
<tr><td><kbd>n</kbd></td><td>Add forward</td></tr>
|
|
<tr><td><kbd>e</kbd></td><td>Edit forward</td></tr>
|
|
<tr><td><kbd>d</kbd></td><td>Delete forward</td></tr>
|
|
<tr><td><kbd>b</kbd></td><td>Benchmark</td></tr>
|
|
<tr><td><kbd>l</kbd></td><td>HTTP log</td></tr>
|
|
<tr><td><kbd>q</kbd></td><td>Quit</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<p>In the add and edit wizard, <kbd>h</kbd> on the confirmation step toggles <code>httpLog</code>.</p>
|
|
</div>
|
|
<div>
|
|
<h3>Status</h3>
|
|
<div class="tablewrap">
|
|
<table>
|
|
<tbody>
|
|
<tr><td>● Active</td><td>Connection healthy</td></tr>
|
|
<tr><td>○ Starting</td><td>First connection. Failed checks show Starting for 10s.</td></tr>
|
|
<tr><td>◐ Reconnecting</td><td>Reconnecting after a failure</td></tr>
|
|
<tr><td>✗ Error</td><td>Connection failed</td></tr>
|
|
<tr><td>○ Disabled</td><td>Turned off in the UI</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="config">
|
|
<h2>Configuration</h2>
|
|
<h3>Forward fields</h3>
|
|
<div class="tablewrap">
|
|
<table>
|
|
<thead><tr><th>Field</th><th>Required</th><th>Description</th></tr></thead>
|
|
<tbody>
|
|
<tr><td><code>resource</code></td><td>Yes</td><td><code>service/<name></code>, <code>pod/<name></code>, or <code>pod</code> with <code>selector</code></td></tr>
|
|
<tr><td><code>protocol</code></td><td>No</td><td><code>tcp</code>. No other value is accepted.</td></tr>
|
|
<tr><td><code>port</code></td><td>Yes</td><td>Remote port</td></tr>
|
|
<tr><td><code>localPort</code></td><td>Yes</td><td>Local port</td></tr>
|
|
<tr><td><code>alias</code></td><td>No</td><td>Display name and mDNS hostname</td></tr>
|
|
<tr><td><code>selector</code></td><td>With bare <code>pod</code></td><td>Label selector, for example <code>app=nginx</code>. Not allowed with <code>pod/<name></code>.</td></tr>
|
|
<tr><td><code>httpLog</code></td><td>No</td><td><code>true</code>, or a map of options</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="cols">
|
|
<div>
|
|
<h3>Health checks and reliability</h3>
|
|
<pre><code>healthCheck:
|
|
interval: "3s"
|
|
timeout: "2s"
|
|
method: "data-transfer"
|
|
maxConnectionAge: "25m"
|
|
maxIdleTime: "10m"
|
|
|
|
reliability:
|
|
tcpKeepalive: "30s"
|
|
dialTimeout: "30s"
|
|
retryOnStale: true
|
|
watchdogPeriod: "30s"</code></pre>
|
|
<p>These are the defaults. <code>method</code> is <code>tcp-dial</code> or <code>data-transfer</code>. A connection past <code>maxConnectionAge</code> reconnects only when it is also idle.</p>
|
|
</div>
|
|
<div>
|
|
<h3>mDNS</h3>
|
|
<pre><code>mdns:
|
|
enabled: true
|
|
|
|
contexts:
|
|
- name: production
|
|
namespaces:
|
|
- name: default
|
|
forwards:
|
|
- resource: service/postgres
|
|
port: 5432
|
|
localPort: 5432
|
|
alias: db # db.local</code></pre>
|
|
<p>Without an alias, <code>service/redis</code> publishes <code>redis.local</code>.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<h3>Table columns</h3>
|
|
<pre><code>tui:
|
|
columns:
|
|
- name: alias
|
|
width: 30 # optional, 1-200
|
|
- name: resource
|
|
- name: local
|
|
- name: status</code></pre>
|
|
<p>Valid names: <code>context</code>, <code>namespace</code>, <code>alias</code>, <code>type</code>, <code>resource</code>, <code>remote</code>, <code>local</code>, <code>status</code>. List order is render order and unlisted columns are hidden. Without <code>tui</code>, the table keeps its default columns. The setting applies to the UI and the <code>-verbose</code> table.</p>
|
|
|
|
<h3>Reload</h3>
|
|
<p>kportal reloads when the file changes. To reload by hand, run <code>kill -HUP $(pgrep kportal)</code>.</p>
|
|
</section>
|
|
|
|
<section id="http-log">
|
|
<h2>HTTP log and benchmark</h2>
|
|
<p>Press <kbd>l</kbd> on a forward that has <code>httpLog</code> set. The list shows TIME, METHOD, STATUS, LATENCY and PATH.</p>
|
|
<pre><code>httpLog:
|
|
enabled: true
|
|
includeHeaders: true # sensitive values are redacted
|
|
maxBodySize: 65536 # bytes, 0 = unlimited
|
|
filterPath: "/api/" # log only paths containing this text
|
|
logFile: "api.log" # also append entries to this file</code></pre>
|
|
<div class="cols">
|
|
<div>
|
|
<h3>List view</h3>
|
|
<ul>
|
|
<li><kbd>Enter</kbd> opens details</li>
|
|
<li><kbd>g</kbd> / <kbd>G</kbd> jump to top or bottom</li>
|
|
<li><kbd>a</kbd> toggles auto-scroll</li>
|
|
<li><kbd>f</kbd> cycles the filter: All, Non-2xx, Errors</li>
|
|
<li><kbd>/</kbd> searches path or method</li>
|
|
<li><kbd>c</kbd> clears filters, <kbd>q</kbd> closes</li>
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<h3>Detail view</h3>
|
|
<ul>
|
|
<li>Headers, bodies and timing</li>
|
|
<li>JSON is pretty-printed, gzip and deflate are decoded</li>
|
|
<li><kbd>c</kbd> copies the response body</li>
|
|
<li><kbd>Esc</kbd>, <kbd>q</kbd> or <kbd>Enter</kbd> returns to the list</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<p>With <code>includeHeaders: true</code>, values of <code>Authorization</code>, <code>Cookie</code>, <code>Set-Cookie</code>, <code>X-Api-Key</code>, <code>X-Auth-Token</code>, <code>X-Csrf-Token</code>, <code>Proxy-Authorization</code>, <code>X-Access-Token</code> and any header whose name contains <code>token</code>, <code>secret</code>, <code>password</code> or <code>apikey</code> become <code>[REDACTED]</code>. This cannot be turned off.</p>
|
|
<h3>Benchmark</h3>
|
|
<p>Press <kbd>b</kbd> on a forward. Set the URL path (default <code>/</code>), method, concurrency and request count.</p>
|
|
</section>
|
|
|
|
<section id="more">
|
|
<h2>More</h2>
|
|
<p>The <a href="https://github.com/lukaszraczylo/kportal#troubleshooting">README</a> covers troubleshooting, kftray import and telemetry opt-out. Release history is in the <a href="https://github.com/lukaszraczylo/kportal/blob/main/CHANGELOG.md">changelog</a>.</p>
|
|
</section>
|
|
</main>
|
|
|
|
<footer>
|
|
<div class="wrap">
|
|
<span>MIT License. By <a href="https://github.com/lukaszraczylo">Lukasz Raczylo</a>.</span>
|
|
<a href="https://github.com/lukaszraczylo/kportal">GitHub</a>
|
|
<a href="https://github.com/lukaszraczylo/kportal/issues">Issues</a>
|
|
<a href="https://github.com/lukaszraczylo/kportal/releases">Releases</a>
|
|
</div>
|
|
</footer>
|
|
|
|
<script>
|
|
document.querySelectorAll("[data-copy]").forEach(function (b) {
|
|
b.addEventListener("click", function () {
|
|
var text = b.getAttribute("data-copy");
|
|
var done = function (ok) { b.textContent = ok ? "Copied" : "Failed"; setTimeout(function () { b.textContent = "Copy"; }, 1500); };
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
navigator.clipboard.writeText(text).then(function () { done(true); }, function () { done(false); });
|
|
} else { done(false); }
|
|
});
|
|
});
|
|
document.getElementById("theme").addEventListener("click", function () {
|
|
var root = document.documentElement;
|
|
var cur = root.getAttribute("data-theme") || (matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
|
|
var next = cur === "dark" ? "light" : "dark";
|
|
root.setAttribute("data-theme", next);
|
|
try { localStorage.setItem("theme", next); } catch (e) {}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|