Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions hugo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ params:
# Docs repository URL
repo: https://github.com/docker/docs

# MCP endpoint that serves semantic search over the docs. Used by the 404 page.
# The stage backend has no /mcp route, so every environment uses production.
docsSearchURL: https://ai-backend-service.docker.com/mcp

# === Example versions ===
# The following parameters are used in various ways in content and templates.
# Mostly as examples in code snippets, but also in other exotic ways.
Expand Down
193 changes: 154 additions & 39 deletions layouts/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,161 @@
{{ end }}

{{ define "main" }}
<article class="prose max-w-none dark:prose-invert">
<h1>404</h1>
<p>
There might be a mistake in the URL or you might've clicked a link to
content that no longer exists. If you think it's the latter, please file
an issue in our issue tracker on GitHub.
<div class="flex min-h-[60vh] flex-col items-center justify-center py-16 text-center">

<div class="mb-6 text-red-500 dark:text-red-400" style="width: 2.5rem; height: 2.5rem;">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="width: 100%; height: 100%;">
<path d="M12 8.00008V12.0001M12 16.0001H12.01M3 7.94153V16.0586C3 16.4013 3 16.5726 3.05048 16.7254C3.09515 16.8606 3.16816 16.9847 3.26463 17.0893C3.37369 17.2077 3.52345 17.2909 3.82297 17.4573L11.223 21.5684C11.5066 21.726 11.6484 21.8047 11.7985 21.8356C11.9315 21.863 12.0685 21.863 12.2015 21.8356C12.3516 21.8047 12.4934 21.726 12.777 21.5684L20.177 17.4573C20.4766 17.2909 20.6263 17.2077 20.7354 17.0893C20.8318 16.9847 20.9049 16.8606 20.9495 16.7254C21 16.5726 21 16.4013 21 16.0586V7.94153C21 7.59889 21 7.42756 20.9495 7.27477C20.9049 7.13959 20.8318 7.01551 20.7354 6.91082C20.6263 6.79248 20.4766 6.70928 20.177 6.54288L12.777 2.43177C12.4934 2.27421 12.3516 2.19543 12.2015 2.16454C12.0685 2.13721 11.9315 2.13721 11.7985 2.16454C11.6484 2.19543 11.5066 2.27421 11.223 2.43177L3.82297 6.54288C3.52345 6.70928 3.37369 6.79248 3.26463 6.91082C3.16816 7.01551 3.09515 7.13959 3.05048 7.27477C3 7.42756 3 7.59889 3 7.94153Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>

<h1 class="mb-3 text-3xl font-bold text-gray-900 dark:text-white">Page not found</h1>

<p class="mb-6 max-w-sm text-gray-500 dark:text-gray-400">
There might be a mistake in the URL, or you might have followed a broken
link. Use the navigation, or try one of the suggested pages below. If you
followed a link from a Docker product, consider filing an issue.
</p>

<a
id="newissue"
class="link"
href="{{ site.Params.repo }}/issues/new"
>
<strong>Create a new issue</strong></a
>

<a href="{{ site.BaseURL }}">
<figure>
<img src="/assets/images/404-docs.png" alt="404 page not found" />
<figcaption class="link">Go to the homepage</figcaption>
</figure>
</a>

