demo article in English: stack overview and redesigned architecture diagrams
@@ -1,18 +1,18 @@
|
||||
# CTAO Science Portal — content
|
||||
# CTAO Science Portal: content
|
||||
|
||||
Editorial content of the CTAO Science Portal, separated from the portal code
|
||||
so editorial history stays clean and publishing never mixes with development.
|
||||
|
||||
| Path | What |
|
||||
|---|---|
|
||||
| `news/*.md` | Articles — Markdown + frontmatter |
|
||||
| `news/*.md` | Articles: Markdown with frontmatter |
|
||||
| `pages/*.md` | Static pages (contact, privacy, disclaimer) |
|
||||
| `uploads/` | Editor-uploaded media (the CMS converts images to WebP) |
|
||||
|
||||
## How to edit
|
||||
|
||||
Editors use the visual editor at the portal's `/admin/` page (sign in with a
|
||||
Gitea account) — every save is a commit here, and the site republishes
|
||||
Gitea account). Every save is a commit here, and the site republishes
|
||||
automatically within seconds. Direct git commits work exactly the same way.
|
||||
|
||||
The portal code lives in the `ctao-portal` repository; its build overlays
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
---
|
||||
title: "Jak działa to demo: portal statyczny + CMS oparty o git"
|
||||
description: "Architektura rozwiązania, przydatne linki i porównanie klas rozwiązań: strona w 100% statyczna z graficznym edytorem treści vs CMS z własną bazą danych."
|
||||
date: 2026-07-23
|
||||
category: demo
|
||||
author: Cyfronet
|
||||
cover: /uploads/diagrams/cover-git-cms.svg
|
||||
lang: pl
|
||||
draft: false
|
||||
---
|
||||
|
||||
Ten portal to **statyczne pliki HTML** generowane z plików Markdown trzymanych
|
||||
w repozytorium git. Redaktor dostaje **graficzny edytor w przeglądarce** —
|
||||
a mimo to **po stronie publicznej** nie działa żadna aplikacja ani baza:
|
||||
między czytelnikiem a treścią jest wyłącznie serwer plików. Jedyny działający
|
||||
serwis (Gitea) obsługuje edycję i stoi poza ścieżką czytelnika — wracamy do
|
||||
tego uczciwie niżej. Ten artykuł został napisany i opublikowany dokładnie tą
|
||||
ścieżką.
|
||||
|
||||
## Od zapisu do publikacji
|
||||
|
||||

|
||||
|
||||
Publikacja jest automatyczna — zapis w edytorze po chwili sam pojawia się
|
||||
na stronie, bez żadnego ręcznego deployu. Każda zmiana to commit, więc
|
||||
**pełna historia treści i możliwość cofnięcia są w gicie za darmo**.
|
||||
|
||||
## Jak to wygląda na maszynie
|
||||
|
||||

|
||||
|
||||
Dla porównania — ta sama maszyna z klasycznym CMS-em. Układ diagramu jest
|
||||
celowo identyczny, żeby różnice było widać na pierwszy rzut oka: tu każdy
|
||||
element działa bez przerwy, a panel logowania i aplikacja są wystawione
|
||||
do internetu.
|
||||
|
||||

