feat: query language docs page at /dashboard/docs
This commit is contained in:
parent
27c9044a8b
commit
33d1209ac9
|
|
@ -0,0 +1,208 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PingQL — Query Language Docs</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
body { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', ui-monospace, monospace; background: #0a0a0a; }
|
||||
pre, code { font-family: inherit; }
|
||||
.prose h2 { color: #f3f4f6; font-size: 1.125rem; font-weight: 600; margin-top: 2rem; margin-bottom: 0.75rem; padding-bottom: 0.5rem; border-bottom: 1px solid #1f2937; }
|
||||
.prose h3 { color: #d1d5db; font-size: 0.9rem; font-weight: 600; margin-top: 1.5rem; margin-bottom: 0.5rem; text-transform: uppercase; letter-spacing: 0.05em; }
|
||||
.prose p { color: #9ca3af; font-size: 0.875rem; line-height: 1.7; margin-bottom: 0.75rem; }
|
||||
.prose ul { color: #9ca3af; font-size: 0.875rem; margin-bottom: 0.75rem; padding-left: 1.25rem; list-style: disc; }
|
||||
.prose li { margin-bottom: 0.25rem; line-height: 1.6; }
|
||||
code.inline { background: #1f2937; color: #93c5fd; padding: 0.1em 0.4em; border-radius: 0.25rem; font-size: 0.8rem; }
|
||||
.codeblock { background: #0f172a; border: 1px solid #1e293b; border-radius: 0.5rem; padding: 1rem; margin: 0.75rem 0; overflow-x: auto; }
|
||||
.codeblock pre { color: #e2e8f0; font-size: 0.8rem; line-height: 1.6; margin: 0; white-space: pre; }
|
||||
.tag-op { color: #60a5fa; }
|
||||
.tag-str { color: #34d399; }
|
||||
.tag-num { color: #f59e0b; }
|
||||
.tag-key { color: #c084fc; }
|
||||
.tag-cmt { color: #475569; }
|
||||
table { width: 100%; border-collapse: collapse; font-size: 0.8rem; margin: 0.75rem 0; }
|
||||
th { text-align: left; color: #6b7280; font-weight: 500; padding: 0.4rem 0.75rem; border-bottom: 1px solid #1f2937; }
|
||||
td { color: #9ca3af; padding: 0.4rem 0.75rem; border-bottom: 1px solid #111827; vertical-align: top; }
|
||||
td:first-child { color: #93c5fd; white-space: nowrap; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-[#0a0a0a] text-gray-100 min-h-screen">
|
||||
|
||||
<nav class="border-b border-gray-800 px-6 py-3 flex items-center justify-between sticky top-0 bg-[#0a0a0a]/95 backdrop-blur z-10">
|
||||
<a href="/dashboard/home" class="text-xl font-bold">Ping<span class="text-blue-400">QL</span></a>
|
||||
<div class="flex items-center gap-6 text-sm text-gray-500">
|
||||
<a href="/dashboard/home" class="hover:text-gray-300 transition-colors">Monitors</a>
|
||||
<a href="/docs" class="hover:text-gray-300 transition-colors">API</a>
|
||||
<span class="text-blue-400">Query Docs</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="max-w-3xl mx-auto px-6 py-10 prose">
|
||||
|
||||
<h1 class="text-2xl font-bold text-white mb-1">Query Language</h1>
|
||||
<p class="text-gray-500 text-sm mb-8">A MongoDB-style query language for defining exactly when a monitor is up or down.</p>
|
||||
|
||||
<!-- Overview -->
|
||||
<h2>Overview</h2>
|
||||
<p>A PingQL query is a JSON object that runs against every ping result. If the query evaluates to <code class="inline">true</code>, the monitor is considered <strong class="text-white">up</strong>. Use <code class="inline">$consider: "down"</code> to invert this.</p>
|
||||
<p>By default (no query set), a monitor is up when the HTTP status code is below 400.</p>
|
||||
|
||||
<!-- Fields -->
|
||||
<h2>Fields</h2>
|
||||
<p>These are the values you can query against:</p>
|
||||
<table>
|
||||
<thead><tr><th>Field</th><th>Type</th><th>Description</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>status</td><td>number</td><td>HTTP status code (e.g. 200, 404)</td></tr>
|
||||
<tr><td>body</td><td>string</td><td>Full response body as text</td></tr>
|
||||
<tr><td>headers.<em>name</em></td><td>string</td><td>Response header value (e.g. <code class="inline">headers.content-type</code>)</td></tr>
|
||||
<tr><td>$responseTime</td><td>number</td><td>Request latency in milliseconds</td></tr>
|
||||
<tr><td>$certExpiry</td><td>number</td><td>Days until SSL certificate expires</td></tr>
|
||||
<tr><td>$json</td><td>object</td><td>JSONPath expression against response body — see below</td></tr>
|
||||
<tr><td>$select</td><td>object</td><td>CSS selector against response HTML — see below</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Operators -->
|
||||
<h2>Operators</h2>
|
||||
<table>
|
||||
<thead><tr><th>Operator</th><th>Description</th><th>Types</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>$eq</td><td>Equal to</td><td>any</td></tr>
|
||||
<tr><td>$ne</td><td>Not equal to</td><td>any</td></tr>
|
||||
<tr><td>$gt</td><td>Greater than</td><td>number</td></tr>
|
||||
<tr><td>$gte</td><td>Greater than or equal</td><td>number</td></tr>
|
||||
<tr><td>$lt</td><td>Less than</td><td>number</td></tr>
|
||||
<tr><td>$lte</td><td>Less than or equal</td><td>number</td></tr>
|
||||
<tr><td>$contains</td><td>String contains substring</td><td>string</td></tr>
|
||||
<tr><td>$startsWith</td><td>String starts with</td><td>string</td></tr>
|
||||
<tr><td>$endsWith</td><td>String ends with</td><td>string</td></tr>
|
||||
<tr><td>$regex</td><td>Matches regular expression</td><td>string</td></tr>
|
||||
<tr><td>$exists</td><td>Field is present and non-null</td><td>any</td></tr>
|
||||
<tr><td>$in</td><td>Value is in array</td><td>any</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- Basic examples -->
|
||||
<h2>Basic Examples</h2>
|
||||
|
||||
<h3>Status code</h3>
|
||||
<div class="codeblock"><pre><span class="tag-cmt">// Up if status is 200</span>
|
||||
{ <span class="tag-key">"status"</span>: <span class="tag-num">200</span> }
|
||||
|
||||
<span class="tag-cmt">// Up if status is 2xx or 3xx</span>
|
||||
{ <span class="tag-key">"status"</span>: { <span class="tag-op">"$lt"</span>: <span class="tag-num">400</span> } }</pre></div>
|
||||
|
||||
<h3>Response body</h3>
|
||||
<div class="codeblock"><pre><span class="tag-cmt">// Up if body contains "healthy"</span>
|
||||
{ <span class="tag-key">"body"</span>: { <span class="tag-op">"$contains"</span>: <span class="tag-str">"healthy"</span> } }
|
||||
|
||||
<span class="tag-cmt">// Up if body matches a regex</span>
|
||||
{ <span class="tag-key">"body"</span>: { <span class="tag-op">"$regex"</span>: <span class="tag-str">"status.*(ok|healthy)"</span> } }</pre></div>
|
||||
|
||||
<h3>Headers</h3>
|
||||
<div class="codeblock"><pre><span class="tag-cmt">// Up if response is JSON</span>
|
||||
{ <span class="tag-key">"headers.content-type"</span>: { <span class="tag-op">"$contains"</span>: <span class="tag-str">"application/json"</span> } }</pre></div>
|
||||
|
||||
<h3>Response time</h3>
|
||||
<div class="codeblock"><pre><span class="tag-cmt">// Up if response is under 500ms</span>
|
||||
{ <span class="tag-key">"$responseTime"</span>: { <span class="tag-op">"$lt"</span>: <span class="tag-num">500</span> } }</pre></div>
|
||||
|
||||
<h3>SSL certificate expiry</h3>
|
||||
<div class="codeblock"><pre><span class="tag-cmt">// Up if cert expires in more than 14 days</span>
|
||||
{ <span class="tag-key">"$certExpiry"</span>: { <span class="tag-op">"$gt"</span>: <span class="tag-num">14</span> } }</pre></div>
|
||||
|
||||
<!-- JSONPath -->
|
||||
<h2>JSON Body — $json</h2>
|
||||
<p>Use <code class="inline">$json</code> to extract and compare a value from a JSON response body. The key is a dot-notation path, the value is a condition.</p>
|
||||
<div class="codeblock"><pre><span class="tag-cmt">// Up if response JSON has { "status": "ok" }</span>
|
||||
{ <span class="tag-key">"$json"</span>: { <span class="tag-str">"$.status"</span>: { <span class="tag-op">"$eq"</span>: <span class="tag-str">"ok"</span> } } }
|
||||
|
||||
<span class="tag-cmt">// Up if search index has >= 1000 entries</span>
|
||||
{ <span class="tag-key">"$json"</span>: { <span class="tag-str">"$.data.count"</span>: { <span class="tag-op">"$gte"</span>: <span class="tag-num">1000</span> } } }
|
||||
|
||||
<span class="tag-cmt">// Nested path</span>
|
||||
{ <span class="tag-key">"$json"</span>: { <span class="tag-str">"$.db.connections.active"</span>: { <span class="tag-op">"$lt"</span>: <span class="tag-num">100</span> } } }</pre></div>
|
||||
|
||||
<!-- CSS Selector -->
|
||||
<h2>HTML Parsing — $select</h2>
|
||||
<p>Use <code class="inline">$select</code> to extract text from an HTML response using a CSS selector. Useful for monitoring public-facing pages where there's no API.</p>
|
||||
<div class="codeblock"><pre><span class="tag-cmt">// Up if h1 text is "Example Domain"</span>
|
||||
{ <span class="tag-key">"$select"</span>: { <span class="tag-str">"h1"</span>: { <span class="tag-op">"$eq"</span>: <span class="tag-str">"Example Domain"</span> } } }
|
||||
|
||||
<span class="tag-cmt">// Up if status badge contains "operational"</span>
|
||||
{ <span class="tag-key">"$select"</span>: { <span class="tag-str">".status-badge"</span>: { <span class="tag-op">"$contains"</span>: <span class="tag-str">"operational"</span> } } }</pre></div>
|
||||
|
||||
<!-- Logical -->
|
||||
<h2>Logical Operators</h2>
|
||||
<div class="codeblock"><pre><span class="tag-cmt">// Up if status is 200 AND body contains "ok"</span>
|
||||
{
|
||||
<span class="tag-op">"$and"</span>: [
|
||||
{ <span class="tag-key">"status"</span>: <span class="tag-num">200</span> },
|
||||
{ <span class="tag-key">"body"</span>: { <span class="tag-op">"$contains"</span>: <span class="tag-str">"ok"</span> } }
|
||||
]
|
||||
}
|
||||
|
||||
<span class="tag-cmt">// Up if status is 200 OR 204</span>
|
||||
{
|
||||
<span class="tag-op">"$or"</span>: [
|
||||
{ <span class="tag-key">"status"</span>: <span class="tag-num">200</span> },
|
||||
{ <span class="tag-key">"status"</span>: <span class="tag-num">204</span> }
|
||||
]
|
||||
}
|
||||
|
||||
<span class="tag-cmt">// Up if status is NOT 500</span>
|
||||
{ <span class="tag-op">"$not"</span>: { <span class="tag-key">"status"</span>: <span class="tag-num">500</span> } }</pre></div>
|
||||
|
||||
<!-- $consider -->
|
||||
<h2>$consider — Inverting the Result</h2>
|
||||
<p>By default, a query returning <code class="inline">true</code> means the monitor is <strong class="text-green-400">up</strong>. Set <code class="inline">$consider: "down"</code> to invert this — if the conditions match, the monitor is <strong class="text-red-400">down</strong>.</p>
|
||||
<div class="codeblock"><pre><span class="tag-cmt">// DOWN if response time exceeds 2 seconds</span>
|
||||
{
|
||||
<span class="tag-op">"$consider"</span>: <span class="tag-str">"down"</span>,
|
||||
<span class="tag-key">"$responseTime"</span>: { <span class="tag-op">"$gt"</span>: <span class="tag-num">2000</span> }
|
||||
}
|
||||
|
||||
<span class="tag-cmt">// DOWN if cert expires in less than 7 days</span>
|
||||
{
|
||||
<span class="tag-op">"$consider"</span>: <span class="tag-str">"down"</span>,
|
||||
<span class="tag-key">"$certExpiry"</span>: { <span class="tag-op">"$lt"</span>: <span class="tag-num">7</span> }
|
||||
}
|
||||
|
||||
<span class="tag-cmt">// DOWN if any of these match</span>
|
||||
{
|
||||
<span class="tag-op">"$consider"</span>: <span class="tag-str">"down"</span>,
|
||||
<span class="tag-op">"$or"</span>: [
|
||||
{ <span class="tag-key">"status"</span>: { <span class="tag-op">"$gte"</span>: <span class="tag-num">500</span> } },
|
||||
{ <span class="tag-key">"$responseTime"</span>: { <span class="tag-op">"$gt"</span>: <span class="tag-num">5000</span> } }
|
||||
]
|
||||
}</pre></div>
|
||||
|
||||
<!-- Combining -->
|
||||
<h2>Combining Multiple Queries</h2>
|
||||
<p>You can add multiple monitors pointing to the same URL with different queries — one for uptime, one for performance, one for content integrity.</p>
|
||||
<div class="codeblock"><pre><span class="tag-cmt">// Monitor 1: basic uptime</span>
|
||||
{ <span class="tag-key">"status"</span>: { <span class="tag-op">"$lt"</span>: <span class="tag-num">400</span> } }
|
||||
|
||||
<span class="tag-cmt">// Monitor 2: performance — down if slow</span>
|
||||
{ <span class="tag-op">"$consider"</span>: <span class="tag-str">"down"</span>, <span class="tag-key">"$responseTime"</span>: { <span class="tag-op">"$gt"</span>: <span class="tag-num">1000</span> } }
|
||||
|
||||
<span class="tag-cmt">// Monitor 3: content — check API response shape</span>
|
||||
{
|
||||
<span class="tag-op">"$and"</span>: [
|
||||
{ <span class="tag-key">"status"</span>: <span class="tag-num">200</span> },
|
||||
{ <span class="tag-key">"$json"</span>: { <span class="tag-str">"$.ok"</span>: { <span class="tag-op">"$eq"</span>: <span class="tag-str">"true"</span> } } }
|
||||
]
|
||||
}</pre></div>
|
||||
|
||||
<div class="mt-12 pt-6 border-t border-gray-800 flex items-center justify-between">
|
||||
<a href="/dashboard/home" class="text-sm text-gray-500 hover:text-gray-300 transition-colors">← Back to monitors</a>
|
||||
<a href="/docs" class="text-sm text-blue-400 hover:text-blue-300 transition-colors">API Reference →</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/dashboard/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -16,6 +16,8 @@
|
|||
<nav class="border-b border-gray-800 px-6 py-4 flex items-center justify-between">
|
||||
<a href="/dashboard/home" class="text-xl font-bold tracking-tight">Ping<span class="text-blue-400">QL</span></a>
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="/dashboard/docs" class="text-gray-500 hover:text-gray-300 text-sm transition-colors">Query Docs</a>
|
||||
<a href="/docs" class="text-gray-500 hover:text-gray-300 text-sm transition-colors">API</a>
|
||||
<a href="/dashboard/monitors/new" class="bg-blue-600 hover:bg-blue-500 text-white text-sm font-medium px-4 py-2 rounded-lg transition-colors">+ New Monitor</a>
|
||||
<button onclick="logout()" class="text-gray-500 hover:text-gray-300 text-sm transition-colors">Logout</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,4 +11,5 @@ export const dashboard = new Elysia()
|
|||
.get("/dashboard", () => Bun.file(`${dir}/index.html`), hide)
|
||||
.get("/dashboard/home", () => Bun.file(`${dir}/home.html`), hide)
|
||||
.get("/dashboard/monitors/new", () => Bun.file(`${dir}/new.html`), hide)
|
||||
.get("/dashboard/monitors/:id", () => Bun.file(`${dir}/detail.html`), hide);
|
||||
.get("/dashboard/monitors/:id", () => Bun.file(`${dir}/detail.html`), hide)
|
||||
.get("/dashboard/docs", () => Bun.file(`${dir}/docs.html`), hide);
|
||||
|
|
|
|||
Loading…
Reference in New Issue