<script>
let el = document.querySelector("#newissue");
if (el) {
let url = new URL("{{ site.Params.repo }}/issues/new");
url.searchParams.set("title", "404 at " + window.location.pathname);
let body = "I found a broken link : " + window.location.href;
if (document.referrer !== "") {
body += "\nI arrived on this page through: " + document.referrer
<div class="mb-8 flex flex-wrap justify-center gap-3" x-data>
<button
@click="$store.gordon.open(window.location.pathname + ' was not found. Suggest alternative pages.', true)"
class="cursor-pointer rounded-lg border border-gray-200 px-4 py-2 text-sm text-gray-600 transition hover:border-blue-500 hover:text-blue-600 dark:border-gray-700 dark:text-gray-300 dark:hover:border-blue-500 dark:hover:text-blue-400"
>
Ask Gordon for help
</button>
<a
id="newissue"
href="{{ site.Params.repo }}/issues/new"
class="rounded-lg border border-gray-200 px-4 py-2 text-sm text-gray-600 transition hover:border-blue-500 hover:text-blue-600 dark:border-gray-700 dark:text-gray-300 dark:hover:border-blue-500 dark:hover:text-blue-400"
>
Create an issue
</a>
</div>

<div class="w-full max-w-sm" x-data="docsSuggestions()">
<p class="mb-3 text-base font-semibold text-gray-700 dark:text-gray-300">
Suggested pages
</p>

<ul x-show="isLoading || links.length > 0" class="space-y-2">
<template x-for="i in maxLinks" :key="i">
<li class="flex h-6 items-center justify-center">
<template x-if="links[i - 1]">
<a
:href="links[i - 1].url"
x-text="links[i - 1].text"
class="text-sm text-blue-600 hover:underline dark:text-blue-400"
></a>
</template>
<template x-if="!links[i - 1] && isLoading">
<div class="h-3.5 w-2/3 animate-pulse rounded bg-gray-200 dark:bg-gray-700"></div>
</template>
</li>
</template>
</ul>

<p x-show="!isLoading && links.length === 0" x-cloak class="text-sm text-gray-500 dark:text-gray-400">
No suggestions found. Try asking Gordon for help.
</p>
</div>

</div>

<script>
function docsSuggestions() {
return {
links: [],
isLoading: true,
maxLinks: 5,

async init() {
const path = window.location.pathname;
const words = path
.split(/[\/_.-]+/)
.map((s) => s.trim())
.filter(Boolean);

if (words.length === 0) {
this.isLoading = false;
return;
}
url.searchParams.set("body", body);
url.searchParams.set("template", "broken_link.yml")
url.searchParams.set("title", "[404]: " + window.location.pathname);
url.searchParams.set("target", window.location.href);
url.searchParams.set("location", document.referrer);
url.searchParams.set("labels", "status/triage");
el.setAttribute("href", url.toString());
}
</script>
</article>

try {
// The search tool only returns results for queries that mention
// Docker, so prefix the words taken from the broken URL.
const message = 'Docker documentation: ' + words.join(' ');
const response = await fetch('{{ site.Params.docsSearchURL }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/event-stream',
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/call',
params: { name: 'search_docker_docs', arguments: { message } },
}),
});
if (!response.ok) return;

const result = await response.json();
const documents = result?.result?.structuredContent?.documents ?? [];

const seen = new Set();
for (const doc of documents) {
const source = doc?.metadata?.source;
if (!source) continue;

// Rewrite the origin so links also work in previews and locally.
const url = new URL(source);
url.protocol = window.location.protocol;
url.host = window.location.host;
if (url.pathname === path || seen.has(url.pathname)) continue;

seen.add(url.pathname);
this.links.push({
url: url.toString(),
text: this.titleFromContent(doc.content) || this.titleFromPath(url.pathname),
});
if (this.links.length >= this.maxLinks) break;
}
} catch {
} finally {
this.isLoading = false;
}
},

// Chunks start with the page title, optionally followed by " > section".
titleFromContent(content) {
if (typeof content !== 'string') return '';
const firstLine = content.split('\n')[0];
return firstLine.replace(/^#+\s*/, '').split(' > ')[0].trim();
},

titleFromPath(pathname) {
return pathname
.split('/')
.filter(Boolean)
.map((s) => s.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()))
.join(' / ');
},
};
}

const issueLink = document.querySelector('#newissue');
if (issueLink) {
const url = new URL('{{ site.Params.repo }}/issues/new');
let body = 'I found a broken link: ' + window.location.href;
if (document.referrer) body += '\nI arrived via: ' + document.referrer;
url.searchParams.set('body', body);
url.searchParams.set('template', 'broken_link.yml');
url.searchParams.set('title', '[404]: ' + window.location.pathname);
url.searchParams.set('target', window.location.href);
url.searchParams.set('location', document.referrer);
url.searchParams.set('labels', 'status/triage');
issueLink.href = url.toString();
}
</script>
{{ end }}
Binary file removed static/assets/images/404-docs.png
Binary file not shown.
Loading