|
||||
|
||||
## Gdzie co jest
|
||||
|
||||
| Co | Gdzie |
|
||||
|---|---|
|
||||
| Portal | strona główna tego serwisu |
|
||||
| Edytor treści | `/admin/` (link „Content editor" w stopce) |
|
||||
| Gitea (repo `ctao/portal`) | port 3000 na maszynie demo |
|
||||
| Konto demo | użytkownik `ctao` (hasło u prowadzącego demo) |
|
||||
| Dokumentacja wdrożenia | katalog `deploy/` w repo kodu: `README.md` (runbook) |
|
||||
|
||||
Całość działa w podmanie na jednej maszynie: kontener Gitei, kontener nginx
|
||||
i jednorazowy kontener builda odpalany przez systemd timer.
|
||||
|
||||
## Dlaczego strona statyczna
|
||||
|
||||

|
||||
|
||||
- **Bezpieczeństwo**: publicznie wystawione są tylko pliki. Nie istnieje
|
||||
panel admina dostępny z internetu, baza do wykradzenia ani runtime z CVE.
|
||||
- **Utrzymanie**: nie ma aplikacji, która musi być monitorowana i
|
||||
aktualizowana pod presją łatek bezpieczeństwa. Do utrzymania zostaje git
|
||||
(który i tak utrzymujemy) i krótki skrypt builda.
|
||||
- **Szybkość**: statyczny HTML to najszybsza możliwa strona — istotne też
|
||||
dla SEO.
|
||||
- **Historia**: wersjonowanie treści to naturalna cecha gita, nie płatna
|
||||
funkcja produktu.
|
||||
|
||||
## Dwie klasy rozwiązań
|
||||
|
||||
Linia podziału jest często mylona: nie chodzi o to, czy treść leży w bazie
|
||||
czy w plikach, tylko o to, **czy między czytelnikiem a treścią stoi
|
||||
działająca aplikacja**.
|
||||
|
||||
| | Statyczne + git-CMS *(to demo)* | CMS serwujący stronę aplikacją |
|
||||
|---|---|---|
|
||||
| Przykłady | Sveltia, Decap (+ Astro / Hugo / Eleventy) | Strapi, WordPress, Drupal, Grav, TinaCMS |
|
||||
| Treść żyje w | plikach Markdown w gicie | zwykle w bazie; Grav — w plikach, Tina — w gicie |
|
||||
| W produkcji działa | serwer plików | aplikacja (zwykle + baza), non stop |
|
||||
| Utrzymanie | skrypt builda + serwis gitowy | patche, aktualizacje, backupy bazy |
|
||||
| Historia zmian | git, wbudowana | zależnie od produktu (bywa płatna) |
|
||||
|
||||
Dwa doprecyzowania, żeby tabela nie upraszczała: **Grav** trzyma treść
|
||||
w plikach bez żadnej bazy — ale stronę i tak serwuje aplikacja PHP, więc pod
|
||||
względem powierzchni ataku i utrzymania należy do prawej kolumny. **TinaCMS**
|
||||
trzyma treść wręcz w Markdownie w gicie, ale do działania edytora wymaga
|
||||
stale działającego backendu z bazą — jest hybrydą, która dziedziczy koszty
|
||||
obu światów.
|
||||
|
||||
Ograniczenia poszczególnych rozwiązań, które zweryfikowaliśmy podczas
|
||||
researchu:
|
||||
|
||||
- **Strapi** — historia wersji treści nie istnieje w darmowym self-hosted
|
||||
(tylko płatne plany Growth/Enterprise). Wymaga stale działającego Node +
|
||||
Postgresa i regularnych aktualizacji.
|
||||
- **WordPress** — największy ekosystem, ale i największa historia podatności
|
||||
(głównie pluginy); publiczny panel logowania i PHP do ciągłego patchowania.
|
||||
- **Grav** (PHP, flat-file — „lekka" alternatywa) — poważne podatności RCE
|
||||
w ostatnich latach; nadal wymaga serwera PHP wystawionego do internetu.
|
||||
- **TinaCMS** — edytuje Markdown, ale wymaga backendu: chmura Tina jest
|
||||
darmowa tylko do 2 użytkowników, a self-hosting to własny serwer Node +
|
||||
baza (Mongo/Postgres) + auth — wracamy do utrzymywania aplikacji.
|
||||
- **Ryzyko open-core**: funkcje potrafią z czasem przechodzić do płatnych
|
||||
planów (przykład wyżej). Gdy treść i jej historia leżą w gicie, nie ma
|
||||
funkcji, którą dostawca mógłby przenieść do płatnego planu.
|
||||
|
||||
## Plusy i minusy obu podejść
|
||||
|
||||
Portal ma być utrzymywany przez co najmniej 6 lat, więc bilans trzeba robić
|
||||
w dwóch horyzontach. Poniżej ocena podejść jako klas rozwiązań, nie
|
||||
konkretnych produktów.
|
||||
|
||||
**Krótkoterminowo (uruchomienie i pierwsze miesiące)**
|
||||
|
||||
| | Statyczne + git | CMS z bazą |
|
||||
|---|---|---|
|
||||
| Plusy | mało ruchomych części od pierwszego dnia; edytor graficzny gotowy; historia i rollback od razu, w gicie | bogatszy panel od ręki: role i uprawnienia, workflow redakcyjny, relacje między treściami, więcej gotowych integracji |
|
||||
| Minusy | uprawnienia proste (dziedziczone z gita — bez ról redakcyjnych); przeglądanie historii na razie w interfejsie gita, nie w edytorze | więcej komponentów do postawienia, spięcia i zabezpieczenia od pierwszego dnia (aplikacja, baza, backupy) |
|
||||
|
||||
**W horyzoncie 6 lat**
|
||||
|
||||
| | Statyczne + git | CMS z bazą |
|
||||
|---|---|---|
|
||||
| Plusy | strona publiczna bez aplikacji = brak presji łatek na ścieżce czytelnika; do utrzymania zostaje serwis gitowy (najlepiej ten, który zespół i tak ma); treść w otwartym formacie przetrwa każdą wymianę narzędzi | jeśli z czasem będą potrzebne funkcje aplikacyjne (konta, personalizacja, treści silnie strukturalne), platforma już je ma |
|
||||
| Minusy | przy bardzo dużej skali (tysiące stron) buildy rosną i wymagają optymalizacji; zaawansowane funkcje redakcyjne zależą od tempa rozwoju narzędzi open-source | kilka dużych migracji wersji w 6 lat (zmiany łamiące); baza wymaga backupów i testów odtwarzania; stała powierzchnia ataku; ryzyko przenoszenia funkcji do płatnych planów |
|
||||
|
||||
## „Czy Gitea to nie jest po prostu drugi CMS?"
|
||||
|
||||
Uczciwe pytanie: Gitea też ma logowanie, bazę i wymaga aktualizacji
|
||||
bezpieczeństwa, a dla edytorów musi być dostępna spoza VPN. Czy różnica nie
|
||||
sprowadza się więc do tego, że zamiast CMS-a utrzymujemy Giteę?
|
||||
|
||||
Częściowo tak — i dlatego nie twierdzimy, że utrzymanie jest zerowe. Różnica
|
||||
polega na trzech rzeczach:
|
||||
|
||||
- **Gitea stoi poza ścieżką czytelnika.** Może mieć przerwę, aktualizację,
|
||||
a nawet zostać przejęta — portal dalej stoi i serwuje bezpieczne pliki.
|
||||
Najgorszy scenariusz włamania to wandalizm treści: widoczny, z pełną
|
||||
historią, odwracalny jednym revertem. W CMS-ie serwującym stronę ten sam
|
||||
incydent oznacza przejętą stronę publiczną.
|
||||
- **Zespół już utrzymuje Giteę.** Docelowo repozytorium treści może żyć na
|
||||
istniejącej instancji zespołu — wtedy krańcowy koszt utrzymania jest
|
||||
bliski zera, a logowanie załatwia jedna integracja z AAI.
|
||||
- **Klasa oprogramowania.** Gitea to pojedynczy program w Go z SQLite:
|
||||
aktualizacja to podmiana obrazu i restart, bez ekosystemu pluginów i bez
|
||||
drzewa zależności npm po stronie serwera. Platformy CMS to frameworki
|
||||
aplikacyjne z regularnymi migracjami głównych wersji.
|
||||
|
||||
## Gdzie jeszcze może się przydać
|
||||
|
||||
Ten sam wzorzec — edytor graficzny nad repozytorium Markdown — nie jest
|
||||
ograniczony do portalu. Naturalni kandydaci u nas:
|
||||
|
||||
- **Projekt Mickiewicza** — treści redagowane przez osoby nietechniczne,
|
||||
a publikowane jako strona statyczna.
|
||||
- **Dokumentacja Episodes Platform** — już dziś jest w Markdownie; podpięcie
|
||||
edytora dałoby wygodną edycję w przeglądarce zamiast edytora plików
|
||||
w Gitei, bez żadnych zmian w istniejącym repozytorium.
|
||||
|
||||
Koszt wdrożenia w takich miejscach jest niewielki: edytor to jeden statyczny
|
||||
plik HTML plus konfiguracja wskazująca repozytorium i strukturę treści.
|
||||
|
||||
## Brak lock-inu
|
||||
|
||||
Każdy element jest wymienialny osobno, bo treść to czysty Markdown:
|
||||
|
||||
- **Sveltia ↔ Decap** — ten sam format konfiguracji; podmiana = jedna linijka
|
||||
`<script>`.
|
||||
- **Astro ↔ Hugo / Eleventy** — Markdown zostaje bez zmian, wymieniamy tylko
|
||||
szablony.
|
||||
- **Gitea ↔ dowolny hosting gitowy** — GitHub, GitLab i inne.
|
||||
- Nawet rezygnacja z całego podejścia = eksport trywialny, bo treść od
|
||||
początku leży w otwartym formacie w naszym repozytorium.
|
||||
|
||||
Wniosek z tego demo: zaczynamy od najprostszego rozwiązania, które spełnia
|
||||
wymagania — graficzny edytor dla redaktorów i bezpieczna, bezobsługowa strona
|
||||
publiczna. Złożoność dodajemy dopiero wtedy, gdy pojawi się potrzeba, której
|
||||
to podejście nie obsłuży.
|
||||
@@ -0,0 +1,191 @@
|
||||
---
|
||||
title: "How this portal works: static pages, a visual editor, git as the database"
|
||||
description: "The architecture behind this demo: a visual editor in the browser, content as Markdown in git, and a public site that is nothing but static files. With an honest comparison against database-backed CMS platforms."
|
||||
date: 2026-09-11
|
||||
category: demo
|
||||
author: Cyfronet
|
||||
cover: /uploads/diagrams/cover-git-cms.svg
|
||||
draft: false
|
||||
---
|
||||
|
||||
This portal is a set of **static HTML files** generated from Markdown kept in a
|
||||
git repository. Editors get a **visual editor in the browser**, and still
|
||||
**nothing on the public side runs an application or a database**: between the
|
||||
reader and the content there is only a file server. The one service that does
|
||||
run, the git server, stands behind the editor and off the reader's path, and we
|
||||
come back to that honestly below. This article was written and published
|
||||
through exactly the path it describes.
|
||||
|
||||
## The stack in three parts
|
||||
|
||||

|
||||
|
||||
**Sveltia CMS is the editor.** It is one JavaScript file served as a static page
|
||||
under `/admin/`. It signs the editor in to Gitea and writes Markdown files and
|
||||
images straight into the repository through the git API. There is no CMS server
|
||||
and no database behind it, and every save is a commit.
|
||||
|
||||
**Git, served by Gitea, is the database.** An article is a Markdown file with a
|
||||
defined set of fields, and uploaded images sit next to it. History, diffs and a
|
||||
one-command rollback come from git itself rather than from a paid tier of a
|
||||
product.
|
||||
|
||||
**Astro is a generator, not a server.** Its build turns those Markdown files
|
||||
into a directory of plain HTML, CSS and images, so deployment is copying that
|
||||
directory onto a file server. Nothing from Astro runs in production. Pages ship
|
||||
without JavaScript by default, while components, React among them, are rendered
|
||||
to HTML during the build, so an interactive dashboard can later be added as an
|
||||
island on the same site without changing the stack. The build also checks every
|
||||
article against the defined fields, which means a malformed article stops the
|
||||
build instead of reaching the public site.
|
||||
|
||||
On the reader's side that leaves one moving part: nginx handing over files.
|
||||
|
||||
## From a save to a published page
|
||||
|
||||

|
||||
|
||||
Publishing is automatic. A save in the editor shows up on the site moments
|
||||
later, with no manual deployment step in between. Every change is a commit, so
|
||||
**a full content history and a one-command rollback come for free**.
|
||||
|
||||
## How it runs on the machine
|
||||
|
||||

|
||||
|
||||
Two repositories, by design. The portal **code** is developed by the team, the
|
||||
editorial **content** is written by the CMS. Every build takes the newest commit
|
||||
from both, overlays content onto code and renders the site, so a push to either
|
||||
repository republishes. Editors never touch the code repository, and
|
||||
development never mixes with the editorial history.
|
||||
|
||||
Everything runs rootless in podman on a single machine: a Gitea container, an
|
||||
nginx container, and a one-shot build container started by a systemd timer. A
|
||||
build that fails changes nothing, because the previous release keeps serving.
|
||||
|
||||
For comparison, the same machine running a classic CMS. The diagram keeps the
|
||||
same skeleton on purpose, so the difference is visible at a glance: every
|
||||
element is up around the clock, the editor and the reader talk to the very same
|
||||
application, and its login panel faces the internet.
|
||||
|
||||

|
||||
|
||||
## Where things are
|
||||
|
||||
| What | Where |
|
||||
|---|---|
|
||||
| Portal | this site |
|
||||
| Content editor | `/admin/`, the "Content editor" link in the footer |
|
||||
| Content repository | `ctao/content` on the demo machine's Gitea, written by the CMS |
|
||||
| Code repository | `ctao/portal`, maintained by the team |
|
||||
| Deployment documentation | `deploy/README.md` in the code repository |
|
||||
|
||||
## Why a static site
|
||||
|
||||

|
||||
|
||||
- **Security.** What faces the internet is a file server and a directory of
|
||||
files. There is no admin panel to attack, no database to exfiltrate and no
|
||||
application runtime carrying its own vulnerabilities.
|
||||
- **Maintenance.** There is no application that has to be monitored and upgraded
|
||||
under patch pressure. What remains is a git service, which the team runs
|
||||
anyway, and a short build script.
|
||||
- **Speed.** Static HTML is as fast as a page gets, which search engines reward
|
||||
as well.
|
||||
- **History.** Versioning is a property of git, not a feature tier of a product.
|
||||
|
||||
## Two classes of solution
|
||||
|
||||
The dividing line is often misread. The question is not whether content sits in
|
||||
a database or in files. It is **whether a running application stands between the
|
||||
reader and the content**.
|
||||
|
||||
| | Static + git CMS *(this demo)* | CMS that serves the site itself |
|
||||
|---|---|---|
|
||||
| Examples | Sveltia, Decap (+ Astro / Hugo / Eleventy) | Strapi, WordPress, Drupal, Grav, TinaCMS |
|
||||
| Content lives in | Markdown files in git | usually a database; Grav in files, Tina in git |
|
||||
| In production you run | a file server | an application, usually with a database, non stop |
|
||||
| Maintenance | a build script plus a git service | patches, upgrades, database backups |
|
||||
| Change history | git, built in | product-dependent, sometimes paid |
|
||||
|
||||
Two clarifications, so the table is not read too simply. **Grav** keeps content
|
||||
in files with no database at all, yet a PHP application still serves every page,
|
||||
which puts it in the right-hand column for attack surface and maintenance.
|
||||
**TinaCMS** keeps content as Markdown in git, but its editor needs a backend
|
||||
with a database in order to work, so it inherits the costs of both worlds.
|
||||
|
||||
Limits of the individual products, verified during our research:
|
||||
|
||||
- **Strapi**: content version history does not exist in the free self-hosted
|
||||
edition (Growth and Enterprise plans only). It needs Node and PostgreSQL
|
||||
running at all times, plus regular upgrades.
|
||||
- **WordPress**: the largest ecosystem, and also the longest vulnerability
|
||||
history, mostly through plugins; a public login panel and PHP to patch
|
||||
continuously.
|
||||
- **Grav** (PHP, flat file, the "light" alternative): serious remote code
|
||||
execution issues in recent years, and still a PHP server facing the internet.
|
||||
- **TinaCMS**: it edits Markdown, but it requires a backend. The Tina cloud is
|
||||
free for up to two users, and self-hosting means your own Node server, a
|
||||
database and authentication, which is an application to maintain again.
|
||||
- **Open-core risk**: features can move into paid plans over time, as above.
|
||||
When the content and its history live in git, there is no feature a vendor
|
||||
can move behind a paywall.
|
||||
|
||||
## Trade-offs over six years
|
||||
|
||||
The portal is meant to be maintained for at least six years, so the balance has
|
||||
to be drawn over two horizons. What follows judges the two approaches as classes
|
||||
of solution, not individual products.
|
||||
|
||||
**Short term: launch and the first months**
|
||||
|
||||
| | Static + git | CMS with a database |
|
||||
|---|---|---|
|
||||
| Strengths | few moving parts from day one; a visual editor ready to use; history and rollback immediately, in git | a richer panel out of the box: roles and permissions, editorial workflow, relations between content types, more ready-made integrations |
|
||||
| Weaknesses | simple permissions, inherited from git, without editorial roles; browsing history happens in the git interface rather than in the editor | more components to stand up, connect and secure from day one: application, database, backups |
|
||||
|
||||
**Over six years**
|
||||
|
||||
| | Static + git | CMS with a database |
|
||||
|---|---|---|
|
||||
| Strengths | no application on the reader's path, so no patch pressure there; what stays is a git service, ideally the one the team already runs; content in an open format survives any change of tooling | if application features are eventually needed (accounts, personalisation, strongly structured content), the platform already has them |
|
||||
| Weaknesses | at very large scale (thousands of pages) build times grow and need attention; advanced editorial features depend on the pace of open-source tools | several major version migrations in six years, with breaking changes; the database needs backups and restore drills; a permanent attack surface; features can move into paid tiers |
|
||||
|
||||
## "Isn't Gitea just another CMS?"
|
||||
|
||||
A fair question. Gitea also has a login, a database and security updates, and it
|
||||
has to be reachable by editors. Does the difference come down to maintaining
|
||||
Gitea instead of a CMS?
|
||||
|
||||
Partly yes, and we do not claim that maintenance is zero. The difference rests
|
||||
on three things:
|
||||
|
||||
- **Gitea is off the reader's path.** It can be down, mid-upgrade, or even
|
||||
compromised while the portal keeps serving safe files. The worst case of a
|
||||
break-in is content vandalism: visible, fully recorded, and undone with a
|
||||
single revert. In a CMS that serves the site, the same incident means a
|
||||
compromised public site.
|
||||
- **The team already runs Gitea.** The content repository can live on the
|
||||
instance the team maintains anyway, which brings the marginal cost close to
|
||||
zero and lets one AAI integration handle sign-in.
|
||||
- **Class of software.** Gitea is a single Go program with SQLite: an upgrade is
|
||||
swapping an image and restarting, with no plugin ecosystem and no server-side
|
||||
npm dependency tree. CMS platforms are application frameworks with regular
|
||||
major-version migrations.
|
||||
|
||||
## No lock-in
|
||||
|
||||
Every element can be replaced on its own, because the content is plain Markdown:
|
||||
|
||||
- **Sveltia to Decap**: the same configuration format, so switching is one
|
||||
script tag.
|
||||
- **Astro to Hugo or Eleventy**: the Markdown stays untouched, only templates
|
||||
change.
|
||||
- **Gitea to any git hosting**: GitHub, GitLab, or an internal service.
|
||||
- Even dropping the whole approach is a trivial export, because the content has
|
||||
been sitting in an open format in our own repository from the start.
|
||||
|
||||
The conclusion we draw from this demo: start with the simplest solution that
|
||||
meets the requirements, which means a visual editor for the editors and a public
|
||||
site that needs no care. Add complexity only when a need appears that this
|
||||
approach cannot serve.
|
||||
@@ -0,0 +1,101 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" role="img" aria-label="Architecture of a database-backed CMS: one always-on application serves both the editor's admin panel and the reader's pages, backed by a database that needs backups and a separate media volume">
|
||||
<defs>
|
||||
<marker id="arrB" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M0 0 L10 5 L0 10 z" fill="#00004a"/>
|
||||
</marker>
|
||||
<filter id="softB" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="5" flood-color="#00004a" flood-opacity="0.10"/>
|
||||
</filter>
|
||||
<style>
|
||||
.t { font-family: Inter, "Segoe UI", Arial, sans-serif; }
|
||||
.box { fill: #ffffff; stroke: #00004a; stroke-width: 1.5; }
|
||||
.sub { fill: #f5f5f5; stroke: #e2e5ee; stroke-width: 1; }
|
||||
.ttl { font-weight: 700; font-size: 19px; fill: #00004a; letter-spacing: .04em; }
|
||||
.act { font-weight: 700; font-size: 18px; fill: #00004a; }
|
||||
.cap { font-size: 15px; fill: #46536a; }
|
||||
.sml { font-size: 14px; fill: #46536a; }
|
||||
.lbl { font-size: 14px; fill: #46536a; paint-order: stroke; stroke: #f8f9fc; stroke-width: 5; }
|
||||
.chipbg { fill: #e9ebf3; }
|
||||
.chip { font-size: 12.5px; font-weight: 700; fill: #00004a; letter-spacing: .06em; }
|
||||
.flow { stroke: #00004a; stroke-width: 1.8; fill: none; marker-end: url(#arrB); }
|
||||
.name { font-size: 16px; font-weight: 700; fill: #101228; }
|
||||
.pill { fill: #f5f5f5; stroke: #e2e5ee; }
|
||||
.pilltxt { font-size: 14px; fill: #101228; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- actor: editor -->
|
||||
<g filter="url(#softB)"><rect class="box" x="49" y="20" width="380" height="86" rx="16"/></g>
|
||||
<circle cx="80" cy="48" r="11" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<path d="M64 78 q16 -18 32 0" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<text class="t act" x="108" y="54">Editor</text>
|
||||
<text class="t sml" x="108" y="80">admin panel in the browser, signs in</text>
|
||||
|
||||
<!-- actor: reader -->
|
||||
<g filter="url(#softB)"><rect class="box" x="395" y="20" width="380" height="86" rx="16"/></g>
|
||||
<circle cx="426" cy="48" r="11" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<path d="M410 78 q16 -18 32 0" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<text class="t act" x="454" y="54">Reader</text>
|
||||
<text class="t sml" x="454" y="80">the public portal, assembled live</text>
|
||||
|
||||
<!-- machine boundary (same vertical skeleton as the static diagram) -->
|
||||
<rect x="30" y="158" width="740" height="560" rx="18" fill="#f8f9fc" stroke="#e2e5ee" stroke-width="1.5"/>
|
||||
<text class="t" x="400" y="194" text-anchor="middle" font-size="14" font-weight="700" fill="#00004a" letter-spacing=".18em">MACHINE · DATABASE-BACKED CMS</text>
|
||||
<text class="t sml" x="400" y="216" text-anchor="middle">WordPress, Strapi, Drupal and the like</text>
|
||||
|
||||
<path class="flow" d="M239 106 V232"/>
|
||||
<text class="t lbl" x="253" y="150">sign in to the panel</text>
|
||||
<path class="flow" d="M585 232 V110"/>
|
||||
<text class="t lbl" x="599" y="150">HTML built per request</text>
|
||||
|
||||
<!-- the application: always on, serves everybody -->
|
||||
<g filter="url(#softB)"><rect class="box" x="60" y="238" width="680" height="210" rx="16"/></g>
|
||||
<text class="t ttl" x="88" y="278">CMS APPLICATION</text>
|
||||
<rect class="chipbg" x="290" y="258" width="110" height="24" rx="12"/>
|
||||
<text class="t chip" x="345" y="275" text-anchor="middle">ALWAYS ON</text>
|
||||
<rect class="sub" x="84" y="298" width="204" height="130" rx="12"/>
|
||||
<text class="t name" x="104" y="330">admin panel</text>
|
||||
<text class="t cap" x="104" y="356">public address</text>
|
||||
<text class="t cap" x="104" y="378">and login, open</text>
|
||||
<text class="t cap" x="104" y="400">to the internet</text>
|
||||
<rect class="sub" x="298" y="298" width="204" height="130" rx="12"/>
|
||||
<text class="t name" x="318" y="330">page rendering</text>
|
||||
<text class="t cap" x="318" y="356">every visit is</text>
|
||||
<text class="t cap" x="318" y="378">work for the app</text>
|
||||
<text class="t cap" x="318" y="400">and the database</text>
|
||||
<rect class="sub" x="512" y="298" width="204" height="130" rx="12"/>
|
||||
<text class="t name" x="532" y="330">plugins</text>
|
||||
<text class="t cap" x="532" y="356">each with its own</text>
|
||||
<text class="t cap" x="532" y="378">update cycle and</text>
|
||||
<text class="t cap" x="532" y="400">its own bugs</text>
|
||||
|
||||
<path class="flow" d="M239 448 V504"/>
|
||||
<text class="t lbl" x="253" y="484">upload</text>
|
||||
<path class="flow" d="M585 448 V504"/>
|
||||
<text class="t lbl" x="599" y="484">SQL</text>
|
||||
|
||||
<!-- media volume -->
|
||||
<g filter="url(#softB)"><rect class="sub" x="60" y="508" width="310" height="170" rx="16"/></g>
|
||||
<text class="t ttl" x="88" y="546">media/</text>
|
||||
<rect class="chipbg" x="180" y="526" width="86" height="24" rx="12"/>
|
||||
<text class="t chip" x="223" y="543" text-anchor="middle">VOLUME</text>
|
||||
<text class="t cap" x="88" y="582">images and files</text>
|
||||
<text class="t cap" x="88" y="606">outside the database</text>
|
||||
<text class="t cap" x="88" y="630">backed up separately</text>
|
||||
|
||||
<!-- database -->
|
||||
<g filter="url(#softB)"><rect class="box" x="430" y="508" width="310" height="170" rx="16"/></g>
|
||||
<text class="t ttl" x="458" y="546">DATABASE</text>
|
||||
<rect class="chipbg" x="590" y="526" width="110" height="24" rx="12"/>
|
||||
<text class="t chip" x="645" y="543" text-anchor="middle">ALWAYS ON</text>
|
||||
<text class="t cap" x="458" y="582">content and accounts</text>
|
||||
<text class="t cap" x="458" y="606">backups and restore drills</text>
|
||||
<text class="t cap" x="458" y="630">schema migrations</text>
|
||||
<text class="t cap" x="458" y="654">on every upgrade</text>
|
||||
|
||||
<!-- summary pills -->
|
||||
<rect class="pill" x="60" y="740" width="330" height="46" rx="23"/>
|
||||
<text class="t pilltxt" x="225" y="768" text-anchor="middle">two processes run non stop, day and night</text>
|
||||
<rect class="pill" x="410" y="740" width="330" height="46" rx="23"/>
|
||||
<text class="t pilltxt" x="575" y="768" text-anchor="middle">security updates are a standing duty</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.0 KiB |
@@ -0,0 +1,105 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 1040" role="img" aria-label="Architecture on the demo machine: the editor writes through Sveltia into the Gitea content repository, a systemd timer runs a one-shot Astro build container that overlays content onto the code repository, the result lands in the releases volume and nginx serves readers static files only">
|
||||
<defs>
|
||||
<marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M0 0 L10 5 L0 10 z" fill="#00004a"/>
|
||||
</marker>
|
||||
<filter id="soft" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="5" flood-color="#00004a" flood-opacity="0.10"/>
|
||||
</filter>
|
||||
<style>
|
||||
.t { font-family: Inter, "Segoe UI", Arial, sans-serif; }
|
||||
.box { fill: #ffffff; stroke: #00004a; stroke-width: 1.5; }
|
||||
.sub { fill: #f5f5f5; stroke: #e2e5ee; stroke-width: 1; }
|
||||
.ttl { font-weight: 700; font-size: 19px; fill: #00004a; letter-spacing: .04em; }
|
||||
.act { font-weight: 700; font-size: 18px; fill: #00004a; }
|
||||
.cap { font-size: 15px; fill: #46536a; }
|
||||
.sml { font-size: 14px; fill: #46536a; }
|
||||
.lbl { font-size: 14px; fill: #46536a; paint-order: stroke; stroke: #f8f9fc; stroke-width: 5; }
|
||||
.chipbg { fill: #d2faf7; }
|
||||
.chip { font-size: 12.5px; font-weight: 700; fill: #00004a; letter-spacing: .06em; }
|
||||
.flow { stroke: #00004a; stroke-width: 1.8; fill: none; marker-end: url(#arr); }
|
||||
.mono { font-family: "SF Mono", Consolas, Menlo, monospace; font-size: 14px; fill: #101228; }
|
||||
.name { font-size: 16px; font-weight: 700; fill: #101228; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- actor: editor -->
|
||||
<g filter="url(#soft)"><rect class="box" x="49" y="20" width="380" height="86" rx="16"/></g>
|
||||
<circle cx="80" cy="48" r="11" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<path d="M64 78 q16 -18 32 0" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<text class="t act" x="108" y="54">Editor</text>
|
||||
<text class="t sml" x="108" y="80">browser · <tspan font-weight="600">/admin/</tspan> (Sveltia), visual editing</text>
|
||||
|
||||
<!-- machine boundary -->
|
||||
<rect x="30" y="158" width="740" height="730" rx="18" fill="#f8f9fc" stroke="#e2e5ee" stroke-width="1.5"/>
|
||||
<text class="t" x="400" y="194" text-anchor="middle" font-size="14" font-weight="700" fill="#00004a" letter-spacing=".18em">DEMO MACHINE · PODMAN</text>
|
||||
<text class="t sml" x="400" y="216" text-anchor="middle">separate containers under systemd</text>
|
||||
|
||||
<path class="flow" d="M239 106 V232"/>
|
||||
<text class="t lbl" x="253" y="150">save through the API (OAuth)</text>
|
||||
|
||||
<!-- gitea: two repositories -->
|
||||
<g filter="url(#soft)"><rect class="box" x="60" y="238" width="680" height="190" rx="16"/></g>
|
||||
<text class="t ttl" x="88" y="278">GITEA</text>
|
||||
<rect class="chipbg" x="170" y="258" width="62" height="24" rx="12"/>
|
||||
<text class="t chip" x="201" y="275" text-anchor="middle">:3000</text>
|
||||
<rect class="sub" x="84" y="298" width="310" height="108" rx="12"/>
|
||||
<text class="t name" x="106" y="330">ctao/content</text>
|
||||
<text class="t cap" x="106" y="356">Markdown and images,</text>
|
||||
<text class="t cap" x="106" y="380">written by the CMS</text>
|
||||
<rect class="sub" x="414" y="298" width="302" height="108" rx="12"/>
|
||||
<text class="t name" x="436" y="330">ctao/portal</text>
|
||||
<text class="t cap" x="436" y="356">portal code,</text>
|
||||
<text class="t cap" x="436" y="380">maintained by the team</text>
|
||||
|
||||
<path class="flow" d="M400 428 V454"/>
|
||||
<text class="t lbl" x="414" y="447">git fetch, both repos</text>
|
||||
|
||||
<!-- build (ephemeral) -->
|
||||
<g filter="url(#soft)">
|
||||
<rect x="60" y="460" width="680" height="190" rx="16" fill="#ffffff" stroke="#00004a" stroke-width="1.5" stroke-dasharray="7 5"/>
|
||||
</g>
|
||||
<text class="t ttl" x="88" y="500">BUILD · ASTRO</text>
|
||||
<rect class="chipbg" x="250" y="480" width="190" height="24" rx="12"/>
|
||||
<text class="t chip" x="345" y="497" text-anchor="middle">ONE-SHOT CONTAINER</text>
|
||||
<text class="t cap" x="88" y="536">a systemd timer checks both repositories every 10 seconds</text>
|
||||
<text class="t cap" x="88" y="564">on a change: fetch both, overlay content onto code</text>
|
||||
<text class="t cap" x="88" y="592" font-weight="700" fill="#101228">Markdown becomes static HTML</text>
|
||||
<text class="t cap" x="88" y="620">the container exits when the build is done</text>
|
||||
|
||||
<path class="flow" d="M400 650 V676"/>
|
||||
<text class="t lbl" x="414" y="669">new version of the site</text>
|
||||
|
||||
<!-- releases volume -->
|
||||
<g filter="url(#soft)"><rect class="sub" x="60" y="682" width="310" height="176" rx="16"/></g>
|
||||
<text class="t ttl" x="88" y="720">releases/</text>
|
||||
<rect class="chipbg" x="196" y="700" width="86" height="24" rx="12"/>
|
||||
<text class="t chip" x="239" y="717" text-anchor="middle">VOLUME</text>
|
||||
<text class="t mono" x="88" y="754">9f2c1d-43e2bb/ previous</text>
|
||||
<text class="t mono" x="88" y="778">71b980-8c1a2f/ <tspan font-weight="700" fill="#00004a">← current</tspan></text>
|
||||
<text class="t cap" x="88" y="812">atomic swap,</text>
|
||||
<text class="t cap" x="88" y="836">zero downtime</text>
|
||||
|
||||
<!-- nginx -->
|
||||
<g filter="url(#soft)"><rect class="box" x="430" y="682" width="310" height="176" rx="16"/></g>
|
||||
<text class="t ttl" x="458" y="720">NGINX</text>
|
||||
<rect class="chipbg" x="540" y="700" width="62" height="24" rx="12"/>
|
||||
<text class="t chip" x="571" y="717" text-anchor="middle">:8080</text>
|
||||
<text class="t cap" x="458" y="754">serves <tspan font-weight="600">releases/current</tspan></text>
|
||||
<text class="t cap" x="458" y="782">static files only</text>
|
||||
<text class="t cap" x="458" y="810">no application, no database,</text>
|
||||
<text class="t cap" x="458" y="836">no PHP, no plugins</text>
|
||||
|
||||
<path class="flow" d="M372 770 H424"/>
|
||||
<text class="t lbl" x="398" y="756" text-anchor="middle">current/</text>
|
||||
|
||||
<path class="flow" d="M585 860 V930"/>
|
||||
<text class="t lbl" x="599" y="900">static HTML</text>
|
||||
|
||||
<!-- actor: reader -->
|
||||
<g filter="url(#soft)"><rect class="box" x="395" y="940" width="380" height="86" rx="16"/></g>
|
||||
<circle cx="426" cy="968" r="11" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<path d="M410 998 q16 -18 32 0" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<text class="t act" x="454" y="974">Reader</text>
|
||||
<text class="t sml" x="454" y="1000">the public portal, plain and fast HTML</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.3 KiB |
@@ -1,110 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 640" role="img" aria-label="Architektura klasycznego CMS z bazą danych: aplikacja CMS z publicznym panelem admina działa non stop, obok baza danych i wolumen mediów; czytelnik dostaje strony renderowane na żądanie">
|
||||
<defs>
|
||||
<marker id="arrB" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M0 0 L10 5 L0 10 z" fill="#00004a"/>
|
||||
</marker>
|
||||
<filter id="softB" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="5" flood-color="#00004a" flood-opacity="0.10"/>
|
||||
</filter>
|
||||
<style>
|
||||
.t { font-family: Inter, "Segoe UI", Arial, sans-serif; }
|
||||
.box { fill: #ffffff; stroke: #00004a; stroke-width: 1.5; }
|
||||
.sub { fill: #f5f5f5; stroke: #e2e5ee; stroke-width: 1; }
|
||||
.ttl { font-weight: 700; font-size: 15.5px; fill: #00004a; letter-spacing: .04em; }
|
||||
.cap { font-size: 12.5px; fill: #46536a; }
|
||||
.lbl { font-size: 12px; fill: #46536a; paint-order: stroke; stroke: #ffffff; stroke-width: 4; }
|
||||
.chipbg { fill: #e9ebf3; }
|
||||
.chip { font-size: 11.5px; font-weight: 700; fill: #00004a; letter-spacing: .06em; }
|
||||
.flow { stroke: #00004a; stroke-width: 1.8; fill: none; marker-end: url(#arrB); }
|
||||
.pill { fill: #f5f5f5; stroke: #e2e5ee; }
|
||||
.pilltxt { font-size: 12.5px; fill: #101228; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- machine boundary (mirrors the static-demo diagram 1:1) -->
|
||||
<rect x="340" y="40" width="830" height="570" rx="18" fill="#f8f9fc" stroke="#e2e5ee" stroke-width="1.5"/>
|
||||
<text class="t" x="366" y="76" font-size="13" font-weight="700" fill="#00004a" letter-spacing=".18em">MASZYNA · CMS Z BAZĄ DANYCH</text>
|
||||
<text class="t cap" x="366" y="96">np. WordPress, Strapi, Drupal — wspólny schemat tej klasy rozwiązań</text>
|
||||
|
||||
<!-- actor: redaktor -->
|
||||
<g filter="url(#softB)">
|
||||
<rect class="box" x="52" y="120" width="220" height="120" rx="16"/>
|
||||
</g>
|
||||
<circle cx="88" cy="156" r="11" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<path d="M72 186 q16 -18 32 0" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<text class="t ttl" x="116" y="162">Redaktor</text>
|
||||
<text class="t cap" x="70" y="212">panel admina w przeglądarce</text>
|
||||
<text class="t cap" x="70" y="228">logowanie do aplikacji</text>
|
||||
|
||||
<!-- actor: czytelnik -->
|
||||
<g filter="url(#softB)">
|
||||
<rect class="box" x="52" y="400" width="220" height="120" rx="16"/>
|
||||
</g>
|
||||
<circle cx="88" cy="436" r="11" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<path d="M72 466 q16 -18 32 0" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<text class="t ttl" x="116" y="442">Czytelnik</text>
|
||||
<text class="t cap" x="70" y="492">publiczna strona portalu</text>
|
||||
<text class="t cap" x="70" y="508">HTML składany na żądanie</text>
|
||||
|
||||
<!-- CMS application (tall box, always running) -->
|
||||
<g filter="url(#softB)">
|
||||
<rect class="box" x="400" y="120" width="320" height="400" rx="16"/>
|
||||
</g>
|
||||
<text class="t ttl" x="424" y="152">APLIKACJA CMS</text>
|
||||
<rect class="chipbg" x="590" y="136" width="106" height="22" rx="11"/>
|
||||
<text class="t chip" x="643" y="151" text-anchor="middle">NON STOP</text>
|
||||
<rect class="sub" x="420" y="176" width="280" height="88" rx="12"/>
|
||||
<text class="t" x="436" y="202" font-size="13.5" font-weight="700" fill="#101228">panel admina</text>
|
||||
<text class="t cap" x="436" y="224">publiczny adres + logowanie</text>
|
||||
<text class="t cap" x="436" y="242">z internetu</text>
|
||||
<rect class="sub" x="420" y="278" width="280" height="88" rx="12"/>
|
||||
<text class="t" x="436" y="304" font-size="13.5" font-weight="700" fill="#101228">renderowanie stron</text>
|
||||
<text class="t cap" x="436" y="326">każde wejście czytelnika =</text>
|
||||
<text class="t cap" x="436" y="344">praca aplikacji i bazy</text>
|
||||
<rect class="sub" x="420" y="380" width="280" height="88" rx="12"/>
|
||||
<text class="t" x="436" y="406" font-size="13.5" font-weight="700" fill="#101228">pluginy / rozszerzenia</text>
|
||||
<text class="t cap" x="436" y="428">własny cykl aktualizacji</text>
|
||||
<text class="t cap" x="436" y="446">i podatności</text>
|
||||
|
||||
<!-- database -->
|
||||
<g filter="url(#softB)">
|
||||
<rect class="box" x="790" y="120" width="340" height="185" rx="16"/>
|
||||
</g>
|
||||
<text class="t ttl" x="814" y="152">BAZA DANYCH</text>
|
||||
<rect class="chipbg" x="972" y="136" width="106" height="22" rx="11"/>
|
||||
<text class="t chip" x="1025" y="151" text-anchor="middle">NON STOP</text>
|
||||
<text class="t cap" x="814" y="188">treść + konta użytkowników</text>
|
||||
<text class="t cap" x="814" y="212">wymaga backupów i testów odtwarzania</text>
|
||||
<text class="t cap" x="814" y="236">migracje schematu przy aktualizacjach</text>
|
||||
<text class="t cap" x="814" y="266">historia zmian: zależnie od produktu,</text>
|
||||
<text class="t cap" x="814" y="284">bywa funkcją płatną</text>
|
||||
|
||||
<!-- media volume -->
|
||||
<g filter="url(#softB)">
|
||||
<rect class="sub" x="790" y="400" width="340" height="120" rx="16"/>
|
||||
</g>
|
||||
<text class="t ttl" x="814" y="432">media/</text>
|
||||
<rect class="chipbg" x="896" y="416" width="82" height="22" rx="11"/>
|
||||
<text class="t chip" x="937" y="431" text-anchor="middle">WOLUMEN</text>
|
||||
<text class="t cap" x="814" y="462">obrazy i pliki poza bazą</text>
|
||||
<text class="t cap" x="814" y="480">osobny backup</text>
|
||||
|
||||
<!-- flows -->
|
||||
<path class="flow" d="M272 180 H396"/>
|
||||
<text class="t lbl" x="334" y="170" text-anchor="middle">logowanie do panelu</text>
|
||||
|
||||
<path class="flow" d="M400 460 H276"/>
|
||||
<text class="t lbl" x="338" y="450" text-anchor="middle">HTML na żądanie</text>
|
||||
|
||||
<path class="flow" d="M720 212 H786"/>
|
||||
<text class="t lbl" x="753" y="202" text-anchor="middle">SQL</text>
|
||||
|
||||
<path class="flow" d="M720 440 H786"/>
|
||||
<text class="t lbl" x="753" y="430" text-anchor="middle">upload</text>
|
||||
|
||||
<!-- summary pills -->
|
||||
<rect class="pill" x="380" y="552" width="360" height="36" rx="18"/>
|
||||
<text class="t pilltxt" x="560" y="575" text-anchor="middle">2 procesy działają bez przerwy, także nocą</text>
|
||||
<rect class="pill" x="770" y="552" width="360" height="36" rx="18"/>
|
||||
<text class="t pilltxt" x="950" y="575" text-anchor="middle">aktualizacje bezpieczeństwa = stały obowiązek</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.1 KiB |
@@ -1,111 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 640" role="img" aria-label="Architektura demo na maszynie: redaktor i czytelnik łączą się z kontenerami podmana — Gitea, build Astro, wolumen releases i nginx">
|
||||
<defs>
|
||||
<marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M0 0 L10 5 L0 10 z" fill="#00004a"/>
|
||||
</marker>
|
||||
<filter id="soft" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="5" flood-color="#00004a" flood-opacity="0.10"/>
|
||||
</filter>
|
||||
<style>
|
||||
.t { font-family: Inter, "Segoe UI", Arial, sans-serif; }
|
||||
.box { fill: #ffffff; stroke: #00004a; stroke-width: 1.5; }
|
||||
.sub { fill: #f5f5f5; stroke: #e2e5ee; stroke-width: 1; }
|
||||
.ttl { font-weight: 700; font-size: 15.5px; fill: #00004a; letter-spacing: .04em; }
|
||||
.cap { font-size: 12.5px; fill: #46536a; }
|
||||
.lbl { font-size: 12px; fill: #46536a; paint-order: stroke; stroke: #ffffff; stroke-width: 4; }
|
||||
.chipbg { fill: #d2faf7; }
|
||||
.chip { font-size: 11.5px; font-weight: 700; fill: #00004a; letter-spacing: .06em; }
|
||||
.flow { stroke: #00004a; stroke-width: 1.8; fill: none; marker-end: url(#arr); }
|
||||
.mono { font-family: "SF Mono", Consolas, Menlo, monospace; font-size: 12.5px; fill: #101228; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- machine boundary -->
|
||||
<rect x="340" y="40" width="830" height="570" rx="18" fill="#f8f9fc" stroke="#e2e5ee" stroke-width="1.5"/>
|
||||
<text class="t" x="366" y="76" font-size="13" font-weight="700" fill="#00004a" letter-spacing=".18em">MASZYNA DEMO · PODMAN</text>
|
||||
<text class="t cap" x="366" y="96">osobne kontenery pod systemd · wszystko w jednym katalogu użytkownika</text>
|
||||
|
||||
<!-- actor: redaktor -->
|
||||
<g filter="url(#soft)">
|
||||
<rect class="box" x="52" y="120" width="220" height="120" rx="16"/>
|
||||
</g>
|
||||
<circle cx="88" cy="156" r="11" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<path d="M72 186 q16 -18 32 0" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<text class="t ttl" x="116" y="162">Redaktor</text>
|
||||
<text class="t cap" x="70" y="212">przeglądarka · <tspan font-weight="600">/admin/</tspan> (Sveltia)</text>
|
||||
<text class="t cap" x="70" y="228">edycja WYSIWYG, podgląd 1:1</text>
|
||||
|
||||
<!-- actor: czytelnik -->
|
||||
<g filter="url(#soft)">
|
||||
<rect class="box" x="52" y="400" width="220" height="120" rx="16"/>
|
||||
</g>
|
||||
<circle cx="88" cy="436" r="11" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<path d="M72 466 q16 -18 32 0" fill="none" stroke="#00004a" stroke-width="1.8"/>
|
||||
<text class="t ttl" x="116" y="442">Czytelnik</text>
|
||||
<text class="t cap" x="70" y="492">publiczna strona portalu</text>
|
||||
<text class="t cap" x="70" y="508">czysty, szybki HTML</text>
|
||||
|
||||
<!-- gitea -->
|
||||
<g filter="url(#soft)">
|
||||
<rect class="box" x="400" y="120" width="320" height="185" rx="16"/>
|
||||
</g>
|
||||
<text class="t ttl" x="424" y="152">GITEA</text>
|
||||
<rect class="chipbg" x="490" y="136" width="52" height="22" rx="11"/>
|
||||
<text class="t chip" x="516" y="151" text-anchor="middle">:3000</text>
|
||||
<rect class="sub" x="420" y="172" width="280" height="112" rx="12"/>
|
||||
<text class="t" x="436" y="198" font-size="13.5" font-weight="700" fill="#101228">repo ctao/portal</text>
|
||||
<text class="t cap" x="436" y="220">treść: Markdown + obrazy (WebP)</text>
|
||||
<text class="t cap" x="436" y="238">każda zmiana = commit</text>
|
||||
<text class="t cap" x="436" y="256">pełna historia + rollback za darmo</text>
|
||||
|
||||
<!-- build (ephemeral) -->
|
||||
<g filter="url(#soft)">
|
||||
<rect x="790" y="120" width="340" height="185" rx="16" fill="#ffffff" stroke="#00004a" stroke-width="1.5" stroke-dasharray="7 5"/>
|
||||
</g>
|
||||
<text class="t ttl" x="814" y="152">BUILD · ASTRO</text>
|
||||
<rect class="chipbg" x="948" y="136" width="164" height="22" rx="11"/>
|
||||
<text class="t chip" x="1030" y="151" text-anchor="middle">KONTENER JEDNORAZOWY</text>
|
||||
<text class="t cap" x="814" y="188">timer systemd (co ~10 s) sprawdza,</text>
|
||||
<text class="t cap" x="814" y="206">czy w repo jest nowy commit</text>
|
||||
<text class="t cap" x="814" y="234">jeśli tak: git fetch → checkout →</text>
|
||||
<text class="t cap" x="814" y="252"><tspan font-weight="700" fill="#101228">Markdown → statyczny HTML</tspan></text>
|
||||
<text class="t cap" x="814" y="280">po buildzie kontener znika</text>
|
||||
|
||||
<!-- releases volume -->
|
||||
<g filter="url(#soft)">
|
||||
<rect class="sub" x="790" y="400" width="340" height="150" rx="16"/>
|
||||
</g>
|
||||
<text class="t ttl" x="814" y="432">releases/</text>
|
||||
<rect class="chipbg" x="908" y="416" width="82" height="22" rx="11"/>
|
||||
<text class="t chip" x="949" y="431" text-anchor="middle">WOLUMEN</text>
|
||||
<text class="t mono" x="814" y="466">9f2c1d/ poprzednia wersja</text>
|
||||
<text class="t mono" x="814" y="488">71b980/ <tspan font-weight="700" fill="#00004a">← current (symlink)</tspan></text>
|
||||
<text class="t cap" x="814" y="518">podmiana atomowa — zero przestoju</text>
|
||||
|
||||
<!-- nginx -->
|
||||
<g filter="url(#soft)">
|
||||
<rect class="box" x="400" y="400" width="320" height="150" rx="16"/>
|
||||
</g>
|
||||
<text class="t ttl" x="424" y="432">NGINX</text>
|
||||
<rect class="chipbg" x="500" y="416" width="52" height="22" rx="11"/>
|
||||
<text class="t chip" x="526" y="431" text-anchor="middle">:8080</text>
|
||||
<text class="t cap" x="424" y="466">serwuje <tspan font-weight="600">releases/current</tspan></text>
|
||||
<text class="t cap" x="424" y="484">wyłącznie statyczne pliki</text>
|
||||
<text class="t cap" x="424" y="502">brak aplikacji · brak bazy · brak PHP</text>
|
||||
|
||||
<!-- flows -->
|
||||
<path class="flow" d="M272 180 H396"/>
|
||||
<text class="t lbl" x="334" y="170" text-anchor="middle">zapis przez API (OAuth)</text>
|
||||
|
||||
<path class="flow" d="M790 212 H724"/>
|
||||
<text class="t lbl" x="757" y="202" text-anchor="middle">git fetch</text>
|
||||
|
||||
<path class="flow" d="M960 305 V396"/>
|
||||
<text class="t lbl" x="968" y="354" text-anchor="start">nowa wersja strony</text>
|
||||
|
||||
<path class="flow" d="M790 475 H724"/>
|
||||
<text class="t lbl" x="757" y="465" text-anchor="middle">current/</text>
|
||||
|
||||
<path class="flow" d="M400 475 H276"/>
|
||||
<text class="t lbl" x="338" y="465" text-anchor="middle">statyczny HTML</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.1 KiB |
@@ -0,0 +1,48 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 540" role="img" aria-label="Attack surface compared: a database-backed CMS exposes an application, a login panel, a database and plugins, while this solution exposes static HTML files only and keeps editing behind a git sign-in">
|
||||
<defs>
|
||||
<filter id="soft3" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="5" flood-color="#00004a" flood-opacity="0.10"/>
|
||||
</filter>
|
||||
<marker id="arr3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M0 0 L10 5 L0 10 z" fill="#00004a"/>
|
||||
</marker>
|
||||
<style>
|
||||
.t { font-family: Inter, "Segoe UI", Arial, sans-serif; }
|
||||
.hd { font-weight: 800; font-size: 16px; fill: #00004a; letter-spacing: .14em; }
|
||||
.cap { font-size: 14.5px; fill: #46536a; }
|
||||
.item { font-size: 15.5px; fill: #101228; }
|
||||
.side { font-size: 15px; fill: #101228; }
|
||||
.bad { fill: #fdf0f0; stroke: #e8c5c5; stroke-width: 1.5; }
|
||||
.good { fill: #ffffff; stroke: #00004a; stroke-width: 1.5; }
|
||||
.flow { stroke: #00004a; stroke-width: 1.8; fill: none; marker-end: url(#arr3); }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- top: classic CMS -->
|
||||
<text class="t hd" x="40" y="48">DATABASE-BACKED CMS</text>
|
||||
<text class="t side" x="40" y="173">internet</text>
|
||||
<path class="flow" d="M120 168 H176"/>
|
||||
<g filter="url(#soft3)"><rect class="bad" x="180" y="68" width="580" height="200" rx="16"/></g>
|
||||
<text class="t item" x="208" y="106">▪ CMS application (PHP / Node), public</text>
|
||||
<text class="t item" x="208" y="140">▪ login panel, public</text>
|
||||
<text class="t item" x="208" y="174">▪ database holding the content</text>
|
||||
<text class="t item" x="208" y="208">▪ plugins and dependencies</text>
|
||||
<text class="t" x="208" y="242" font-size="14.5" font-weight="700" fill="#8f3b3b">all of it runs non stop and needs patching</text>
|
||||
|
||||
<!-- divider -->
|
||||
<line x1="40" y1="300" x2="760" y2="300" stroke="#e2e5ee" stroke-width="1.5"/>
|
||||
|
||||
<!-- bottom: this solution -->
|
||||
<text class="t hd" x="40" y="334">THIS SOLUTION</text>
|
||||
<text class="t side" x="40" y="389">internet</text>
|
||||
<path class="flow" d="M120 384 H176"/>
|
||||
<g filter="url(#soft3)"><rect class="good" x="180" y="346" width="580" height="76" rx="16"/></g>
|
||||
<text class="t item" x="208" y="378">▪ HTML files served by nginx</text>
|
||||
<text class="t cap" x="208" y="404">no application, no database, no login panel</text>
|
||||
|
||||
<text class="t side" x="40" y="483">editing</text>
|
||||
<path class="flow" d="M120 478 H176"/>
|
||||
<g filter="url(#soft3)"><rect class="good" x="180" y="440" width="580" height="76" rx="16"/></g>
|
||||
<text class="t item" x="208" y="472">▪ git (Gitea), signed-in users only</text>
|
||||
<text class="t cap" x="208" y="498">off the reader's path, so an outage is invisible</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 900" role="img" aria-label="Grafika: graf commitów w gicie przechodzący w statyczną stronę WWW">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 900" role="img" aria-label="Illustration: a graph of git commits flowing into a static web page">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#00004a"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
@@ -1,76 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 430" role="img" aria-label="Pięć kroków od zapisu w edytorze do publikacji: zapis, commit, detekcja, build, publikacja">
|
||||
<defs>
|
||||
<marker id="arr2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M0 0 L10 5 L0 10 z" fill="#00004a"/>
|
||||
</marker>
|
||||
<filter id="soft2" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="5" flood-color="#00004a" flood-opacity="0.10"/>
|
||||
</filter>
|
||||
<style>
|
||||
.t { font-family: Inter, "Segoe UI", Arial, sans-serif; }
|
||||
.card { fill: #ffffff; stroke: #00004a; stroke-width: 1.5; }
|
||||
.ttl { font-weight: 700; font-size: 15px; fill: #00004a; }
|
||||
.cap { font-size: 12.3px; fill: #46536a; }
|
||||
.num { font-weight: 800; font-size: 15px; fill: #00004a; }
|
||||
.pill { fill: #f5f5f5; stroke: #e2e5ee; }
|
||||
.pilltxt { font-size: 12.5px; fill: #101228; }
|
||||
.flow { stroke: #00004a; stroke-width: 1.8; fill: none; marker-end: url(#arr2); }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- cards -->
|
||||
<g filter="url(#soft2)"><rect class="card" x="55" y="100" width="200" height="175" rx="16"/></g>
|
||||
<g filter="url(#soft2)"><rect class="card" x="285" y="100" width="200" height="175" rx="16"/></g>
|
||||
<g filter="url(#soft2)"><rect class="card" x="515" y="100" width="200" height="175" rx="16"/></g>
|
||||
<g filter="url(#soft2)"><rect class="card" x="745" y="100" width="200" height="175" rx="16"/></g>
|
||||
<g filter="url(#soft2)"><rect class="card" x="975" y="100" width="200" height="175" rx="16"/></g>
|
||||
|
||||
<!-- connectors -->
|
||||
<path class="flow" d="M255 187 H281"/>
|
||||
<path class="flow" d="M485 187 H511"/>
|
||||
<path class="flow" d="M715 187 H741"/>
|
||||
<path class="flow" d="M945 187 H971"/>
|
||||
|
||||
<!-- step numbers -->
|
||||
<circle cx="155" cy="100" r="17" fill="#00e4d8"/><text class="t num" x="155" y="106" text-anchor="middle">1</text>
|
||||
<circle cx="385" cy="100" r="17" fill="#00e4d8"/><text class="t num" x="385" y="106" text-anchor="middle">2</text>
|
||||
<circle cx="615" cy="100" r="17" fill="#00e4d8"/><text class="t num" x="615" y="106" text-anchor="middle">3</text>
|
||||
<circle cx="845" cy="100" r="17" fill="#00e4d8"/><text class="t num" x="845" y="106" text-anchor="middle">4</text>
|
||||
<circle cx="1075" cy="100" r="17" fill="#00e4d8"/><text class="t num" x="1075" y="106" text-anchor="middle">5</text>
|
||||
|
||||
<!-- card 1 -->
|
||||
<text class="t ttl" x="155" y="152" text-anchor="middle">Zapis w edytorze</text>
|
||||
<text class="t cap" x="155" y="182" text-anchor="middle">Sveltia · WYSIWYG</text>
|
||||
<text class="t cap" x="155" y="200" text-anchor="middle">podgląd artykułu 1:1</text>
|
||||
<text class="t cap" x="155" y="218" text-anchor="middle">obrazy → WebP przy uploadzie</text>
|
||||
|
||||
<!-- card 2 -->
|
||||
<text class="t ttl" x="385" y="152" text-anchor="middle">Commit w repo</text>
|
||||
<text class="t cap" x="385" y="182" text-anchor="middle">Gitea · logowanie OAuth</text>
|
||||
<text class="t cap" x="385" y="200" text-anchor="middle">zapis przez API = commit</text>
|
||||
<text class="t cap" x="385" y="218" text-anchor="middle">historia każdej zmiany</text>
|
||||
|
||||
<!-- card 3 -->
|
||||
<text class="t ttl" x="615" y="152" text-anchor="middle">Detekcja zmiany</text>
|
||||
<text class="t cap" x="615" y="182" text-anchor="middle">timer systemd</text>
|
||||
<text class="t cap" x="615" y="200" text-anchor="middle">sprawdza repo co ~10 s</text>
|
||||
<text class="t cap" x="615" y="218" text-anchor="middle">brak zmian = brak pracy</text>
|
||||
|
||||
<!-- card 4 -->
|
||||
<text class="t ttl" x="845" y="152" text-anchor="middle">Build strony</text>
|
||||
<text class="t cap" x="845" y="182" text-anchor="middle">jednorazowy kontener</text>
|
||||
<text class="t cap" x="845" y="200" text-anchor="middle">Astro: Markdown →</text>
|
||||
<text class="t cap" x="845" y="218" text-anchor="middle">statyczny HTML</text>
|
||||
|
||||
<!-- card 5 -->
|
||||
<text class="t ttl" x="1075" y="152" text-anchor="middle">Publikacja</text>
|
||||
<text class="t cap" x="1075" y="182" text-anchor="middle">atomowa podmiana symlinka</text>
|
||||
<text class="t cap" x="1075" y="200" text-anchor="middle">nginx od razu serwuje</text>
|
||||
<text class="t cap" x="1075" y="218" text-anchor="middle">nową wersję</text>
|
||||
|
||||
<!-- reassurance pills -->
|
||||
<rect class="pill" x="240" y="330" width="330" height="40" rx="20"/>
|
||||
<text class="t pilltxt" x="405" y="355" text-anchor="middle">awaria builda? strona działa dalej na starej wersji</text>
|
||||
<rect class="pill" x="630" y="330" width="330" height="40" rx="20"/>
|
||||
<text class="t pilltxt" x="795" y="355" text-anchor="middle">rollback = git revert — jedna komenda</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.5 KiB |
@@ -1,48 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 360" role="img" aria-label="Porównanie powierzchni ataku: CMS z bazą wystawia publicznie aplikację, bazę i pluginy; rozwiązanie statyczne wystawia tylko pliki HTML, a edycja idzie przez git">
|
||||
<defs>
|
||||
<filter id="soft3" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="5" flood-color="#00004a" flood-opacity="0.10"/>
|
||||
</filter>
|
||||
<marker id="arr3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M0 0 L10 5 L0 10 z" fill="#00004a"/>
|
||||
</marker>
|
||||
<style>
|
||||
.t { font-family: Inter, "Segoe UI", Arial, sans-serif; }
|
||||
.hd { font-weight: 800; font-size: 14px; fill: #00004a; letter-spacing: .14em; }
|
||||
.cap { font-size: 12.5px; fill: #46536a; }
|
||||
.item{ font-size: 13px; fill: #101228; }
|
||||
.bad { fill: #fdf0f0; stroke: #e8c5c5; stroke-width: 1.5; }
|
||||
.good{ fill: #ffffff; stroke: #00004a; stroke-width: 1.5; }
|
||||
.flow{ stroke: #00004a; stroke-width: 1.8; fill: none; marker-end: url(#arr3); }
|
||||
.lbl { font-size: 12px; fill: #46536a; paint-order: stroke; stroke: #ffffff; stroke-width: 4; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- left: classic CMS -->
|
||||
<text class="t hd" x="300" y="52" text-anchor="middle">CMS Z BAZĄ DANYCH</text>
|
||||
<text class="t item" x="60" y="150">internet</text>
|
||||
<path class="flow" d="M130 145 H186"/>
|
||||
<g filter="url(#soft3)"><rect class="bad" x="190" y="80" width="360" height="200" rx="16"/></g>
|
||||
<text class="t item" x="218" y="126">▪ aplikacja CMS (PHP / Node) — publiczna</text>
|
||||
<text class="t item" x="218" y="158">▪ panel logowania — publiczny</text>
|
||||
<text class="t item" x="218" y="190">▪ baza danych z treścią</text>
|
||||
<text class="t item" x="218" y="222">▪ pluginy i zależności</text>
|
||||
<text class="t cap" x="218" y="256" font-weight="700" fill="#8f3b3b">wszystko działa non stop i wymaga patchowania</text>
|
||||
|
||||
<!-- divider -->
|
||||
<line x1="600" y1="60" x2="600" y2="320" stroke="#e2e5ee" stroke-width="1.5"/>
|
||||
|
||||
<!-- right: static -->
|
||||
<text class="t hd" x="900" y="52" text-anchor="middle">TO ROZWIĄZANIE</text>
|
||||
<text class="t item" x="646" y="130">internet</text>
|
||||
<path class="flow" d="M716 125 H772"/>
|
||||
<g filter="url(#soft3)"><rect class="good" x="776" y="88" width="360" height="76" rx="16"/></g>
|
||||
<text class="t item" x="804" y="120">▪ pliki HTML — i nic więcej</text>
|
||||
<text class="t cap" x="804" y="146">nie ma czego zhakować ani patchować</text>
|
||||
|
||||
<text class="t item" x="646" y="252">edycja</text>
|
||||
<path class="flow" d="M716 247 H772"/>
|
||||
<g filter="url(#soft3)"><rect class="good" x="776" y="210" width="360" height="76" rx="16"/></g>
|
||||
<text class="t item" x="804" y="242">▪ git (Gitea) — tylko zalogowani</text>
|
||||
<text class="t cap" x="804" y="268">poza ścieżką czytelnika — awaria nie kładzie strony</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,54 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 620 600" role="img" aria-label="Five steps from a save in the editor to a published page: save, commit, change detected, site build, published">
|
||||
<defs>
|
||||
<filter id="soft2" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="5" flood-color="#00004a" flood-opacity="0.10"/>
|
||||
</filter>
|
||||
<style>
|
||||
.t { font-family: Inter, "Segoe UI", Arial, sans-serif; }
|
||||
.ttl { font-weight: 700; font-size: 18px; fill: #00004a; }
|
||||
.cap { font-size: 15px; fill: #46536a; }
|
||||
.num { font-weight: 800; font-size: 16px; fill: #00004a; }
|
||||
.pill { fill: #f5f5f5; stroke: #e2e5ee; }
|
||||
.pilltxt { font-size: 14px; fill: #101228; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- rail -->
|
||||
<line x1="60" y1="70" x2="60" y2="470" stroke="#c7cde9" stroke-width="2"/>
|
||||
|
||||
<!-- step 1 -->
|
||||
<circle cx="60" cy="70" r="20" fill="#00e4d8"/>
|
||||
<text class="t num" x="60" y="76" text-anchor="middle">1</text>
|
||||
<text class="t ttl" x="100" y="66">Save in the editor</text>
|
||||
<text class="t cap" x="100" y="92">Sveltia, visual editing with a preview that matches the page</text>
|
||||
|
||||
<!-- step 2 -->
|
||||
<circle cx="60" cy="170" r="20" fill="#00e4d8"/>
|
||||
<text class="t num" x="60" y="176" text-anchor="middle">2</text>
|
||||
<text class="t ttl" x="100" y="166">Commit in the repository</text>
|
||||
<text class="t cap" x="100" y="192">Gitea signs the editor in; each save becomes one commit</text>
|
||||
|
||||
<!-- step 3 -->
|
||||
<circle cx="60" cy="270" r="20" fill="#00e4d8"/>
|
||||
<text class="t num" x="60" y="276" text-anchor="middle">3</text>
|
||||
<text class="t ttl" x="100" y="266">Change detected</text>
|
||||
<text class="t cap" x="100" y="292">a systemd timer checks both repositories every 10 seconds</text>
|
||||
|
||||
<!-- step 4 -->
|
||||
<circle cx="60" cy="370" r="20" fill="#00e4d8"/>
|
||||
<text class="t num" x="60" y="376" text-anchor="middle">4</text>
|
||||
<text class="t ttl" x="100" y="366">Site build</text>
|
||||
<text class="t cap" x="100" y="392">a one-shot container runs Astro: Markdown becomes static HTML</text>
|
||||
|
||||
<!-- step 5 -->
|
||||
<circle cx="60" cy="470" r="20" fill="#00e4d8"/>
|
||||
<text class="t num" x="60" y="476" text-anchor="middle">5</text>
|
||||
<text class="t ttl" x="100" y="466">Published</text>
|
||||
<text class="t cap" x="100" y="492">an atomic symlink swap; nginx serves the new version at once</text>
|
||||
|
||||
<!-- reassurance -->
|
||||
<rect class="pill" x="40" y="524" width="310" height="46" rx="23"/>
|
||||
<text class="t pilltxt" x="195" y="552" text-anchor="middle">a failed build leaves the old version up</text>
|
||||
<rect class="pill" x="370" y="524" width="220" height="46" rx="23"/>
|
||||
<text class="t pilltxt" x="480" y="552" text-anchor="middle">rollback is one git revert</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,70 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 700" role="img" aria-label="The three parts of the stack: Sveltia CMS as the editor, git and Gitea as the database, Astro as the build-time generator; when a reader opens a page only nginx and the generated files are involved">
|
||||
<defs>
|
||||
<marker id="arr0" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M0 0 L10 5 L0 10 z" fill="#00004a"/>
|
||||
</marker>
|
||||
<filter id="soft0" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="2" stdDeviation="5" flood-color="#00004a" flood-opacity="0.10"/>
|
||||
</filter>
|
||||
<style>
|
||||
.t { font-family: Inter, "Segoe UI", Arial, sans-serif; }
|
||||
.card { fill: #ffffff; stroke: #00004a; stroke-width: 1.5; }
|
||||
.ttl { font-weight: 700; font-size: 19px; fill: #00004a; letter-spacing: .04em; }
|
||||
.cap { font-size: 15.5px; fill: #46536a; }
|
||||
.chipbg { fill: #d2faf7; }
|
||||
.chip { font-size: 12.5px; font-weight: 700; fill: #00004a; letter-spacing: .06em; }
|
||||
.gain { font-size: 15.5px; font-weight: 700; fill: #101228; }
|
||||
.flow { stroke: #00004a; stroke-width: 1.8; fill: none; marker-end: url(#arr0); }
|
||||
.lbl { font-size: 14px; fill: #46536a; }
|
||||
.band { fill: #f5f5f5; stroke: #e2e5ee; }
|
||||
.bandtxt { font-size: 14px; fill: #101228; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- row 1: the editor -->
|
||||
<g filter="url(#soft0)"><rect class="card" x="20" y="20" width="760" height="160" rx="16"/></g>
|
||||
<text class="t ttl" x="48" y="68">SVELTIA CMS</text>
|
||||
<rect class="chipbg" x="48" y="84" width="78" height="26" rx="13"/>
|
||||
<text class="t chip" x="87" y="102" text-anchor="middle">EDITOR</text>
|
||||
<text class="t gain" x="48" y="142">every save is</text>
|
||||
<text class="t gain" x="48" y="164">a commit</text>
|
||||
<line x1="284" y1="46" x2="284" y2="154" stroke="#e2e5ee" stroke-width="1"/>
|
||||
<circle cx="312" cy="64" r="3" fill="#00e4d8"/><text class="t cap" x="330" y="69">visual editing in the browser at /admin/</text>
|
||||
<circle cx="312" cy="98" r="3" fill="#00e4d8"/><text class="t cap" x="330" y="103">one JavaScript file, no server to run</text>
|
||||
<circle cx="312" cy="132" r="3" fill="#00e4d8"/><text class="t cap" x="330" y="137">signs in to Gitea over OAuth</text>
|
||||
|
||||
<path class="flow" d="M400 180 V220"/>
|
||||
<text class="t lbl" x="414" y="205">commit</text>
|
||||
|
||||
<!-- row 2: the storage -->
|
||||
<g filter="url(#soft0)"><rect class="card" x="20" y="224" width="760" height="160" rx="16"/></g>
|
||||
<text class="t ttl" x="48" y="272">GIT · GITEA</text>
|
||||
<rect class="chipbg" x="48" y="288" width="86" height="26" rx="13"/>
|
||||
<text class="t chip" x="91" y="306" text-anchor="middle">STORAGE</text>
|
||||
<text class="t gain" x="48" y="346">the repository</text>
|
||||
<text class="t gain" x="48" y="368">is the database</text>
|
||||
<line x1="284" y1="250" x2="284" y2="358" stroke="#e2e5ee" stroke-width="1"/>
|
||||
<circle cx="312" cy="268" r="3" fill="#00e4d8"/><text class="t cap" x="330" y="273">articles as Markdown files</text>
|
||||
<circle cx="312" cy="302" r="3" fill="#00e4d8"/><text class="t cap" x="330" y="307">images stored next to them</text>
|
||||
<circle cx="312" cy="336" r="3" fill="#00e4d8"/><text class="t cap" x="330" y="341">history, diff and revert built in</text>
|
||||
|
||||
<path class="flow" d="M400 384 V424"/>
|
||||
<text class="t lbl" x="414" y="409">build</text>
|
||||
|
||||
<!-- row 3: the generator -->
|
||||
<g filter="url(#soft0)"><rect class="card" x="20" y="428" width="760" height="160" rx="16"/></g>
|
||||
<text class="t ttl" x="48" y="476">ASTRO</text>
|
||||
<rect class="chipbg" x="48" y="492" width="110" height="26" rx="13"/>
|
||||
<text class="t chip" x="103" y="510" text-anchor="middle">BUILD ONLY</text>
|
||||
<text class="t gain" x="48" y="550">nothing of it runs</text>
|
||||
<text class="t gain" x="48" y="572">in production</text>
|
||||
<line x1="284" y1="454" x2="284" y2="562" stroke="#e2e5ee" stroke-width="1"/>
|
||||
<circle cx="312" cy="472" r="3" fill="#00e4d8"/><text class="t cap" x="330" y="477">Markdown becomes static HTML</text>
|
||||
<circle cx="312" cy="506" r="3" fill="#00e4d8"/><text class="t cap" x="330" y="511">article fields checked at build time</text>
|
||||
<circle cx="312" cy="540" r="3" fill="#00e4d8"/><text class="t cap" x="330" y="545">React islands where they are needed</text>
|
||||
|
||||
<!-- what the reader actually touches -->
|
||||
<rect class="band" x="20" y="616" width="760" height="64" rx="32"/>
|
||||
<text class="t bandtxt" x="400" y="645" text-anchor="middle">When a reader opens a page, only nginx and the generated files are involved.</text>
|
||||
<text class="t bandtxt" x="400" y="667" text-anchor="middle">No application, no database, no admin login.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |