mirror of
https://github.com/healthchecks/healthchecks.git
synced 2025-04-10 23:40:11 +00:00
Implement documentation search
This commit is contained in:
parent
42b9fbec46
commit
5d5e469347
14 changed files with 254 additions and 85 deletions
|
@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
|
||||||
### Improvements
|
### Improvements
|
||||||
- Add support for EMAIL_USE_SSL environment variable (#685)
|
- Add support for EMAIL_USE_SSL environment variable (#685)
|
||||||
- Switch from requests to pycurl
|
- Switch from requests to pycurl
|
||||||
|
- Implement documentation search
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
- Fix the handling of TooManyRedirects exceptions
|
- Fix the handling of TooManyRedirects exceptions
|
||||||
|
|
|
@ -333,3 +333,7 @@ class AddGotifyForm(forms.Form):
|
||||||
|
|
||||||
def get_value(self):
|
def get_value(self):
|
||||||
return json.dumps(dict(self.cleaned_data), sort_keys=True)
|
return json.dumps(dict(self.cleaned_data), sort_keys=True)
|
||||||
|
|
||||||
|
|
||||||
|
class SearchForm(forms.Form):
|
||||||
|
q = forms.RegexField(regex=r"^[0-9a-zA-Z\s]{3,100}$")
|
||||||
|
|
38
hc/front/management/commands/populate_searchdb.py
Normal file
38
hc/front/management/commands/populate_searchdb.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from hc.front.views import _replace_placeholders
|
||||||
|
from hc.lib.html import html2text
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Renders Markdown to HTML"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
con = sqlite3.connect(os.path.join(settings.BASE_DIR, "search.db"))
|
||||||
|
cur = con.cursor()
|
||||||
|
cur.execute("DROP TABLE IF EXISTS docs")
|
||||||
|
cur.execute(
|
||||||
|
"""CREATE VIRTUAL TABLE docs USING FTS5(slug, title, body, tokenize="trigram")"""
|
||||||
|
)
|
||||||
|
|
||||||
|
docs_path = os.path.join(settings.BASE_DIR, "templates/docs")
|
||||||
|
for filename in os.listdir(docs_path):
|
||||||
|
if not filename.endswith(".html"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
slug = filename[:-5] # cut ".html"
|
||||||
|
print("Processing %s" % slug)
|
||||||
|
|
||||||
|
html = open(os.path.join(docs_path, filename), "r").read()
|
||||||
|
html = _replace_placeholders(slug, html)
|
||||||
|
|
||||||
|
lines = html.split("\n")
|
||||||
|
title = html2text(lines[0])
|
||||||
|
text = html2text("\n".join(lines[1:]), skip_pre=True)
|
||||||
|
|
||||||
|
cur.execute("INSERT INTO docs VALUES (?, ?, ?)", (slug, title, text))
|
||||||
|
|
||||||
|
con.commit()
|
17
hc/front/tests/test_search.py
Normal file
17
hc/front/tests/test_search.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
from hc.test import BaseTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class SearchTestCase(BaseTestCase):
|
||||||
|
def test_it_works(self):
|
||||||
|
r = self.client.get("/docs/search/?q=failure")
|
||||||
|
self.assertContains(
|
||||||
|
r, "You can actively signal a <span>failure</span>", status_code=200
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_it_handles_no_results(self):
|
||||||
|
r = self.client.get("/docs/search/?q=asfghjkl")
|
||||||
|
self.assertContains(r, "Your search query matched no results", status_code=200)
|
||||||
|
|
||||||
|
def test_it_rejects_special_characters(self):
|
||||||
|
r = self.client.get("/docs/search/?q=api/v1")
|
||||||
|
self.assertContains(r, "Your search query matched no results", status_code=200)
|
|
@ -106,5 +106,6 @@ urlpatterns = [
|
||||||
path("projects/<uuid:code>/", include(project_urls)),
|
path("projects/<uuid:code>/", include(project_urls)),
|
||||||
path("docs/", views.serve_doc, name="hc-docs"),
|
path("docs/", views.serve_doc, name="hc-docs"),
|
||||||
path("docs/cron/", views.docs_cron, name="hc-docs-cron"),
|
path("docs/cron/", views.docs_cron, name="hc-docs-cron"),
|
||||||
|
path("docs/search/", views.docs_search, name="hc-docs-search"),
|
||||||
path("docs/<slug:doc>/", views.serve_doc, name="hc-serve-doc"),
|
path("docs/<slug:doc>/", views.serve_doc, name="hc-serve-doc"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,6 +5,7 @@ import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from secrets import token_urlsafe
|
from secrets import token_urlsafe
|
||||||
|
import sqlite3
|
||||||
from urllib.parse import urlencode, urlparse
|
from urllib.parse import urlencode, urlparse
|
||||||
|
|
||||||
from cron_descriptor import ExpressionDescriptor
|
from cron_descriptor import ExpressionDescriptor
|
||||||
|
@ -348,6 +349,29 @@ def dashboard(request):
|
||||||
return render(request, "front/dashboard.html", {})
|
return render(request, "front/dashboard.html", {})
|
||||||
|
|
||||||
|
|
||||||
|
def _replace_placeholders(doc, html):
|
||||||
|
if doc.startswith("self_hosted"):
|
||||||
|
return html
|
||||||
|
|
||||||
|
replaces = {
|
||||||
|
"{{ default_timeout }}": str(int(DEFAULT_TIMEOUT.total_seconds())),
|
||||||
|
"{{ default_grace }}": str(int(DEFAULT_GRACE.total_seconds())),
|
||||||
|
"SITE_NAME": settings.SITE_NAME,
|
||||||
|
"SITE_ROOT": settings.SITE_ROOT,
|
||||||
|
"SITE_HOSTNAME": site_hostname(),
|
||||||
|
"SITE_SCHEME": urlparse(settings.SITE_ROOT).scheme,
|
||||||
|
"PING_ENDPOINT": settings.PING_ENDPOINT,
|
||||||
|
"PING_URL": settings.PING_ENDPOINT + "your-uuid-here",
|
||||||
|
"PING_BODY_LIMIT": str(settings.PING_BODY_LIMIT or 100),
|
||||||
|
"IMG_URL": os.path.join(settings.STATIC_URL, "img/docs"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for placeholder, value in replaces.items():
|
||||||
|
html = html.replace(placeholder, value)
|
||||||
|
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
def serve_doc(request, doc="introduction"):
|
def serve_doc(request, doc="introduction"):
|
||||||
# Filenames in /templates/docs/ consist of lowercase letters and underscores,
|
# Filenames in /templates/docs/ consist of lowercase letters and underscores,
|
||||||
# -- make sure we don't accept anything else
|
# -- make sure we don't accept anything else
|
||||||
|
@ -361,23 +385,7 @@ def serve_doc(request, doc="introduction"):
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
if not doc.startswith("self_hosted"):
|
content = _replace_placeholders(doc, content)
|
||||||
replaces = {
|
|
||||||
"{{ default_timeout }}": str(int(DEFAULT_TIMEOUT.total_seconds())),
|
|
||||||
"{{ default_grace }}": str(int(DEFAULT_GRACE.total_seconds())),
|
|
||||||
"SITE_NAME": settings.SITE_NAME,
|
|
||||||
"SITE_ROOT": settings.SITE_ROOT,
|
|
||||||
"SITE_HOSTNAME": site_hostname(),
|
|
||||||
"SITE_SCHEME": urlparse(settings.SITE_ROOT).scheme,
|
|
||||||
"PING_ENDPOINT": settings.PING_ENDPOINT,
|
|
||||||
"PING_URL": settings.PING_ENDPOINT + "your-uuid-here",
|
|
||||||
"PING_BODY_LIMIT": str(settings.PING_BODY_LIMIT or 100),
|
|
||||||
"IMG_URL": os.path.join(settings.STATIC_URL, "img/docs"),
|
|
||||||
}
|
|
||||||
|
|
||||||
for placeholder, value in replaces.items():
|
|
||||||
content = content.replace(placeholder, value)
|
|
||||||
|
|
||||||
ctx = {
|
ctx = {
|
||||||
"page": "docs",
|
"page": "docs",
|
||||||
"section": doc,
|
"section": doc,
|
||||||
|
@ -388,6 +396,30 @@ def serve_doc(request, doc="introduction"):
|
||||||
return render(request, "front/docs_single.html", ctx)
|
return render(request, "front/docs_single.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def docs_search(request):
|
||||||
|
form = forms.SearchForm(request.GET)
|
||||||
|
if not form.is_valid():
|
||||||
|
ctx = {"results": []}
|
||||||
|
return render(request, "front/docs_search.html", ctx)
|
||||||
|
|
||||||
|
query = """
|
||||||
|
SELECT slug, title, snippet(docs, 2, '<span>', '</span>', '…', 50)
|
||||||
|
FROM docs
|
||||||
|
WHERE docs MATCH ?
|
||||||
|
ORDER BY bm25(docs, 2.0, 10.0, 1.0)
|
||||||
|
LIMIT 8
|
||||||
|
"""
|
||||||
|
|
||||||
|
q = form.cleaned_data["q"]
|
||||||
|
con = sqlite3.connect(os.path.join(settings.BASE_DIR, "search.db"))
|
||||||
|
cur = con.cursor()
|
||||||
|
res = cur.execute(query, (q,))
|
||||||
|
|
||||||
|
ctx = {"results": res.fetchall()}
|
||||||
|
return render(request, "front/docs_search.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
def docs_cron(request):
|
def docs_cron(request):
|
||||||
return render(request, "front/docs_cron.html", {})
|
return render(request, "front/docs_cron.html", {})
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,14 @@ class TextOnlyParser(HTMLParser):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.active = True
|
self.active = True
|
||||||
self.buf = []
|
self.buf = []
|
||||||
|
self.skiplist = set(["script", "style"])
|
||||||
|
|
||||||
def handle_starttag(self, tag, attrs):
|
def handle_starttag(self, tag, attrs):
|
||||||
if tag in ("script", "style"):
|
if tag in self.skiplist:
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|
||||||
def handle_endtag(self, tag):
|
def handle_endtag(self, tag):
|
||||||
if tag in ("script", "style"):
|
if tag in self.skiplist:
|
||||||
self.active = True
|
self.active = True
|
||||||
|
|
||||||
def handle_data(self, data):
|
def handle_data(self, data):
|
||||||
|
@ -24,7 +25,10 @@ class TextOnlyParser(HTMLParser):
|
||||||
return " ".join(messy.split())
|
return " ".join(messy.split())
|
||||||
|
|
||||||
|
|
||||||
def html2text(html):
|
def html2text(html, skip_pre=False):
|
||||||
parser = TextOnlyParser()
|
parser = TextOnlyParser()
|
||||||
|
if skip_pre:
|
||||||
|
parser.skiplist.add("pre")
|
||||||
|
|
||||||
parser.feed(html)
|
parser.feed(html)
|
||||||
return parser.get_text()
|
return parser.get_text()
|
||||||
|
|
BIN
search.db
Normal file
BIN
search.db
Normal file
Binary file not shown.
23
static/css/search.css
Normal file
23
static/css/search.css
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
#docs-search-form {
|
||||||
|
margin-right: 20%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-results {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-results.on {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docs-nav.off {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-results li span {
|
||||||
|
background: #ffef82;
|
||||||
|
border-radius: 2px;
|
||||||
|
color: #111;
|
||||||
|
}
|
31
static/js/search.js
Normal file
31
static/js/search.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
$(function() {
|
||||||
|
var base = document.getElementById("base-url").getAttribute("href").slice(0, -1);
|
||||||
|
var input = $("#docs-search");
|
||||||
|
|
||||||
|
input.on("keyup focus", function() {
|
||||||
|
var q = this.value;
|
||||||
|
if (q.length < 3) {
|
||||||
|
$("#search-results").removeClass("on");
|
||||||
|
$("#docs-nav").removeClass("off");
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: base + "/docs/search/",
|
||||||
|
type: "get",
|
||||||
|
data: {q: q},
|
||||||
|
success: function(data) {
|
||||||
|
if (q != input.val()) {
|
||||||
|
return; // ignore stale results
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#search-results").html(data).addClass("on");
|
||||||
|
$("#docs-nav").addClass("off");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// input.on("blur", function() {
|
||||||
|
// $("#search-results").removeClass("on");
|
||||||
|
// });
|
||||||
|
});
|
|
@ -52,6 +52,7 @@
|
||||||
<link rel="stylesheet" href="{% static 'css/profile.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/profile.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/projects.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/projects.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/radio.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/radio.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/search.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/settings.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/settings.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/snippet-copy.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/snippet-copy.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/syntax.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/syntax.css' %}" type="text/css">
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
{% load hc_extras %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-3">
|
|
||||||
|
|
||||||
<ul class="docs-nav">
|
|
||||||
<li class="nav-header">{{ site_name }}</li>
|
|
||||||
<li {% if section == "introduction" %} class="active"{% endif %}>
|
|
||||||
<a href="{% url 'hc-docs' %}">Introduction</a>
|
|
||||||
</li>
|
|
||||||
{% include "front/docs_nav_item.html" with slug="configuring_checks" title="Configuring checks" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="configuring_notifications" title="Configuring notifications" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="projects_teams" title="Projects and teams" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="badges" title="Badges" %}
|
|
||||||
|
|
||||||
<li class="nav-header">API</li>
|
|
||||||
{% include "front/docs_nav_item.html" with slug="http_api" title="Pinging API" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="api" title="Management API" %}
|
|
||||||
|
|
||||||
<li class="nav-header">Pinging Examples</li>
|
|
||||||
{% include "front/docs_nav_item.html" with slug="reliability_tips" title="Reliability Tips" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="bash" title="Shell scripts" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="python" title="Python" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="ruby" title="Ruby" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="php" title="PHP" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="go" title="Go" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="csharp" title="C#" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="javascript" title="Javascript" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="powershell" title="PowerShell" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="email" title="Email" %}
|
|
||||||
|
|
||||||
<li class="nav-header">Guides</li>
|
|
||||||
{% include "front/docs_nav_item.html" with slug="monitoring_cron_jobs" title="Monitoring cron jobs" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="signaling_failures" title="Signaling failures" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="measuring_script_run_time" title="Measuring script run time" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="attaching_logs" title="Attaching logs" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="cloning_checks" title="Cloning checks" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="configuring_prometheus" title="Configuring Prometheus" %}
|
|
||||||
|
|
||||||
<li class="nav-header">Developer Tools</li>
|
|
||||||
{% include "front/docs_nav_item.html" with slug="resources" title="Third-party resources" %}
|
|
||||||
|
|
||||||
<li class="nav-header">Self-hosted</li>
|
|
||||||
{% include "front/docs_nav_item.html" with slug="self_hosted" title="Overview" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="self_hosted_configuration" title="Configuration" %}
|
|
||||||
{% include "front/docs_nav_item.html" with slug="self_hosted_docker" title="Running with Docker" %}
|
|
||||||
|
|
||||||
<li class="nav-header">Reference</li>
|
|
||||||
<li><a href="{% url 'hc-docs-cron' %}">Cron syntax cheatsheet</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
{% block docs_content %}
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
14
templates/front/docs_search.html
Normal file
14
templates/front/docs_search.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<ul class="docs-nav">
|
||||||
|
<li class="nav-header">Search Results</li>
|
||||||
|
{% if results %}
|
||||||
|
{% for slug, title, snippet in results %}
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'hc-serve-doc' slug %}">{{ title|safe }}</a>
|
||||||
|
<p>{{ snippet|safe }}</p>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<li>Your search query matched no results.</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
|
@ -1,12 +1,75 @@
|
||||||
{% extends "front/base_docs.html" %}
|
{% extends "base.html" %}
|
||||||
{% load compress static hc_extras %}
|
{% load compress hc_extras static %}
|
||||||
|
|
||||||
{% block title %}{{ first_line|striptags }} - {{ site_name }}{% endblock %}
|
{% block title %}{{ first_line|striptags }} - {{ site_name }}{% endblock %}
|
||||||
{% block description %}{% endblock %}
|
{% block description %}{% endblock %}
|
||||||
{% block keywords %}{% endblock %}
|
{% block keywords %}{% endblock %}
|
||||||
|
|
||||||
{% block docs_content %}
|
{% block content %}
|
||||||
<div class="docs-content docs-{{ section }}">{{ content|safe }}</div>
|
<div class="row">
|
||||||
|
<div class="col-sm-3">
|
||||||
|
|
||||||
|
<div id="docs-search-form">
|
||||||
|
<input
|
||||||
|
id="docs-search"
|
||||||
|
type="text"
|
||||||
|
name="q"
|
||||||
|
class="form-control input-sm"
|
||||||
|
placeholder="Search docs…"
|
||||||
|
autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<div id="search-results"></div>
|
||||||
|
<ul id="docs-nav" class="docs-nav">
|
||||||
|
<li class="nav-header">{{ site_name }}</li>
|
||||||
|
<li {% if section == "introduction" %} class="active"{% endif %}>
|
||||||
|
<a href="{% url 'hc-docs' %}">Introduction</a>
|
||||||
|
</li>
|
||||||
|
{% include "front/docs_nav_item.html" with slug="configuring_checks" title="Configuring checks" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="configuring_notifications" title="Configuring notifications" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="projects_teams" title="Projects and teams" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="badges" title="Badges" %}
|
||||||
|
|
||||||
|
<li class="nav-header">API</li>
|
||||||
|
{% include "front/docs_nav_item.html" with slug="http_api" title="Pinging API" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="api" title="Management API" %}
|
||||||
|
|
||||||
|
<li class="nav-header">Pinging Examples</li>
|
||||||
|
{% include "front/docs_nav_item.html" with slug="reliability_tips" title="Reliability Tips" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="bash" title="Shell scripts" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="python" title="Python" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="ruby" title="Ruby" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="php" title="PHP" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="go" title="Go" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="csharp" title="C#" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="javascript" title="Javascript" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="powershell" title="PowerShell" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="email" title="Email" %}
|
||||||
|
|
||||||
|
<li class="nav-header">Guides</li>
|
||||||
|
{% include "front/docs_nav_item.html" with slug="monitoring_cron_jobs" title="Monitoring cron jobs" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="signaling_failures" title="Signaling failures" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="measuring_script_run_time" title="Measuring script run time" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="attaching_logs" title="Attaching logs" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="cloning_checks" title="Cloning checks" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="configuring_prometheus" title="Configuring Prometheus" %}
|
||||||
|
|
||||||
|
<li class="nav-header">Developer Tools</li>
|
||||||
|
{% include "front/docs_nav_item.html" with slug="resources" title="Third-party resources" %}
|
||||||
|
|
||||||
|
<li class="nav-header">Self-hosted</li>
|
||||||
|
{% include "front/docs_nav_item.html" with slug="self_hosted" title="Overview" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="self_hosted_configuration" title="Configuration" %}
|
||||||
|
{% include "front/docs_nav_item.html" with slug="self_hosted_docker" title="Running with Docker" %}
|
||||||
|
|
||||||
|
<li class="nav-header">Reference</li>
|
||||||
|
<li><a href="{% url 'hc-docs-cron' %}">Cron syntax cheatsheet</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="docs-content docs-{{ section }}">{{ content|safe }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
|
@ -15,5 +78,6 @@
|
||||||
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||||
<script src="{% static 'js/clipboard.min.js' %}"></script>
|
<script src="{% static 'js/clipboard.min.js' %}"></script>
|
||||||
<script src="{% static 'js/snippet-copy.js' %}"></script>
|
<script src="{% static 'js/snippet-copy.js' %}"></script>
|
||||||
|
<script src="{% static 'js/search.js' %}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Add table
Reference in a new issue