diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6c6aca3..30c8c7a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -76,8 +76,7 @@ pages: <<: *pull_cache stage: deploy script: - - pwd - - ls + - sed -e "/Unreleased/,+1d" -i CHANGELOG.md - make docs - echo "https://framasoft.frama.io/framaspace/argos/* https://argos-monitoring.framasoft.org/:splat 301" > public/_redirects artifacts: diff --git a/CHANGELOG.md b/CHANGELOG.md index da118ed..9d84fea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## [Unreleased] +- 💄 — Correctly show results on small screens +- 📝💄 — Add opengraph tags to documentation site (#62) +- 🔨 — Add a small web server to browse documentation when developing +- ✨ — Add new check type: http-to-https (#61) +- 👷 — Remove Unreleased section from CHANGELOG when publishing documentation +- 🩹 — Severity of ssl-certificate-expiration’s errors is now UNKNOWN (#60) +- 💄 — Better display of results’ error details + ## 0.4.1 Date: 2024-09-18 diff --git a/Makefile b/Makefile index 53d3315..9d6bec1 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,8 @@ docs: cog ## Build the docs if [ ! -e "public/mermaid.min.js" ]; then curl -sL $$(grep mermaid.min.js public/search.html | cut -f 2 -d '"') --output public/mermaid.min.js; fi sed -e 's@https://unpkg.com/mermaid[^"]*"@mermaid.min.js"@' -i public/search.html public/genindex.html sed -e 's@https://unpkg.com/mermaid[^"]*"@../mermaid.min.js"@' -i public/developer/models.html public/developer/overview.html +docs-webserver: docs + python3 -m http.server -d public -b 127.0.0.1 8001 cog: ## Run cog, to integrate the CLI options to the docs. venv/bin/cog -r docs/*.md test: venv ## Run the tests diff --git a/argos/checks/checks.py b/argos/checks/checks.py index d1fc530..e729f72 100644 --- a/argos/checks/checks.py +++ b/argos/checks/checks.py @@ -4,6 +4,7 @@ import json import re from datetime import datetime +from httpx import URL from jsonpointer import resolve_pointer, JsonPointerException from argos.checks.base import ( @@ -55,6 +56,33 @@ class HTTPStatusIn(BaseCheck): ) +class HTTPToHTTPS(BaseCheck): + """Checks that the HTTP to HTTPS redirection status code is the expected one.""" + + config = "http-to-https" + expected_cls = ExpectedStringValue + + async def run(self) -> dict: + task = self.task + url = URL(task.url).copy_with(scheme="http") + response = await self.http_client.request(method="get", url=url, timeout=60) + + expected_dict = json.loads(self.expected) + expected = range(300, 400) + if "range" in expected_dict: + expected = range(expected_dict["range"][0], expected_dict["range"][1]) + if "value" in expected_dict: + expected = range(expected_dict["value"], expected_dict["value"] + 1) + if "list" in expected_dict: + expected = expected_dict["list"] + + return self.response( + status=response.status_code in expected, + expected=self.expected, + retrieved=response.status_code, + ) + + class HTTPHeadersContain(BaseCheck): """Checks that response headers contains the expected headers (without checking their values)""" @@ -313,6 +341,8 @@ class SSLCertificateExpiration(BaseCheck): @classmethod async def finalize(cls, config, result, **context): + if result.status == Status.ERROR: + return result.status, Severity.UNKNOWN if result.status != Status.ON_CHECK: return result.status, Severity.WARNING diff --git a/argos/config-example.yaml b/argos/config-example.yaml index 763367c..2999e12 100644 --- a/argos/config-example.yaml +++ b/argos/config-example.yaml @@ -107,6 +107,21 @@ websites: - headers-contain: - "content-encoding" - "content-type" + # Check that there is a HTTP to HTTPS redirection with 3xx status code + - http-to-https: true + # Check that there is a HTTP to HTTPS redirection with 301 status code + - http-to-https: 301 + # Check that there is a HTTP to HTTPS redirection with a status code + # in the provided range (stop value excluded) + - http-to-https: + start: 301 + stop: 308 + # Check that there is a HTTP to HTTPS redirection with a status code + # in the provided list + - http-to-https: + - 301 + - 302 + - 307 - path: "/admin/" checks: # Check that the return HTTP status is one of those diff --git a/argos/schemas/config.py b/argos/schemas/config.py index 520613a..7cc29d1 100644 --- a/argos/schemas/config.py +++ b/argos/schemas/config.py @@ -79,12 +79,26 @@ def parse_checks(value): if name not in available_names: msg = f"Check should be one of f{available_names}. ({name} given)" raise ValueError(msg) - if isinstance(expected, int): - expected = str(expected) - if isinstance(expected, list): - expected = json.dumps(expected) - if isinstance(expected, dict): - expected = json.dumps(expected) + if name == "http-to-https": + if isinstance(expected, int) and expected in range(300, 400): + expected = json.dumps({"value": expected}) + elif isinstance(expected, list): + expected = json.dumps({"list": expected}) + elif ( + isinstance(expected, dict) + and "start" in expected + and "stop" in expected + ): + expected = json.dumps({"range": [expected["start"], expected["stop"]]}) + else: + expected = json.dumps({"range": [300, 400]}) + else: + if isinstance(expected, int): + expected = str(expected) + if isinstance(expected, list): + expected = json.dumps(expected) + if isinstance(expected, dict): + expected = json.dumps(expected) return (name, expected) diff --git a/argos/server/routes/views.py b/argos/server/routes/views.py index fe48566..6dc5ace 100644 --- a/argos/server/routes/views.py +++ b/argos/server/routes/views.py @@ -14,6 +14,7 @@ from passlib.context import CryptContext from sqlalchemy import func from sqlalchemy.orm import Session +from argos.checks.base import Status from argos.schemas import Config from argos.server import queries from argos.server.models import Result, Task, User @@ -190,7 +191,7 @@ async def get_result_view( """Show the details of a result""" result = db.query(Result).get(result_id) return templates.TemplateResponse( - "result.html", {"request": request, "result": result} + "result.html", {"request": request, "result": result, "error": Status.ERROR} ) @@ -220,6 +221,7 @@ async def get_task_results_view( "results": results, "task": task, "description": description, + "error": Status.ERROR, }, ) diff --git a/argos/server/static/styles.css b/argos/server/static/styles.css index 6b03c9e..870bafb 100644 --- a/argos/server/static/styles.css +++ b/argos/server/static/styles.css @@ -1,5 +1,22 @@ @import url("pico.min.css"); +.display-small { + display: none; + text-align: center; +} +@media (max-width: 767px) { + .display-large { + display: none !important; + } + .display-small { + display: block; + } + .display-small article { + display: inline-block; + width: 24%; + } +} + code { white-space: pre-wrap; } diff --git a/argos/server/templates/index.html b/argos/server/templates/index.html index 455697d..87bd07a 100644 --- a/argos/server/templates/index.html +++ b/argos/server/templates/index.html @@ -44,7 +44,29 @@
-
+
+
+ âť” +
+ {{ counts_dict['unknown'] }} +
+
+ âś… +
+ {{ counts_dict['ok'] }} +
+
+ ⚠️ +
+ {{ counts_dict['warning'] }} +
+
+ ❌ +
+ {{ counts_dict['critical'] }} +
+
+
âť” diff --git a/argos/server/templates/result.html b/argos/server/templates/result.html index abd7d53..634c0e4 100644 --- a/argos/server/templates/result.html +++ b/argos/server/templates/result.html @@ -3,7 +3,11 @@ {% block content %}
Task
-
{{ result.task }}
+
+ + {{ result.task }} + +
Submitted at
{{ result.submitted_at }}
Status
@@ -11,6 +15,26 @@
Severity
{{ result.severity }}
Context
-
{{ result.context }}
+
+ {% if result.status != error %} + {{ result.context }} + {% else %} +
+ {% if result.context['error_message'] %} +
Error message
+
{{ result.context['error_message'] }}
+ {% endif %} +
Error type
+
{{ result.context['error_type'] }}
+
Error details
+
+
+ {{ result.context['error_details'] | truncate(120, False, '…') }} (click to expand) +
{{ result.context['error_details'] | replace('\n', '
') | safe }}
+
+
+
+ {% endif %} +
{% endblock content %} diff --git a/argos/server/templates/results.html b/argos/server/templates/results.html index d7a7d57..3a394c5 100644 --- a/argos/server/templates/results.html +++ b/argos/server/templates/results.html @@ -14,10 +14,34 @@ {% for result in results %} - {{ result.submitted_at }} + + + {{ result.submitted_at }} + + {{ result.status }} {{ result.severity }} - {{ result.context }} + + {% if result.status != error %} + {{ result.context }} + {% else %} +
+ {% if result.context["error_message"] %} +
Error message
+
{{ result.context["error_message"] }}
+ {% endif %} +
Error type
+
{{ result.context["error_type"] }}
+
Error details
+
+
+ {{ result.context["error_details"] | truncate(120, False, "…") }} (click to expand) +
{{ result.context["error_details"] | replace("\n", "
") | safe }}
+
+
+
+ {% endif %} + {% endfor %} diff --git a/docs/_static/fix-nav.css b/docs/_static/fix-nav.css new file mode 100644 index 0000000..d0e5d01 --- /dev/null +++ b/docs/_static/fix-nav.css @@ -0,0 +1,4 @@ +.sy-head-brand img + strong { + display: inline; + margin-left: 1em; +} diff --git a/docs/_static/logo.png b/docs/_static/logo.png new file mode 120000 index 0000000..ff65d50 --- /dev/null +++ b/docs/_static/logo.png @@ -0,0 +1 @@ +../../argos/server/static/logo.png \ No newline at end of file diff --git a/docs/api.md b/docs/api.md index 7fe1b2a..4272995 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1,3 +1,6 @@ +--- +description: Argos exposes a website and an API. This is how to use the API. +--- # The HTTP API Argos exposes a website and an API. The website is available at "/" and the API at "/api". diff --git a/docs/changelog.md b/docs/changelog.md index 8261b35..029cce1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,2 +1,5 @@ +--- +description: Last changes in Argos. +--- ```{include} ../CHANGELOG.md -``` \ No newline at end of file +``` diff --git a/docs/checks.md b/docs/checks.md index 06fddd6..8e961e2 100644 --- a/docs/checks.md +++ b/docs/checks.md @@ -1,6 +1,9 @@ +--- +description: Here are the checks that Argos proposes, with a description of what they do and how to configure them. +--- # Checks -At its core, argos runs checks and return the results to the service. Here are the implemented checks, with a description of what they do and how to configure them. +At its core, Argos runs checks and return the results to the service. Here are the implemented checks, with a description of what they do and how to configure them. ## Simple checks @@ -8,17 +11,18 @@ These checks are the most basic ones. They simply check that the response from t | Check | Description | Configuration | | --- | --- | --- | -| `status-is` | Check that the returned status code matches what you expect. | `status-is: "200"` | +| `status-is` | Check that the returned status code matches what you expect. |
status-is: \"200\"
| | `status-in` | Check that the returned status code is in the list of codes you expect. |
status-in:
- 200
- 302
| -| `body-contains` | Check that the returned body contains a given string. | `body-contains: "Hello world"` | -| `body-like` | Check that the returned body matches a given regex. | `body-like: "Hel+o w.*"` | +| `body-contains` | Check that the returned body contains a given string. |
body-contains: "Hello world"
| +| `body-like` | Check that the returned body matches a given regex. |
body-like: "Hel+o w.*"
| | `headers-contain` | Check that the response contains the expected headers. |
headers-contain:
- "content-encoding"
- "content-type"
| | `headers-have` | Check that the response contains the expected headers with the expected value. |
headers-have:
content-encoding: "gzip"
content-type: "text/html"
| | `headers-like` | Check that response headers contains the expected headers and that the values matches the provided regexes. |
headers-like:
content-encoding: "gzip\|utf"
content-type: "text/(html\|css)"
| | `json-contains` | Check that JSON response contains the expected structure. |
json-contains:
- /foo/bar/0
- /timestamp
| | `json-has` | Check that JSON response contains the expected structure and values. |
json-has:
/maintenance: false
/productname: "Nextcloud"
| | `json-like` | Check that JSON response contains the expected structure and that the values matches the provided regexes. |
json-like:
/productname: ".\*cloud"
/versionstring: "29\\\\..\*"
| -| `json-is` | Check that JSON response is the exact expected JSON object. | `json-is: '{"foo": "bar", "baz": 42}'`| +| `json-is` | Check that JSON response is the exact expected JSON object. |
json-is: '{"foo": "bar", "baz": 42}'
| +| `http-to-https` | Check that the HTTP version of the domain redirects to HTTPS. Multiple choices of configuration. |
http-to-https: true
http-to-https: 301
http-to-https:
start: 301
stop: 308
http-to-https:
- 301
- 302
- 307
| ```{code-block} yaml --- @@ -34,6 +38,21 @@ caption: argos-config.yaml - headers-contain: - "content-encoding" - "content-type" + # Check that there is a HTTP to HTTPS redirection with 3xx status code + - http-to-https: true + # Check that there is a HTTP to HTTPS redirection with 301 status code + - http-to-https: 301 + # Check that there is a HTTP to HTTPS redirection with a status code + # in the provided range (stop value excluded) + - http-to-https: + start: 301 + stop: 308 + # Check that there is a HTTP to HTTPS redirection with a status code + # in the provided list + - http-to-https: + - 301 + - 302 + - 307 - path: "/foobar" checks: - status-in: diff --git a/docs/cli.md b/docs/cli.md index cd17663..9d018af 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -1,3 +1,6 @@ +--- +description: How to use Argos from the command line. +--- # Command-line interface