├── .dev └── .gitstore ├── .gitignore ├── frontend ├── .npmrc ├── .dockerignore ├── src │ ├── routes │ │ ├── +layout.js │ │ ├── +page.server.js │ │ └── +page.svelte │ ├── lib │ │ ├── stores.js │ │ ├── utils.js │ │ ├── header │ │ │ └── ButtonGitHub.svelte │ │ ├── Dummy.svelte │ │ ├── Footer.svelte │ │ ├── ComponentsList.svelte │ │ ├── api.js │ │ ├── Overview.svelte │ │ ├── Header.svelte │ │ ├── Tick.svelte │ │ └── Component.svelte │ └── app.html ├── static │ ├── favicon.png │ └── style.css ├── vite.config.js ├── .gitignore ├── .eslintignore ├── .prettierignore ├── .prettierrc ├── .eslintrc.cjs ├── Dockerfile ├── svelte.config.js ├── package.json ├── README.md └── package-lock.json ├── docs ├── images │ └── screenshot.png ├── architecture.md └── import-data.md ├── backend ├── requirements.txt ├── Dockerfile ├── README.md ├── lib │ ├── view.py │ └── db_interface.py └── main.py ├── charts └── pagetron │ ├── Chart.yaml │ ├── templates │ ├── prometheus.pvc.yaml │ ├── backend.service.yaml │ ├── blackbox.service.yaml │ ├── prometheus.service.yaml │ ├── frontend.service.yaml │ ├── tests │ │ └── test-connection.yaml │ ├── publisher.configmap.yaml │ ├── housekeeper.cronjob.yaml │ ├── housekeeper.configmap.yaml │ ├── blackbox.deployment.yaml │ ├── frontend.deployment.yaml │ ├── backend.deployment.yaml │ ├── common.ingress.yaml │ ├── prometheus.configmap.yaml │ ├── publisher.cronjob.yaml │ └── prometheus.deployment.yaml │ ├── .helmignore │ ├── values.dev.yaml │ └── values.yaml ├── .github └── workflows │ ├── release-chart.yaml │ ├── deploy-gh-pages.yaml │ ├── build-backend.yaml │ └── build-frontend.yaml ├── skaffold.yaml └── README.md /.dev/.gitstore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.secret.yaml 2 | -------------------------------------------------------------------------------- /frontend/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | .svelte-kit/ 2 | build/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /frontend/src/routes/+layout.js: -------------------------------------------------------------------------------- 1 | export const prerender = true; 2 | -------------------------------------------------------------------------------- /docs/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agrrh/pagetron/HEAD/docs/images/screenshot.png -------------------------------------------------------------------------------- /frontend/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agrrh/pagetron/HEAD/frontend/static/favicon.png -------------------------------------------------------------------------------- /frontend/src/lib/stores.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const viewStore = writable('quarter'); 4 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.104.1 2 | uvicorn==0.23.2 3 | prometheus-api-client==0.5.4 4 | pydantic==2.5.2 5 | ttl-cache==1.6 6 | -------------------------------------------------------------------------------- /frontend/src/lib/utils.js: -------------------------------------------------------------------------------- 1 | export let isBuildingSnapshot = false; 2 | 3 | isBuildingSnapshot = import.meta.env.VITE_BUILD_SNAPSHOT == true; 4 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /charts/pagetron/Chart.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: v2 4 | name: pagetron 5 | description: A Helm chart for Kubernetes 6 | 7 | type: application 8 | 9 | version: 0.1.0 10 | 11 | appVersion: "0.2.2" 12 | 13 | # foo 14 | -------------------------------------------------------------------------------- /frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /frontend/static/style.css: -------------------------------------------------------------------------------- 1 | h1, 2 | h2, 3 | h3, 4 | h4, 5 | h5, 6 | .title { 7 | font-family: 'Rubik', serif; 8 | font-weight: normal; 9 | } 10 | 11 | body, 12 | div, 13 | p { 14 | font-family: 'Nunito', serif; 15 | font-weight: light; 16 | } 17 | -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12 2 | 3 | WORKDIR /app 4 | 5 | COPY ./requirements.txt ./ 6 | RUN pip install --no-cache-dir -r requirements.txt 7 | 8 | ENV HADOLINT_RELAX yes 9 | 10 | COPY ./ ./ 11 | 12 | CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "3000"] 13 | -------------------------------------------------------------------------------- /frontend/src/lib/header/ButtonGitHub.svelte: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Info 2 | 3 | ``` 4 | docker build . -t local/tmp 5 | docker run --rm -ti --network host local/tmp 6 | ``` 7 | 8 | ``` 9 | http://0.0.0.0:3000/overview/ 10 | ``` 11 | 12 | ``` 13 | http://0.0.0.0:3000/components/ 14 | ``` 15 | 16 | ``` 17 | http://0.0.0.0:3000/components/?name=https://example.com&view=day 18 | ``` 19 | -------------------------------------------------------------------------------- /charts/pagetron/templates/prometheus.pvc.yaml: -------------------------------------------------------------------------------- 1 | {{- with $.Values.prometheus }} 2 | 3 | --- 4 | 5 | kind: PersistentVolumeClaim 6 | apiVersion: v1 7 | 8 | metadata: 9 | name: pagetron-prometheus-data 10 | 11 | spec: 12 | accessModes: 13 | - ReadWriteOnce 14 | resources: 15 | requests: 16 | storage: 10Gi 17 | 18 | {{- end }} 19 | -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type { import("eslint").Linter.FlatConfig } */ 2 | module.exports = { 3 | root: true, 4 | extends: ['eslint:recommended', 'plugin:svelte/recommended', 'prettier'], 5 | parserOptions: { 6 | sourceType: 'module', 7 | ecmaVersion: 2020, 8 | extraFileExtensions: ['.svelte'] 9 | }, 10 | env: { 11 | browser: true, 12 | es2017: true, 13 | node: true 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:21 AS builder 2 | 3 | WORKDIR /app 4 | 5 | COPY ./package.json ./ 6 | COPY ./package-lock.json ./ 7 | 8 | RUN npm install --include=dev 9 | 10 | COPY ./ ./ 11 | 12 | FROM node:21 AS build 13 | 14 | WORKDIR /app 15 | 16 | COPY --from=builder /app/ ./ 17 | 18 | RUN npm run build 19 | 20 | # --- 21 | 22 | FROM nginx:1.23-alpine 23 | 24 | COPY --from=build /app/build /usr/share/nginx/html 25 | -------------------------------------------------------------------------------- /charts/pagetron/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /charts/pagetron/templates/backend.service.yaml: -------------------------------------------------------------------------------- 1 | {{- with $.Values.backend }} 2 | 3 | --- 4 | 5 | apiVersion: v1 6 | kind: Service 7 | 8 | metadata: 9 | name: pagetron-backend 10 | labels: 11 | app: pagetron 12 | component: backend 13 | 14 | spec: 15 | type: ClusterIP 16 | ports: 17 | - name: http 18 | port: 80 19 | targetPort: http 20 | protocol: TCP 21 | 22 | selector: 23 | app: pagetron 24 | component: backend 25 | 26 | {{- end }} 27 | -------------------------------------------------------------------------------- /charts/pagetron/templates/blackbox.service.yaml: -------------------------------------------------------------------------------- 1 | {{- with $.Values.blackbox }} 2 | 3 | --- 4 | 5 | apiVersion: v1 6 | kind: Service 7 | 8 | metadata: 9 | name: blackbox 10 | labels: 11 | app: pagetron 12 | component: blackbox 13 | 14 | spec: 15 | type: ClusterIP 16 | ports: 17 | - name: http 18 | port: 9115 19 | targetPort: http 20 | protocol: TCP 21 | 22 | selector: 23 | app: pagetron 24 | component: blackbox 25 | 26 | {{- end }} 27 | -------------------------------------------------------------------------------- /charts/pagetron/templates/prometheus.service.yaml: -------------------------------------------------------------------------------- 1 | {{- with $.Values.prometheus }} 2 | 3 | --- 4 | 5 | apiVersion: v1 6 | kind: Service 7 | 8 | metadata: 9 | name: prometheus 10 | labels: 11 | app: pagetron 12 | component: prometheus 13 | 14 | spec: 15 | type: ClusterIP 16 | 17 | selector: 18 | app: pagetron 19 | component: prometheus 20 | 21 | ports: 22 | - name: http 23 | port: 9090 24 | targetPort: http 25 | protocol: TCP 26 | 27 | {{- end }} 28 | -------------------------------------------------------------------------------- /frontend/src/routes/+page.server.js: -------------------------------------------------------------------------------- 1 | import { apiUrl, getAllData } from '$lib/api.js'; 2 | import { isBuildingSnapshot } from '$lib/utils.js'; 3 | import { error } from '@sveltejs/kit'; 4 | 5 | /** @type {import('./$types').PageLoad} */ 6 | export async function load({ fetch, params }) { 7 | let overview = {}; 8 | let components = []; 9 | let componentsData = {}; 10 | 11 | if (isBuildingSnapshot) { 12 | return getAllData(); 13 | } 14 | 15 | return { overview, components, componentsData }; 16 | } 17 | -------------------------------------------------------------------------------- /charts/pagetron/templates/frontend.service.yaml: -------------------------------------------------------------------------------- 1 | {{- if $.Values.publicAccess.ingress.enabled }} 2 | {{- with $.Values.frontend }} 3 | 4 | --- 5 | 6 | apiVersion: v1 7 | kind: Service 8 | 9 | metadata: 10 | name: pagetron-frontend 11 | labels: 12 | app: pagetron 13 | component: frontend 14 | 15 | spec: 16 | type: ClusterIP 17 | ports: 18 | - name: http 19 | port: 80 20 | targetPort: http 21 | protocol: TCP 22 | 23 | selector: 24 | app: pagetron 25 | component: frontend 26 | 27 | {{- end }} 28 | {{- end }} 29 | -------------------------------------------------------------------------------- /charts/pagetron/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: v1 4 | kind: Pod 5 | 6 | metadata: 7 | name: pagetron-test-connection 8 | labels: 9 | app: pagetron 10 | annotations: 11 | "helm.sh/hook": test 12 | 13 | spec: 14 | restartPolicy: Never 15 | containers: 16 | - name: frontend 17 | image: busybox 18 | command: ['wget'] 19 | args: ['http://pagetron-frontend:80'] 20 | 21 | - name: backend 22 | image: busybox 23 | command: ['wget'] 24 | args: ['http://pagetron-backend:80'] 25 | -------------------------------------------------------------------------------- /frontend/src/lib/Dummy.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 | {#if error == null} 8 |

