From 50c8c153eaab46eb476b34e886296096aa9ae88a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C4=93teris=20Caune?= <cuu508@gmail.com>
Date: Thu, 23 Jan 2020 16:04:15 +0200
Subject: [PATCH] Documentation in Markdown.

---
 CHANGELOG.md                                  |   1 +
 hc/front/management/commands/render_docs.py   |  29 +++++
 hc/front/urls.py                              |   1 +
 hc/front/views.py                             |  26 +++-
 static/css/channels.css                       |   2 +-
 static/css/docs.css                           |  21 +++-
 templates/base.html                           |   2 +-
 templates/docs/bash.html                      |  34 ++++++
 templates/docs/bash.md                        |  39 ++++++
 templates/docs/csharp.html                    |   7 ++
 templates/docs/csharp.md                      |  10 ++
 templates/docs/email.html                     |  12 ++
 templates/docs/email.md                       |  16 +++
 templates/docs/introduction.html              |  26 ++++
 templates/docs/introduction.md                |  26 ++++
 templates/docs/javascript.html                |  13 ++
 templates/docs/javascript.md                  |  17 +++
 templates/docs/measuring_script_run_time.html |  30 +++++
 templates/docs/measuring_script_run_time.md   |  35 ++++++
 templates/docs/monitoring_cron_jobs.html      | 109 +++++++++++++++++
 templates/docs/monitoring_cron_jobs.md        | 113 ++++++++++++++++++
 templates/docs/php.html                       |   4 +
 templates/docs/php.md                         |   7 ++
 templates/docs/powershell.html                |  23 ++++
 templates/docs/powershell.md                  |  29 +++++
 templates/docs/python.html                    |  32 +++++
 templates/docs/python.md                      |  37 ++++++
 templates/docs/ruby.html                      |   7 ++
 templates/docs/ruby.md                        |  10 ++
 templates/docs/signalling_failures.html       |  24 ++++
 templates/docs/signalling_failures.md         |  28 +++++
 templates/front/base_docs.html                |  38 ++++--
 templates/front/docs_cron.html                |   4 +-
 templates/front/docs_nav_item.html            |   3 +
 templates/front/docs_single.html              |  22 ++++
 35 files changed, 817 insertions(+), 20 deletions(-)
 create mode 100644 hc/front/management/commands/render_docs.py
 create mode 100644 templates/docs/bash.html
 create mode 100644 templates/docs/bash.md
 create mode 100644 templates/docs/csharp.html
 create mode 100644 templates/docs/csharp.md
 create mode 100644 templates/docs/email.html
 create mode 100644 templates/docs/email.md
 create mode 100644 templates/docs/introduction.html
 create mode 100644 templates/docs/introduction.md
 create mode 100644 templates/docs/javascript.html
 create mode 100644 templates/docs/javascript.md
 create mode 100644 templates/docs/measuring_script_run_time.html
 create mode 100644 templates/docs/measuring_script_run_time.md
 create mode 100644 templates/docs/monitoring_cron_jobs.html
 create mode 100644 templates/docs/monitoring_cron_jobs.md
 create mode 100644 templates/docs/php.html
 create mode 100644 templates/docs/php.md
 create mode 100644 templates/docs/powershell.html
 create mode 100644 templates/docs/powershell.md
 create mode 100644 templates/docs/python.html
 create mode 100644 templates/docs/python.md
 create mode 100644 templates/docs/ruby.html
 create mode 100644 templates/docs/ruby.md
 create mode 100644 templates/docs/signalling_failures.html
 create mode 100644 templates/docs/signalling_failures.md
 create mode 100644 templates/front/docs_nav_item.html
 create mode 100644 templates/front/docs_single.html

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 545140f1..24db3641 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
 - Show sub-second durations with higher precision, 2 digits after decimal point (#321)
 - Replace the gear icon with three horizontal dots icon (#322)
 - Add a Pause button in the checks list (#312)
+- Documentation in Markdown
 
 ### Bug Fixes
 - Increase the allowable length of Matrix room alias to 100 (#320)
diff --git a/hc/front/management/commands/render_docs.py b/hc/front/management/commands/render_docs.py
new file mode 100644
index 00000000..3415da87
--- /dev/null
+++ b/hc/front/management/commands/render_docs.py
@@ -0,0 +1,29 @@
+import os
+
+from django.conf import settings
+from django.core.management.base import BaseCommand
+import markdown
+
+
+class Command(BaseCommand):
+    help = "Renders Markdown to HTML"
+
+    def handle(self, *args, **options):
+        extensions = ["fenced_code", "codehilite", "tables"]
+        ec = {"codehilite": {"css_class": "highlight"}}
+
+        docs_path = os.path.join(settings.BASE_DIR, "templates/docs")
+        for doc in os.listdir(docs_path):
+            if not doc.endswith(".md"):
+                continue
+
+            print("Rendering %s" % doc)
+
+            src_path = os.path.join(docs_path, doc)
+            dst_path = os.path.join(docs_path, doc[:-3] + ".html")
+
+            text = open(src_path, "r", encoding="utf-8").read()
+            html = markdown.markdown(text, extensions=extensions, extension_configs=ec)
+
+            with open(dst_path, "w", encoding="utf-8") as f:
+                f.write(html)
diff --git a/hc/front/urls.py b/hc/front/urls.py
index 68df1844..55142d5b 100644
--- a/hc/front/urls.py
+++ b/hc/front/urls.py
@@ -76,4 +76,5 @@ urlpatterns = [
     path("docs/api/", views.docs_api, name="hc-docs-api"),
     path("docs/cron/", views.docs_cron, name="hc-docs-cron"),
     path("docs/resources/", views.docs_resources, name="hc-docs-resources"),
+    path("docs/<slug:doc>/", views.serve_doc, name="hc-serve-doc"),
 ]
diff --git a/hc/front/views.py b/hc/front/views.py
index 93474128..c34c53a8 100644
--- a/hc/front/views.py
+++ b/hc/front/views.py
@@ -1,5 +1,6 @@
 from datetime import datetime, timedelta as td
 import json
+import os
 from urllib.parse import urlencode
 
 from croniter import croniter
@@ -276,6 +277,28 @@ def docs(request):
     return render(request, "front/docs.html", ctx)
 
 
+def serve_doc(request, doc):
+    path = os.path.join(settings.BASE_DIR, "templates/docs", doc + ".html")
+    if not os.path.exists(path):
+        raise Http404("not found")
+
+    content = open(path, "r", encoding="utf-8").read()
+    content = content.replace("SITE_NAME", settings.SITE_NAME)
+    content = content.replace("PING_URL", settings.PING_ENDPOINT + "your-uuid-here")
+    content = content.replace(
+        "PING_EMAIL", "your-uuid-here@%s" % settings.PING_EMAIL_DOMAIN
+    )
+
+    ctx = {
+        "page": "docs",
+        "section": "home",
+        "section": doc,
+        "content": content,
+    }
+
+    return render(request, "front/docs_single.html", ctx)
+
+
 def docs_api(request):
     ctx = {
         "page": "docs",
@@ -290,8 +313,7 @@ def docs_api(request):
 
 
 def docs_cron(request):
-    ctx = {"page": "docs", "section": "cron"}
-    return render(request, "front/docs_cron.html", ctx)
+    return render(request, "front/docs_cron.html", {})
 
 
 def docs_resources(request):
diff --git a/static/css/channels.css b/static/css/channels.css
index ad21fdfc..b4f2c3f9 100644
--- a/static/css/channels.css
+++ b/static/css/channels.css
@@ -192,7 +192,7 @@ table.channels-table > tbody > tr > th {
     float: left;
     font-size: 24px;
     width: 48px;
-    heigth: 48px;
+    height: 48px;
     background: #7ec1ea;
     border-radius: 32px;
     text-align: center;
diff --git a/static/css/docs.css b/static/css/docs.css
index f6b70a5c..0cbf9752 100644
--- a/static/css/docs.css
+++ b/static/css/docs.css
@@ -4,9 +4,18 @@
     list-style: none;
 }
 
-.docs-nav li a {
+.docs-nav li {
     display: block;
-    padding-bottom: 10px;
+    margin-bottom: 10px;
+}
+
+.docs-nav li.nav-header {
+    color: #333;
+    font-weight: bold;
+}
+
+li + li.nav-header {
+    margin-top: 20px;
 }
 
 .docs-nav li.active > a {
@@ -74,3 +83,11 @@ a.section:hover  {
     border-radius: 4px;
 }
 
+.docs-content li {
+    line-height: 2;
+}
+
+.docs-content img {
+    max-width: 100%;
+    border: 6px solid #DDD;
+}
diff --git a/templates/base.html b/templates/base.html
index 3cde5167..9c460703 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -124,7 +124,7 @@
                 {% endif %}
 
                 <li {% if page == 'docs' %} class="active" {% endif %}>
-                    <a href="{% url 'hc-docs' %}">Docs</a>
+                    <a href="{% url 'hc-serve-doc' 'introduction' %}">Docs</a>
                 </li>
 
                 {% if request.user.is_authenticated %}
diff --git a/templates/docs/bash.html b/templates/docs/bash.html
new file mode 100644
index 00000000..9d2ddd97
--- /dev/null
+++ b/templates/docs/bash.html
@@ -0,0 +1,34 @@
+<h1>Shell scripts</h1>
+<p>You can easily add SITE_NAME monitoring to a shell script. All you
+have to do is make a HTTP request at the end of the script. curl and wget
+are two common command line HTTP clients for that.</p>
+<h2>Using curl</h2>
+<div class="highlight"><pre><span></span><span class="ch">#!/bin/sh</span>
+
+<span class="c1"># Exit immediately if any command exits with a non-zero status:</span>
+<span class="nb">set</span> -e
+
+<span class="c1"># Do the work here</span>
+<span class="nb">echo</span> <span class="s2">&quot;Pretending to to make backups...&quot;</span>
+sleep <span class="m">5</span>
+<span class="nb">echo</span> <span class="s2">&quot;Backup complete!&quot;</span>
+
+<span class="c1"># As the last thing, ping SITE_NAME using curl:</span>
+<span class="hll">curl --retry <span class="m">3</span> PING_URL
+</span></pre></div>
+
+
+<h2>Using wget</h2>
+<div class="highlight"><pre><span></span><span class="ch">#!/bin/sh</span>
+
+<span class="c1"># Exit immediately if any command exits with a non-zero status:</span>
+<span class="nb">set</span> -e
+
+<span class="c1"># Do the work here</span>
+<span class="nb">echo</span> <span class="s2">&quot;Pretending to to generate reports...&quot;</span>
+sleep <span class="m">5</span>
+<span class="nb">echo</span> <span class="s2">&quot;Report generation complete!&quot;</span>
+
+<span class="c1"># As the last thing, ping SITE_NAME using wget:</span>
+<span class="hll">wget PING_URL -O /dev/null
+</span></pre></div>
\ No newline at end of file
diff --git a/templates/docs/bash.md b/templates/docs/bash.md
new file mode 100644
index 00000000..05f9b660
--- /dev/null
+++ b/templates/docs/bash.md
@@ -0,0 +1,39 @@
+# Shell scripts
+
+You can easily add SITE_NAME monitoring to a shell script. All you
+have to do is make a HTTP request at the end of the script. curl and wget
+are two common command line HTTP clients for that.
+
+## Using curl
+
+```bash hl_lines="12"
+#!/bin/sh
+
+# Exit immediately if any command exits with a non-zero status:
+set -e
+
+# Do the work here
+echo "Pretending to to make backups..."
+sleep 5
+echo "Backup complete!"
+
+# As the last thing, ping SITE_NAME using curl:
+curl --retry 3 PING_URL
+```
+
+## Using wget
+
+```bash hl_lines="12"
+#!/bin/sh
+
+# Exit immediately if any command exits with a non-zero status:
+set -e
+
+# Do the work here
+echo "Pretending to to generate reports..."
+sleep 5
+echo "Report generation complete!"
+
+# As the last thing, ping SITE_NAME using wget:
+wget PING_URL -O /dev/null
+```
\ No newline at end of file
diff --git a/templates/docs/csharp.html b/templates/docs/csharp.html
new file mode 100644
index 00000000..ebfb87c6
--- /dev/null
+++ b/templates/docs/csharp.html
@@ -0,0 +1,7 @@
+<h1>C</h1>
+<p>Below is an example of making a HTTP request to SITE_NAME from C#.</p>
+<div class="highlight"><pre><span></span><span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="n">System</span><span class="p">.</span><span class="n">Net</span><span class="p">.</span><span class="n">WebClient</span><span class="p">())</span>
+<span class="p">{</span>
+       <span class="n">client</span><span class="p">.</span><span class="n">DownloadString</span><span class="p">(</span><span class="s">&quot;PING_URL&quot;</span><span class="p">);</span>
+<span class="p">}</span>
+</pre></div>
\ No newline at end of file
diff --git a/templates/docs/csharp.md b/templates/docs/csharp.md
new file mode 100644
index 00000000..af60190e
--- /dev/null
+++ b/templates/docs/csharp.md
@@ -0,0 +1,10 @@
+# C#
+
+Below is an example of making a HTTP request to SITE_NAME from C#.
+
+```csharp
+using (var client = new System.Net.WebClient())
+{
+       client.DownloadString("PING_URL");
+}
+```
\ No newline at end of file
diff --git a/templates/docs/email.html b/templates/docs/email.html
new file mode 100644
index 00000000..1d1aab8b
--- /dev/null
+++ b/templates/docs/email.html
@@ -0,0 +1,12 @@
+<h1>Email</h1>
+<p>As an alternative to HTTP/HTTPS requests, you can "ping" checks by
+sending an emails to special email addresses.</p>
+<h2>Use Case: Email Delivery Monitoring</h2>
+<p>Consider a cron job which runs weekly and sends weekly email
+reports to a list of e-mail addresses. You have already set up a check to get alerted
+when your cron job fails to run. But what you ultimately want to check is if
+<strong>your emails are getting sent and delivered</strong>.</p>
+<p>The solution: set up another check, and add its email address to your list of
+recipient email addresses. Set its Period to 1 week. As long as your weekly email
+script runs correctly, and there are no email delivery issues,
+SITE_NAME will regularly receive an email, and the check and will stay up.</p>
\ No newline at end of file
diff --git a/templates/docs/email.md b/templates/docs/email.md
new file mode 100644
index 00000000..352787c8
--- /dev/null
+++ b/templates/docs/email.md
@@ -0,0 +1,16 @@
+# Email
+
+As an alternative to HTTP/HTTPS requests, you can "ping" checks by
+sending an emails to special email addresses.
+
+## Use Case: Email Delivery Monitoring
+
+Consider a cron job which runs weekly and sends weekly email
+reports to a list of e-mail addresses. You have already set up a check to get alerted
+when your cron job fails to run. But what you ultimately want to check is if
+**your emails are getting sent and delivered**.
+
+The solution: set up another check, and add its email address to your list of
+recipient email addresses. Set its Period to 1 week. As long as your weekly email
+script runs correctly, and there are no email delivery issues,
+SITE_NAME will regularly receive an email, and the check and will stay up.
diff --git a/templates/docs/introduction.html b/templates/docs/introduction.html
new file mode 100644
index 00000000..869f03f0
--- /dev/null
+++ b/templates/docs/introduction.html
@@ -0,0 +1,26 @@
+<h2>SITE_NAME</h2>
+<p>SITE_NAME is a service for monitoring cron jobs and similar periodic processes:</p>
+<ul>
+<li>SITE_NAME <strong>listens for pings</strong> from services being monitored.</li>
+<li>It <strong>keeps silent</strong> as long as pings arrive on time.</li>
+<li>It <strong>raises an alert</strong> as soon as a ping does not arrive on time.</li>
+</ul>
+<p>SITE_NAME works as a <a href="https://en.wikipedia.org/wiki/Dead_man%27s_switch">dead man's switch</a> for processes that need to
+run continuously or on regular, known schedule:</p>
+<ul>
+<li>filesystem, database backups</li>
+<li>task queues</li>
+<li>database replication status</li>
+<li>report generation scripts</li>
+<li>periodic data import and sync jobs</li>
+<li>periodic antivirus scans</li>
+<li>DDNS updater scripts</li>
+<li>SSL renewal scripts</li>
+</ul>
+<p>SITE_NAME is <em>not</em> the right tool for:</p>
+<ul>
+<li>monitoring website uptime by probing it with HTTP requests</li>
+<li>collecting application performance metrics</li>
+<li>error tracking</li>
+<li>log aggregation</li>
+</ul>
\ No newline at end of file
diff --git a/templates/docs/introduction.md b/templates/docs/introduction.md
new file mode 100644
index 00000000..0b362572
--- /dev/null
+++ b/templates/docs/introduction.md
@@ -0,0 +1,26 @@
+## SITE_NAME
+
+SITE_NAME is a service for monitoring cron jobs and similar periodic processes:
+
+* SITE_NAME **listens for pings** from services being monitored.
+* It **keeps silent** as long as pings arrive on time.
+* It **raises an alert** as soon as a ping does not arrive on time.
+
+SITE_NAME works as a [dead man's switch](https://en.wikipedia.org/wiki/Dead_man%27s_switch) for processes that need to
+run continuously or on regular, known schedule:
+
+* filesystem, database backups
+* task queues
+* database replication status
+* report generation scripts
+* periodic data import and sync jobs
+* periodic antivirus scans
+* DDNS updater scripts
+* SSL renewal scripts
+
+SITE_NAME is *not* the right tool for:
+
+* monitoring website uptime by probing it with HTTP requests
+* collecting application performance metrics
+* error tracking
+* log aggregation
diff --git a/templates/docs/javascript.html b/templates/docs/javascript.html
new file mode 100644
index 00000000..2dd7ee40
--- /dev/null
+++ b/templates/docs/javascript.html
@@ -0,0 +1,13 @@
+<h1>Javascript</h1>
+<p>Below is an example of making a HTTP request to SITE_NAME from Node.js.</p>
+<div class="highlight"><pre><span></span><span class="kd">var</span> <span class="nx">https</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;https&#39;</span><span class="p">);</span>
+<span class="nx">https</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s2">&quot;PING_URL&quot;</span><span class="p">);</span>
+</pre></div>
+
+
+<p>You can also send pings from a browser environment. SITE_NAME sets the
+<code>Access-Control-Allow-Origin:*</code> CORS header, so cross-domain AJAX requests work.</p>
+<div class="highlight"><pre><span></span><span class="kd">var</span> <span class="nx">xhr</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
+<span class="nx">xhr</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="s1">&#39;GET&#39;</span><span class="p">,</span> <span class="s1">&#39;PING_URL&#39;</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
+<span class="nx">xhr</span><span class="p">.</span><span class="nx">send</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>
+</pre></div>
\ No newline at end of file
diff --git a/templates/docs/javascript.md b/templates/docs/javascript.md
new file mode 100644
index 00000000..28881533
--- /dev/null
+++ b/templates/docs/javascript.md
@@ -0,0 +1,17 @@
+# Javascript
+
+Below is an example of making a HTTP request to SITE_NAME from Node.js.
+
+```js
+var https = require('https');
+https.get("PING_URL");
+```
+
+You can also send pings from a browser environment. SITE_NAME sets the
+`Access-Control-Allow-Origin:*` CORS header, so cross-domain AJAX requests work.
+
+```js
+var xhr = new XMLHttpRequest();
+xhr.open('GET', 'PING_URL', true);
+xhr.send(null);
+```
diff --git a/templates/docs/measuring_script_run_time.html b/templates/docs/measuring_script_run_time.html
new file mode 100644
index 00000000..1c77ecb7
--- /dev/null
+++ b/templates/docs/measuring_script_run_time.html
@@ -0,0 +1,30 @@
+<h1>Measuring Script Run Time</h1>
+<p>Append <code>/start</code> to a ping URL and use it to signal when a job starts.
+ After receiving a start signal, Healthchecks.io will show the check as "Started".
+ It will store the "start" events and display the job execution times. The job
+ execution times are calculated as the time gaps between adjacent "start" and
+ "complete" events.</p>
+<p>Signalling a start kicks off a separate timer: the job now <strong>must</strong> signal a
+success within its configured "Grace Time", or it will get marked as "down".</p>
+<p>Below is a code example in Python:</p>
+<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">requests</span>
+<span class="n">URL</span> <span class="o">=</span> <span class="s2">&quot;PING_URL&quot;</span>
+
+
+<span class="c1"># &quot;/start&quot; kicks off a timer: if the job takes longer than</span>
+<span class="c1"># the configured grace time, the check will be marked as &quot;down&quot;</span>
+<span class="k">try</span><span class="p">:</span>
+    <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">URL</span> <span class="o">+</span> <span class="s2">&quot;/start&quot;</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>
+<span class="k">except</span> <span class="n">requests</span><span class="o">.</span><span class="n">exceptions</span><span class="o">.</span><span class="n">RequestException</span><span class="p">:</span>
+    <span class="c1"># If the network request fails for any reason, we don&#39;t want</span>
+    <span class="c1"># it to prevent the main job from running</span>
+    <span class="k">pass</span>
+
+
+<span class="c1"># TODO: run the job here</span>
+<span class="n">fib</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="k">if</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="mi">2</span> <span class="k">else</span> <span class="n">fib</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">fib</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">2</span><span class="p">)</span>
+<span class="k">print</span><span class="p">(</span><span class="s2">&quot;F(42) = </span><span class="si">%d</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="n">fib</span><span class="p">(</span><span class="mi">42</span><span class="p">))</span>
+
+<span class="c1"># Signal success:</span>
+<span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">URL</span><span class="p">)</span>
+</pre></div>
\ No newline at end of file
diff --git a/templates/docs/measuring_script_run_time.md b/templates/docs/measuring_script_run_time.md
new file mode 100644
index 00000000..fb289bf0
--- /dev/null
+++ b/templates/docs/measuring_script_run_time.md
@@ -0,0 +1,35 @@
+# Measuring Script Run Time
+
+ Append `/start` to a ping URL and use it to signal when a job starts.
+ After receiving a start signal, Healthchecks.io will show the check as "Started".
+ It will store the "start" events and display the job execution times. The job
+ execution times are calculated as the time gaps between adjacent "start" and
+ "complete" events.
+
+Signalling a start kicks off a separate timer: the job now **must** signal a
+success within its configured "Grace Time", or it will get marked as "down".
+
+Below is a code example in Python:
+
+```python
+import requests
+URL = "PING_URL"
+
+
+# "/start" kicks off a timer: if the job takes longer than
+# the configured grace time, the check will be marked as "down"
+try:
+    requests.get(URL + "/start", timeout=5)
+except requests.exceptions.RequestException:
+    # If the network request fails for any reason, we don't want
+    # it to prevent the main job from running
+    pass
+
+
+# TODO: run the job here
+fib = lambda n: n if n < 2 else fib(n - 1) + fib(n - 2)
+print("F(42) = %d" % fib(42))
+
+# Signal success:
+requests.get(URL)
+```
diff --git a/templates/docs/monitoring_cron_jobs.html b/templates/docs/monitoring_cron_jobs.html
new file mode 100644
index 00000000..eef612db
--- /dev/null
+++ b/templates/docs/monitoring_cron_jobs.html
@@ -0,0 +1,109 @@
+<h2>Monitoring Cron Jobs</h2>
+<p>SITE_NAME is perfectly suited for monitoring cron jobs.
+Let's look at an example: a machine with the following cron job:</p>
+<div class="highlight"><pre><span></span>$ crontab -l
+<span class="c1"># m h dom mon dow command</span>
+  <span class="m">8</span> <span class="m">6</span> * * * /home/user/backup.sh
+</pre></div>
+
+
+<p>You can use SITE_NAME to get a notification whenever the <code>backup.sh</code> script does not
+complete successfully. Here is how to set that up.</p>
+<ol>
+<li>
+<p>If you have not already, sign up for a free SITE_NAME account.</p>
+</li>
+<li>
+<p>In your SITE_NAME account, <strong>add a new check</strong>.</p>
+<p>Note: in SITE_NAME, a <strong>check</strong> represents a single service you want to
+monitor. For example, a single cron job. For each additional cron job you will
+create another check. SITE_NAME pricing plans are structured primarily
+around how many checks you can have in the account.</p>
+</li>
+<li>
+<p>Give the check <strong>a meaningful name</strong>. Good naming will become
+increasingly important as you add more checks to your account.</p>
+</li>
+<li>
+<p>Edit the check's <strong>schedule</strong>:</p>
+<ul>
+<li>change its type from "Simple" to "Cron"</li>
+<li>enter <code>8 6 * * *</code> in the cron epression field</li>
+<li>set the timezone to match your machine's timezone</li>
+</ul>
+</li>
+<li>
+<p>Take note of your check's unique <strong>ping URL</strong></p>
+</li>
+</ol>
+<p>Finally, edit your crontab and append a curl or wget call after the command:</p>
+<div class="highlight"><pre><span></span>$ crontab -e
+<span class="c1"># m h dom mon dow command</span>
+  <span class="m">8</span> <span class="m">6</span> * * * /home/user/backup.sh <span class="o">&amp;&amp;</span> curl -fsS --retry <span class="m">3</span> PING_URL &gt; /dev/null
+</pre></div>
+
+
+<p>Now, each time your cron job runs, it will send a HTTP request to the ping URL.</p>
+<p>Since SITE_NAME knows the schedule of your cron job, it can calculate
+the dates and times when the job should run. As soon as your cron job doesn't
+report at an expected time, SITE_NAME will send you a notification.</p>
+<p>This monitoring technique takes care of various failure scenarios that could
+potentially go unnoticed otherwise:</p>
+<ul>
+<li>The whole machine goes down (power outage, janitor stumbles on wires, VPS provider problems, etc.)</li>
+<li>cron daemon is not running, or has invalid configuration</li>
+<li>cron does start your task, but the task exits with non-zero exit code</li>
+</ul>
+<h2>Curl Options</h2>
+<p>The extra options tells curl to not print anything to standard output unless
+there is an error. Feel free to adjust the curl options to suit your needs.</p>
+<table class="table curl-opts">
+    <tr>
+        <th>&amp;&amp;</th>
+        <td>Run curl only if <code>/home/user/backup.sh</code> exits with an exit code 0</td>
+    </tr>
+    <tr>
+        <th>
+            -f,  --fail
+        </th>
+        <td>Makes curl treat non-200 responses as errors</td>
+    </tr>
+    <tr>
+        <th>-s, --silent</th>
+        <td>Silent or quiet mode. Don't show progress meter or error messages.</td>
+    </tr>
+    <tr>
+        <th>-S, --show-error</th>
+        <td>When used with -s it makes curl show error message if it fails.</td>
+    </tr>
+    <tr>
+        <th>--retry &lt;num&gt;</th>
+        <td>
+            If a transient error is returned when curl tries to perform a
+            transfer, it will retry this number of times before  giving  up.
+            Setting  the number  to  0  makes curl do no retries
+            (which is the default). Transient error means either: a timeout,
+            an FTP 4xx response code or an HTTP 5xx response code.
+        </td>
+    </tr>
+    <tr>
+        <th>&gt; /dev/null</th>
+        <td>
+            Redirect curl's stdout to /dev/null (error messages go to stderr,)
+        </td>
+    </tr>
+</table>
+
+<h2>Looking up Your Machine's Time Zone</h2>
+<p>On modern GNU/Linux systems, you can look up the time zone using the
+<code>timedatectl status</code> command and looking for "Time zone" in its output:</p>
+<div class="highlight"><pre><span></span>$ timedatectl status
+
+               Local time: C  2020-01-23 12:35:50 EET
+           Universal time: C  2020-01-23 10:35:50 UTC
+                 RTC time: C  2020-01-23 10:35:50
+<span class="hll">                Time zone: Europe/Riga (EET, +0200)
+</span>System clock synchronized: yes
+              NTP service: active
+          RTC in local TZ: no
+</pre></div>
\ No newline at end of file
diff --git a/templates/docs/monitoring_cron_jobs.md b/templates/docs/monitoring_cron_jobs.md
new file mode 100644
index 00000000..a286e378
--- /dev/null
+++ b/templates/docs/monitoring_cron_jobs.md
@@ -0,0 +1,113 @@
+## Monitoring Cron Jobs
+
+SITE_NAME is perfectly suited for monitoring cron jobs.
+Let's look at an example: a machine with the following cron job:
+
+```bash
+$ crontab -l
+# m h dom mon dow command
+  8 6 * * * /home/user/backup.sh
+```
+
+You can use SITE_NAME to get a notification whenever the `backup.sh` script does not
+complete successfully. Here is how to set that up.
+
+1. If you have not already, sign up for a free SITE_NAME account.
+
+1. In your SITE_NAME account, **add a new check**.
+
+    Note: in SITE_NAME, a **check** represents a single service you want to
+    monitor. For example, a single cron job. For each additional cron job you will
+    create another check. SITE_NAME pricing plans are structured primarily
+    around how many checks you can have in the account.
+
+1. Give the check **a meaningful name**. Good naming will become
+increasingly important as you add more checks to your account.
+
+1. Edit the check's **schedule**:
+
+    * change its type from "Simple" to "Cron"
+    * enter `8 6 * * *` in the cron epression field
+    * set the timezone to match your machine's timezone
+
+1. Take note of your check's unique **ping URL**
+
+Finally, edit your crontab and append a curl or wget call after the command:
+
+```bash
+$ crontab -e
+# m h dom mon dow command
+  8 6 * * * /home/user/backup.sh && curl -fsS --retry 3 PING_URL > /dev/null
+```
+
+Now, each time your cron job runs, it will send a HTTP request to the ping URL.
+
+Since SITE_NAME knows the schedule of your cron job, it can calculate
+the dates and times when the job should run. As soon as your cron job doesn't
+report at an expected time, SITE_NAME will send you a notification.
+
+This monitoring technique takes care of various failure scenarios that could
+potentially go unnoticed otherwise:
+
+* The whole machine goes down (power outage, janitor stumbles on wires, VPS provider problems, etc.)
+* cron daemon is not running, or has invalid configuration
+* cron does start your task, but the task exits with non-zero exit code
+
+## Curl Options
+
+The extra options tells curl to not print anything to standard output unless
+there is an error. Feel free to adjust the curl options to suit your needs.
+
+<table class="table curl-opts">
+    <tr>
+        <th>&amp;&amp;</th>
+        <td>Run curl only if <code>/home/user/backup.sh</code> exits with an exit code 0</td>
+    </tr>
+    <tr>
+        <th>
+            -f,  --fail
+        </th>
+        <td>Makes curl treat non-200 responses as errors</td>
+    </tr>
+    <tr>
+        <th>-s, --silent</th>
+        <td>Silent or quiet mode. Don't show progress meter or error messages.</td>
+    </tr>
+    <tr>
+        <th>-S, --show-error</th>
+        <td>When used with -s it makes curl show error message if it fails.</td>
+    </tr>
+    <tr>
+        <th>--retry &lt;num&gt;</th>
+        <td>
+            If a transient error is returned when curl tries to perform a
+            transfer, it will retry this number of times before  giving  up.
+            Setting  the number  to  0  makes curl do no retries
+            (which is the default). Transient error means either: a timeout,
+            an FTP 4xx response code or an HTTP 5xx response code.
+        </td>
+    </tr>
+    <tr>
+        <th>&gt; /dev/null</th>
+        <td>
+            Redirect curl's stdout to /dev/null (error messages go to stderr,)
+        </td>
+    </tr>
+</table>
+
+## Looking up Your Machine's Time Zone
+
+On modern GNU/Linux systems, you can look up the time zone using the
+`timedatectl status` command and looking for "Time zone" in its output:
+
+```text hl_lines="6"
+$ timedatectl status
+
+               Local time: C  2020-01-23 12:35:50 EET
+           Universal time: C  2020-01-23 10:35:50 UTC
+                 RTC time: C  2020-01-23 10:35:50
+                Time zone: Europe/Riga (EET, +0200)
+System clock synchronized: yes
+              NTP service: active
+          RTC in local TZ: no
+```
\ No newline at end of file
diff --git a/templates/docs/php.html b/templates/docs/php.html
new file mode 100644
index 00000000..0901a278
--- /dev/null
+++ b/templates/docs/php.html
@@ -0,0 +1,4 @@
+<h1>PHP</h1>
+<p>Below is an example of making a HTTP request to SITE_NAME from PHP.</p>
+<div class="highlight"><pre><span></span><span class="x">file_get_contents(&#39;https://hc-ping.com/your-uuid-here&#39;);</span>
+</pre></div>
\ No newline at end of file
diff --git a/templates/docs/php.md b/templates/docs/php.md
new file mode 100644
index 00000000..be674a48
--- /dev/null
+++ b/templates/docs/php.md
@@ -0,0 +1,7 @@
+# PHP
+
+Below is an example of making a HTTP request to SITE_NAME from PHP.
+
+```php
+file_get_contents('https://hc-ping.com/your-uuid-here');
+```
\ No newline at end of file
diff --git a/templates/docs/powershell.html b/templates/docs/powershell.html
new file mode 100644
index 00000000..f14dfe4b
--- /dev/null
+++ b/templates/docs/powershell.html
@@ -0,0 +1,23 @@
+<h1>PowerShell</h1>
+<p>You can use <a href="https://msdn.microsoft.com/en-us/powershell/mt173057.aspx">PowerShell</a>
+ and Windows Task Scheduler to automate various tasks on a Windows system.
+ From within a PowerShell script it is also easy to ping SITE_NAME.</p>
+<p>Here is a simple PowerShell script that pings SITE_NAME. When scheduled to
+run with Task Scheduler, it will essentially just send regular "I'm alive" messages.
+You can of course extend it to do more things.</p>
+<div class="highlight"><pre><span></span><span class="c1"># inside a PowerShell script:</span>
+Invoke-RestMethod PING_URL
+</pre></div>
+
+
+<p>Save the above to e.g. <code>C:\Scripts\healthchecks.ps1</code>.
+Then use the following command in a Scheduled Task to run the script:</p>
+<div class="highlight"><pre><span></span>powershell.exe -ExecutionPolicy bypass -File C:<span class="se">\S</span>cripts<span class="se">\h</span>ealthchecks.ps1
+</pre></div>
+
+
+<p>In simple cases, you can also pass the script to PowerShell directly,
+using the "-command" argument:</p>
+<div class="highlight"><pre><span></span><span class="c1"># Without an underlying script, passing the command to PowerShell directly:</span>
+powershell.exe -command <span class="p">&amp;</span><span class="o">{</span>Invoke-RestMethod PING_URL<span class="o">}</span>
+</pre></div>
\ No newline at end of file
diff --git a/templates/docs/powershell.md b/templates/docs/powershell.md
new file mode 100644
index 00000000..84d51ee8
--- /dev/null
+++ b/templates/docs/powershell.md
@@ -0,0 +1,29 @@
+# PowerShell
+
+ You can use [PowerShell](https://msdn.microsoft.com/en-us/powershell/mt173057.aspx)
+ and Windows Task Scheduler to automate various tasks on a Windows system.
+ From within a PowerShell script it is also easy to ping SITE_NAME.
+
+Here is a simple PowerShell script that pings SITE_NAME. When scheduled to
+run with Task Scheduler, it will essentially just send regular "I'm alive" messages.
+You can of course extend it to do more things.
+
+```bash
+# inside a PowerShell script:
+Invoke-RestMethod PING_URL
+```
+
+Save the above to e.g. `C:\Scripts\healthchecks.ps1`.
+Then use the following command in a Scheduled Task to run the script:
+
+```bash
+powershell.exe -ExecutionPolicy bypass -File C:\Scripts\healthchecks.ps1
+```
+
+In simple cases, you can also pass the script to PowerShell directly,
+using the "-command" argument:
+
+```bash
+# Without an underlying script, passing the command to PowerShell directly:
+powershell.exe -command &{Invoke-RestMethod PING_URL}
+```
diff --git a/templates/docs/python.html b/templates/docs/python.html
new file mode 100644
index 00000000..7a490b98
--- /dev/null
+++ b/templates/docs/python.html
@@ -0,0 +1,32 @@
+<h1>Python</h1>
+<p>If you are already using the requests library, it's convenient to also use it here:</p>
+<div class="highlight"><pre><span></span><span class="c1"># using requests:</span>
+<span class="kn">import</span> <span class="nn">requests</span>
+<span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;PING_URL&quot;</span><span class="p">)</span>
+</pre></div>
+
+
+<p>Otherwise, you can use the urllib standard module.</p>
+<div class="highlight"><pre><span></span><span class="c1"># urllib with python 3.x:</span>
+<span class="kn">import</span> <span class="nn">urllib.request</span>
+<span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="s2">&quot;PING_URL&quot;</span><span class="p">)</span>
+</pre></div>
+
+
+<div class="highlight"><pre><span></span><span class="c1"># urllib with python 2.x:</span>
+<span class="kn">import</span> <span class="nn">urllib</span>
+<span class="n">urllib</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="s2">&quot;PING_URL&quot;</span><span class="p">)</span>
+</pre></div>
+
+
+<p>You can include additional diagnostic information in the in the request body (for POST requests), or in the "User-Agent" request header:</p>
+<div class="highlight"><pre><span></span><span class="c1"># Passing diagnostic information in the POST body:</span>
+<span class="kn">import</span> <span class="nn">requests</span>
+<span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&quot;PING_URL&quot;</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="s2">&quot;temperature=-7&quot;</span><span class="p">)</span>
+</pre></div>
+
+
+<div class="highlight"><pre><span></span><span class="c1"># Passing diagnostic information in the User-Agent header:</span>
+<span class="kn">import</span> <span class="nn">requests</span>
+<span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;PING_URL&quot;</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s2">&quot;User-Agent&quot;</span><span class="p">:</span> <span class="s2">&quot;temperature=-7&quot;</span><span class="p">})</span>
+</pre></div>
\ No newline at end of file
diff --git a/templates/docs/python.md b/templates/docs/python.md
new file mode 100644
index 00000000..809643ca
--- /dev/null
+++ b/templates/docs/python.md
@@ -0,0 +1,37 @@
+# Python
+
+If you are already using the requests library, it's convenient to also use it here:
+
+```python
+# using requests:
+import requests
+requests.get("PING_URL")
+```
+
+Otherwise, you can use the urllib standard module.
+
+```python
+# urllib with python 3.x:
+import urllib.request
+urllib.request.urlopen("PING_URL")
+```
+
+```python
+# urllib with python 2.x:
+import urllib
+urllib.urlopen("PING_URL")
+```
+
+You can include additional diagnostic information in the in the request body (for POST requests), or in the "User-Agent" request header:
+
+```python
+# Passing diagnostic information in the POST body:
+import requests
+requests.post("PING_URL", data="temperature=-7")
+```
+
+```python
+# Passing diagnostic information in the User-Agent header:
+import requests
+requests.get("PING_URL", headers={"User-Agent": "temperature=-7"})
+```
diff --git a/templates/docs/ruby.html b/templates/docs/ruby.html
new file mode 100644
index 00000000..e73e6b57
--- /dev/null
+++ b/templates/docs/ruby.html
@@ -0,0 +1,7 @@
+<h1>Ruby</h1>
+<p>Below is an example of making a HTTP request to SITE_NAME from Ruby.</p>
+<div class="highlight"><pre><span></span><span class="nb">require</span> <span class="s1">&#39;net/http&#39;</span>
+<span class="nb">require</span> <span class="s1">&#39;uri&#39;</span>
+
+<span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="no">URI</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="s1">&#39;PING_URL&#39;</span><span class="p">))</span>
+</pre></div>
\ No newline at end of file
diff --git a/templates/docs/ruby.md b/templates/docs/ruby.md
new file mode 100644
index 00000000..0b3e33f3
--- /dev/null
+++ b/templates/docs/ruby.md
@@ -0,0 +1,10 @@
+# Ruby
+
+Below is an example of making a HTTP request to SITE_NAME from Ruby.
+
+```ruby
+require 'net/http'
+require 'uri'
+
+Net::HTTP.get(URI.parse('PING_URL'))
+```
\ No newline at end of file
diff --git a/templates/docs/signalling_failures.html b/templates/docs/signalling_failures.html
new file mode 100644
index 00000000..3d76b33c
--- /dev/null
+++ b/templates/docs/signalling_failures.html
@@ -0,0 +1,24 @@
+<h1>Signalling failures</h1>
+<p>Append <code>/fail</code> to a ping URL and use it to actively signal a failure.
+Requesting the <code>/fail</code> URL will immediately mark the check as "down".
+You can use this feature to minimize the delay from your monitored service failing
+to you getting a notification.</p>
+<p>Below is a skeleton code example in Python which signals a failure when the
+work function returns an unexpected value or throws an exception:</p>
+<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">requests</span>
+<span class="n">URL</span> <span class="o">=</span> <span class="s2">&quot;PING_URL&quot;</span>
+
+<span class="k">def</span> <span class="nf">do_work</span><span class="p">():</span>
+    <span class="c1"># Do your number crunching, backup dumping, newsletter sending work here.</span>
+    <span class="c1"># Return a truthy value on success.</span>
+    <span class="c1"># Return a falsy value or throw an exception on failure.</span>
+    <span class="k">return</span> <span class="bp">True</span>
+
+<span class="n">success</span> <span class="o">=</span> <span class="bp">False</span>
+<span class="k">try</span><span class="p">:</span>
+    <span class="n">success</span> <span class="o">=</span> <span class="n">do_work</span><span class="p">()</span>
+<span class="k">finally</span><span class="p">:</span>
+    <span class="c1"># On success, requests PING_URL</span>
+    <span class="c1"># On failure, requests PING_URL/fail</span>
+    <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">URL</span> <span class="k">if</span> <span class="n">success</span> <span class="k">else</span> <span class="n">URL</span> <span class="o">+</span> <span class="s2">&quot;/fail&quot;</span><span class="p">)</span>
+</pre></div>
\ No newline at end of file
diff --git a/templates/docs/signalling_failures.md b/templates/docs/signalling_failures.md
new file mode 100644
index 00000000..49fe04f9
--- /dev/null
+++ b/templates/docs/signalling_failures.md
@@ -0,0 +1,28 @@
+# Signalling failures
+
+Append `/fail` to a ping URL and use it to actively signal a failure.
+Requesting the `/fail` URL will immediately mark the check as "down".
+You can use this feature to minimize the delay from your monitored service failing
+to you getting a notification.
+
+Below is a skeleton code example in Python which signals a failure when the
+work function returns an unexpected value or throws an exception:
+
+```python
+import requests
+URL = "PING_URL"
+
+def do_work():
+    # Do your number crunching, backup dumping, newsletter sending work here.
+    # Return a truthy value on success.
+    # Return a falsy value or throw an exception on failure.
+    return True
+
+success = False
+try:
+    success = do_work()
+finally:
+    # On success, requests PING_URL
+    # On failure, requests PING_URL/fail
+    requests.get(URL if success else URL + "/fail")
+```
diff --git a/templates/front/base_docs.html b/templates/front/base_docs.html
index 89c49a3a..f923ab0c 100644
--- a/templates/front/base_docs.html
+++ b/templates/front/base_docs.html
@@ -4,25 +4,39 @@
 
 {% block content %}
 <div class="row">
-    <div class="col-sm-2">
+    <div class="col-sm-3">
 
     <ul class="docs-nav">
-        <li {% if section == "home" %} class="active" {% endif %}>
-            <a href="{% url 'hc-docs' %}">How {% site_name %} Works</a>
+        <li class="nav-header">Monitoring</li>
+        {% include "front/docs_nav_item.html" with slug="introduction" title="Introduction" %}
+        {% include "front/docs_nav_item.html" with slug="monitoring_cron_jobs" title="Monitoring cron jobs" %}
+        {% include "front/docs_nav_item.html" with slug="signalling_failures" title="Signalling failures" %}
+        {% include "front/docs_nav_item.html" with slug="measuring_script_run_time" title="Measuring script run time" %}
+
+        <li class="nav-header">Platforms</li>
+        {% include "front/docs_nav_item.html" with slug="bash" title="Bash" %}
+        {% 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="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">Developer Tools</li>
+        <li{% if section == "api" %} class="active"{% endif %}>
+            <a href="{% url 'hc-docs-api' %}">API</a>
         </li>
-        <li {% if section == "api" %} class="active" {% endif %}>
-            <a href="{% url 'hc-docs-api' %}">API Reference</a>
-        </li>
-        <li {% if section == "cron" %} class="active" {% endif %}>
-            <a href="{% url 'hc-docs-cron' %}">Cron Syntax</a>
-        </li>
-        <li {% if section == "resources" %} class="active" {% endif %}>
-            <a href="{% url 'hc-docs-resources' %}">Third-Party Resources</a>
+        <li {% if section == "resources" %} class="active"{% endif %}>
+            <a href="{% url 'hc-docs-resources' %}">Third-party resources</a>
         </li>
+
+        <li class="nav-header">Cron</li>
+        <li><a href="{% url 'hc-docs-cron' %}">Cron Syntax Cheatsheet</a></li>
     </ul>
 
     </div>
-    <div class="col-sm-10">
+    <div class="col-sm-9">
         {% block docs_content %}
         {% endblock %}
     </div>
diff --git a/templates/front/docs_cron.html b/templates/front/docs_cron.html
index 47a667a1..43927a14 100644
--- a/templates/front/docs_cron.html
+++ b/templates/front/docs_cron.html
@@ -1,4 +1,4 @@
-{% extends "front/base_docs.html" %}
+{% extends "base.html" %}
 {% load hc_extras %}
 
 {% block title %}Cron Syntax Cheatsheet - {% site_name %}{% endblock %}
@@ -11,7 +11,7 @@
     <meta name="keywords" content="cron syntax, crontab syntax, cron howto, cron tutorial, cron jobs, cron job example, monitor cron jobs">
 {% endblock %}
 
-{% block docs_content %}
+{% block content %}
 
 <h2>Cron Syntax Cheatsheet</h2>
 
diff --git a/templates/front/docs_nav_item.html b/templates/front/docs_nav_item.html
new file mode 100644
index 00000000..4613cd12
--- /dev/null
+++ b/templates/front/docs_nav_item.html
@@ -0,0 +1,3 @@
+<li{% if slug == section %} class="active"{% endif %}>
+    <a href="{% url 'hc-serve-doc' slug %}">{{ title }}</a>
+</li>
\ No newline at end of file
diff --git a/templates/front/docs_single.html b/templates/front/docs_single.html
new file mode 100644
index 00000000..7e2a3263
--- /dev/null
+++ b/templates/front/docs_single.html
@@ -0,0 +1,22 @@
+{% extends "front/base_docs.html" %}
+{% load compress static hc_extras %}
+
+{% block title %}Documentation - {% site_name %}{% endblock %}
+{% block description %}
+    <meta name="description" content="Monitor any service that can make a HTTP request or send an email: cron jobs, Bash scripts, Python, Ruby, Node, PHP, JS, ...">
+{% endblock %}
+{% block keywords %}
+    <meta name="keywords" content="healthchecks, crontab monitoring, python health check, bash health check, cron monitoring, cron tutorial, cron howto, api health check, open source">
+{% endblock %}
+
+
+{% block docs_content %}<div class="docs-content">{{ content|safe }}</div>{% endblock %}
+
+{% block scripts %}
+{% compress js %}
+<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
+<script src="{% static 'js/bootstrap.min.js' %}"></script>
+<script src="{% static 'js/clipboard.min.js' %}"></script>
+<script src="{% static 'js/snippet-copy.js' %}"></script>
+{% endcompress %}
+{% endblock %}