9 | 10 |

11 | {:else} 12 |
13 |
14 |

Error

15 |
16 |
17 | {error} 18 |
19 |
20 | {/if} 21 |
22 |
23 | -------------------------------------------------------------------------------- /backend/lib/view.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from datetime import datetime 4 | 5 | 6 | class ViewPreset(BaseModel): 7 | depth: int 8 | unit: str = "m" 9 | step: str = "1m" 10 | metric: str = "pagetron:availability:1m" 11 | precision: str = "time" 12 | 13 | 14 | def timestamp_to_human_token(ts, precision="datetime"): 15 | dt = datetime.fromtimestamp(ts) 16 | 17 | dt_fmt = { 18 | "datetime": "%b %d, %Y at %H:%M", 19 | "time": "%H:%M", 20 | "date": "%b %d, %Y", 21 | }.get(precision) 22 | 23 | return dt.strftime(dt_fmt) 24 | -------------------------------------------------------------------------------- /frontend/src/lib/Footer.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 |

8 | Powered by 9 | Pagetron 10 |

11 | 12 |

version {version}

13 | 14 |

15 | Made with 16 | 17 | and 18 | 19 |

20 |
21 |
22 | 23 | 28 | -------------------------------------------------------------------------------- /.github/workflows/release-chart.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: helm chart 4 | 5 | on: 6 | push: 7 | branches: 8 | - master 9 | paths: 10 | - "charts/pagetron/**" 11 | 12 | jobs: 13 | release: 14 | name: Release Chart 15 | 16 | runs-on: ubuntu-latest 17 | 18 | permissions: 19 | contents: write 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | with: 25 | fetch-depth: 0 26 | 27 | - name: Publish Helm charts 28 | uses: stefanprodan/helm-gh-pages@v1.7.0 29 | with: 30 | token: ${{ secrets.CR_TOKEN }} 31 | -------------------------------------------------------------------------------- /frontend/src/lib/ComponentsList.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 |
21 |
22 | {#each components as c} 23 | 24 | {/each} 25 |
26 |
27 | -------------------------------------------------------------------------------- /frontend/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-static'; 2 | 3 | /** @type {import('@sveltejs/kit').Config} */ 4 | const config = { 5 | kit: { 6 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 7 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 8 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 9 | adapter: adapter({ 10 | pages: 'build', 11 | assets: 'build', 12 | fallback: undefined, 13 | precompress: false, 14 | strict: true 15 | }) 16 | } 17 | }; 18 | 19 | export default config; 20 | -------------------------------------------------------------------------------- /charts/pagetron/values.dev.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | targets: 4 | - https://www.docker.com 5 | - https://kubernetes.io 6 | - https://svelte.dev 7 | - https://fastapi.tiangolo.com 8 | - https://prometheus.io 9 | 10 | publicAccess: 11 | ingress: 12 | enabled: true 13 | 14 | class: traefik 15 | 16 | s3: 17 | enabled: false 18 | 19 | secretName: s3-publish-credentials 20 | bucketName: pagetron-status-page 21 | 22 | # May be a good idea to use nip.io and minikube, e.g.: 23 | # publicUrl: http://pagetron.192-168-42-1.nip.io 24 | # (know your minikube address with `minikube ip` command) 25 | publicUrl: https://pagetron-dev.agrrh.com 26 | 27 | timezone: Europe/Moscow 28 | 29 | backend: 30 | replicas: 1 31 | 32 | frontend: 33 | replicas: 1 34 | 35 | publisher: 36 | schedule: "*/1 * * * *" 37 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "lint": "prettier --check . && eslint .", 10 | "format": "prettier --write ." 11 | }, 12 | "devDependencies": { 13 | "@sveltejs/adapter-auto": "^3.0.0", 14 | "@sveltejs/adapter-static": "^3.0.0", 15 | "@sveltejs/kit": "^2.0.0", 16 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 17 | "eslint": "^8.28.0", 18 | "eslint-config-prettier": "^9.0.0", 19 | "eslint-plugin-svelte": "^2.30.0", 20 | "prettier": "^3.0.0", 21 | "prettier-plugin-svelte": "^3.0.0", 22 | "svelte": "^4.2.7", 23 | "vite": "^5.0.0" 24 | }, 25 | "type": "module", 26 | "dependencies": { 27 | "humanize-duration": "^3.31.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /charts/pagetron/templates/publisher.configmap.yaml: -------------------------------------------------------------------------------- 1 | {{- with $.Values.publisher }} 2 | 3 | --- 4 | 5 | apiVersion: v1 6 | kind: ConfigMap 7 | 8 | metadata: 9 | name: pagetron-publisher-job 10 | 11 | data: 12 | build.sh: | 13 | #!/bin/bash 14 | 15 | set -e 16 | set -x 17 | 18 | echo "# Starting" 19 | 20 | echo "# Building website" 21 | 22 | export PATH="/app/node_modules/.bin:${PATH}" 23 | npm run build 24 | 25 | mv build /data/build 26 | 27 | echo "# Done" 28 | 29 | publish.sh: | 30 | #!/bin/ash 31 | 32 | set -e 33 | set -x 34 | 35 | echo "# Starting" 36 | 37 | echo "# Publishing website" 38 | 39 | cd /data/build 40 | 41 | aws s3 \ 42 | --endpoint-url=${AWS_ENDPOINT_URL_S3} \ 43 | sync \ 44 | ./ \ 45 | s3://${S3_BUCKET_NAME}/ 46 | 47 | echo "# Done" 48 | 49 | {{- end }} 50 | -------------------------------------------------------------------------------- /skaffold.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | apiVersion: skaffold/v4beta1 4 | kind: Config 5 | 6 | metadata: 7 | name: pagetron 8 | 9 | build: 10 | artifacts: 11 | - image: agrrh/pagetron-backend 12 | context: backend 13 | docker: 14 | dockerfile: Dockerfile 15 | - image: agrrh/pagetron-frontend 16 | context: frontend 17 | docker: 18 | dockerfile: Dockerfile 19 | 20 | deploy: 21 | helm: 22 | hooks: 23 | before: 24 | - host: 25 | command: ["kubectl", "apply", "-n", "pagetron-dev", "-f", ".dev/dev.secret.yaml"] 26 | os: [darwin, linux] 27 | 28 | releases: 29 | - name: pagetron 30 | 31 | namespace: pagetron-dev 32 | createNamespace: true 33 | 34 | chartPath: charts/pagetron 35 | version: 0.1.0 36 | valuesFiles: 37 | - charts/pagetron/values.yaml 38 | - charts/pagetron/values.dev.yaml 39 | -------------------------------------------------------------------------------- /frontend/src/lib/api.js: -------------------------------------------------------------------------------- 1 | export let apiUrl; 2 | 3 | if (import.meta.env.VITE_API_URL) { 4 | apiUrl = import.meta.env.VITE_API_URL; 5 | } else { 6 | apiUrl = import.meta.env.DEV ? 'http://localhost:3000' : '/api'; 7 | } 8 | 9 | export async function getAllData() { 10 | const views = ['quarter', 'hours', 'year']; 11 | 12 | let overview = {}; 13 | let components = []; 14 | let componentsData = {}; 15 | 16 | let response; 17 | 18 | response = await fetch(apiUrl + `/overview/`); 19 | overview = await response.json(); 20 | 21 | response = await fetch(apiUrl + `/components/`); 22 | components = await response.json(); 23 | 24 | for (const c of components) { 25 | componentsData[c] = {}; 26 | 27 | for (const v of views) { 28 | response = await fetch(apiUrl + `/components/?name=${c}&view=${v}`); 29 | const componentData = await response.json(); 30 | 31 | componentsData[c][v] = componentData; 32 | } 33 | } 34 | 35 | return { 36 | overview, 37 | components, 38 | componentsData 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /frontend/src/lib/Overview.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |
27 |

{statusText}

28 |

Last updated on {datetime_human} UTC

29 |
30 |
31 | 32 | 38 | -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | ```mermaid 4 | graph LR 5 | 6 | visitor(("👨👩 Visitors")) 7 | domain["status.example.org"] 8 | 9 | subgraph resources 10 | external-website[website] 11 | external-api[API] 12 | end 13 | 14 | subgraph s3[S3] 15 | frontend-static 16 | end 17 | 18 | subgraph kubernetes["Kubernetes Cluster"] 19 | subgraph pagetron["Namespace: pagetron"] 20 | ingress 21 | 22 | frontend 23 | backend 24 | 25 | prometheus[(prometheus)] 26 | blackbox[blackbox-exporter] 27 | 28 | housekeeper["🕐 housekeeper"] 29 | publisher["🕐 publisher"] 30 | end 31 | end 32 | 33 | prometheus -.- blackbox 34 | blackbox -.-> |probe| external-website & external-api 35 | 36 | backend -.-> |get data| prometheus 37 | 38 | housekeeper -.->|cleanup| prometheus 39 | 40 | visitor --> domain 41 | domain -->|Option A: live data| ingress --> frontend & backend 42 | domain -->|Option B: fault-tolerant static page| frontend-static 43 | 44 | publisher -.->|get actual data| backend 45 | publisher -.->|deploy| frontend-static 46 | ``` 47 | -------------------------------------------------------------------------------- /frontend/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 22 | 23 | 24 |
%sveltekit.body%
25 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 27 | 28 |
29 | 30 | {#await data} 31 | 32 | {:then data} 33 | 34 | 35 | {:catch error} 36 | 37 | {/await} 38 | 39 |