Enhance index.html and custom.css for improved content presentation and new assets
- Updated index.html to refine content structure, including enhanced blockquotes and lists for better readability. - Added a new section to emphasize Kubernetes as a developer-centric tool. - Improved CSS styles in custom.css for blockquote presentation and slide layout adjustments. - Introduced new SVG and WEBP images for better visual representation of tools used in Kubernetes.
This commit is contained in:
@@ -37,27 +37,37 @@
|
||||
<section>
|
||||
<h1>Kubernetes : délire d’Ops ?</h1>
|
||||
<blockquote>
|
||||
<p>On vous l’a vendu comme un truc d’infra</p>
|
||||
<p>On nous l’a surtout vendu comme un truc d’infra.<br />
|
||||
<span class="blockquote-sub">Moi aussi, je suis passé par ce réflexe.</span>
|
||||
</p>
|
||||
</blockquote>
|
||||
<ul>
|
||||
<li>Complexe</li>
|
||||
<li>Réservé aux DevOps</li>
|
||||
<li>Magie noire</li>
|
||||
<li>« Trop complexe »</li>
|
||||
<li>Réservé aux spécialistes plateforme</li>
|
||||
<li>Magie noire / pas « mon » métier</li>
|
||||
</ul>
|
||||
<aside class="notes">
|
||||
<p>On vous a vendu Kubernetes comme un délire d’infra, réservé à des gens chelous.<br />
|
||||
En réalité… c’est beaucoup plus proche de ce que vous faites déjà.</p>
|
||||
<p>On nous présente souvent Kubernetes comme un délire d’infra, réservé à une poignée de spécialistes — j’ai
|
||||
eu
|
||||
ce réflexe aussi, et je m’y retrouve quand j’entends encore ce discours.<br />
|
||||
En réalité… c’est beaucoup plus proche de ce qu’on fait déjà en dev.</p>
|
||||
<p><em>Chaos scripts</em> — dev entouré de scripts, crons, alertes, terminal chargé, ambiance tendue
|
||||
(contraste avec la suite).</p>
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
<blockquote>
|
||||
<p>Kubernetes c'est un délire de dev</span>
|
||||
</p>
|
||||
</blockquote>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Kubernetes = API + état + boucle</h1>
|
||||
<ul>
|
||||
<li>API REST</li>
|
||||
<li>Base d’état</li>
|
||||
<li>Boucle de réconciliation</li>
|
||||
<li>Pas de mystère : des mécanismes explicites</li>
|
||||
<li>Base d’état (etcd)</li>
|
||||
<li>Boucle de réconciliation → events</li>
|
||||
<li>Pas de mystère → des mécanismes explicites (controllers, kubelet, containerd, etc.)</li>
|
||||
</ul>
|
||||
<aside class="notes">
|
||||
<p>Kubernetes, c’est juste une API, une base d’état, et une boucle qui corrige la réalité.<br />
|
||||
@@ -66,13 +76,25 @@
|
||||
</section>
|
||||
<section>
|
||||
<h1>De vos outils au noyau Linux</h1>
|
||||
<pre><code class="language-mermaid">flowchart LR
|
||||
A[Clients] --> B[kube-api]
|
||||
B --> C[etcd]
|
||||
B --> D[Controllers]
|
||||
D --> E[kubelet]
|
||||
E --> F[containerd]
|
||||
F --> G[Kernel Linux]
|
||||
<pre><code class="language-mermaid">
|
||||
flowchart LR
|
||||
B[kube-api]
|
||||
subgraph Clients
|
||||
A01[kubectl]
|
||||
A02@{ img: "img/helmsh-icon.svg", pos: "b", h: 44, constraint: "on" }
|
||||
A03@{ img: "img/logo-kustomize.B5HO6GxI_1tyDev.webp", pos: "b", h: 36, constraint: "on" }
|
||||
end
|
||||
A01 --> B
|
||||
A02 --> B
|
||||
A03 --> B
|
||||
D --> B
|
||||
B --> C[etcd]
|
||||
B --> D[Controllers]
|
||||
D --> E[kubelet]
|
||||
E --> F[containerd]
|
||||
F --> G[Kernel Linux]
|
||||
classDef logoBare fill:transparent,stroke:transparent,stroke-width:0px;
|
||||
class A02,A03 logoBare;
|
||||
</code></pre>
|
||||
<ul>
|
||||
<li><strong>kube-api</strong> — point d’entrée</li>
|
||||
@@ -91,34 +113,39 @@
|
||||
</code></pre>
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Kubernetes ne fait pas tourner vos apps</h1>
|
||||
<ul>
|
||||
<li>Il <strong>orchestre</strong></li>
|
||||
<li>Il <strong>délègue</strong></li>
|
||||
<li>Il <strong>observe</strong></li>
|
||||
</ul>
|
||||
<aside class="notes">
|
||||
<p>Ce n’est pas Kubernetes qui « exécute votre métier » comme un process magique : il coordonne, délègue aux
|
||||
agents, et observe l’état.<br />
|
||||
La suite : le cœur, c’est la boucle de réconciliation.</p>
|
||||
<p>(Cette slide remplace l’ancien zoom « kube-api → etcd » seul : le détail etcd est déjà dans la vue
|
||||
d’ensemble slide 4.)</p>
|
||||
</aside>
|
||||
</section>
|
||||
<section class="slide-boucle">
|
||||
<div class="slide-boucle-inner">
|
||||
<h1 class="slide-boucle-title">État voulu → réel : la boucle</h1>
|
||||
<h1 class="slide-boucle-title">Kubernetes ne fait pas tourner vos apps : la boucle de contrôle</h1>
|
||||
<ul class="slide-boucle-lead">
|
||||
<li>Il <strong>orchestre</strong> · il <strong>délègue</strong> · il <strong>observe</strong></li>
|
||||
</ul>
|
||||
<pre><code class="language-mermaid">flowchart LR
|
||||
A[Désiré] --> B[API] --> C[Controllers] --> D[Réel]
|
||||
DS[Spec · désir] --> AP[kube-apiserver]
|
||||
AP <-->|objets cluster| ET[(etcd)]
|
||||
AP --> EV[Watch · événements]
|
||||
EV --> CTRL[Controllers]
|
||||
CTRL -->|réconciliation · requêtes API| AP
|
||||
subgraph NODE[Nœud]
|
||||
K[kubelet] -->|CRI| CD[containerd]
|
||||
end
|
||||
AP -->|spec Pods · synchro| K
|
||||
K -->|status| AP
|
||||
</code></pre>
|
||||
<p class="slide-boucle-foot">
|
||||
<strong>↻</strong> réinjecte vers les contrôleurs · <em>chaque tour : observer, décider, agir</em>
|
||||
<strong>↻</strong> Les changements d’objets (Pods, etc.) vivent dans l’API ; le <strong>kubelet</strong> les
|
||||
<strong>synchronise</strong> depuis l’API, puis traduit en appels <strong>CRI</strong> vers
|
||||
<strong>containerd</strong>. Le <strong>status</strong> remonte par le même kubelet vers l’API.
|
||||
</p>
|
||||
</div>
|
||||
<aside class="notes">
|
||||
<p>Des agents exécutent les instructions côté nœud (kubelet, runtime), mais le fil conducteur, c’est : état
|
||||
voulu, comparaison, correction en boucle.</p>
|
||||
<p>Aligné sur la doc Kubernetes (<em>Controllers</em>) : boucle de contrôle = observer l’état du cluster,
|
||||
rapprocher le courant du <strong>spec</strong> désiré ; le plus souvent le controller envoie des messages au
|
||||
<strong>kube-apiserver</strong> (création / mise à jour d’objets), d’où des effets de bord (Pods à planifier,
|
||||
etc.). Les <strong>informers</strong> / watches alimentent des files d’<strong>événements</strong>.</p>
|
||||
<p>etcd est la persistance derrière l’API ; le <strong>status</strong> reflète ce qui tourne vraiment et
|
||||
repart dans la boucle. Le <strong>chemin des « commandes » vers containerd</strong> : mises à jour du
|
||||
<strong>spec</strong> dans l’API → le kubelet (watch / liste des Pods du nœud) → <strong>CRI</strong> →
|
||||
containerd. Le schéma du plan de contrôle côté masters reste API ↔ controllers ↔ API.</p>
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
@@ -142,10 +169,10 @@
|
||||
<h1>Kubernetes vous évite de vivre dans le noyau</h1>
|
||||
<ul>
|
||||
<li>Manipuler les <strong>cgroups</strong> à la main</li>
|
||||
<li>Gérer <strong>isolation</strong>, <strong>CPU</strong>, <strong>mémoire</strong> au niveau noyau</li>
|
||||
<li>Gérer <strong>isolation</strong>, <strong>CPU</strong>, <strong>mémoire</strong> au niveau des noyaux de vos differents noeuds</li>
|
||||
</ul>
|
||||
<blockquote>
|
||||
<p>À côté de ça, un cluster qui râle, c’est presque du repos.</p>
|
||||
<p>À côté de ça, un cluster qui râle, c'est presque du repos.</p>
|
||||
</blockquote>
|
||||
<aside class="notes">
|
||||
<p>Si Kubernetes existe, c’est pour vous éviter de vivre dans ces couches-là.<br />
|
||||
@@ -221,13 +248,6 @@
|
||||
Même mécanisme, autre niveau d’abstraction.</p>
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Automatiser le métier… en code dans le cluster</h1>
|
||||
<aside class="notes">
|
||||
<p>Un opérateur, c’est du code qui dit au cluster comment faire vivre un service au-delà du simple « Pod +
|
||||
Service » : upgrades, backup, réplication, etc.</p>
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Sans opérateur : scripts, crons, humains dans la boucle</h1>
|
||||
<ul>
|
||||
@@ -347,6 +367,65 @@ spec:
|
||||
</ul>
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Exemple Reflector : secret DB → namespace applicatif</h1>
|
||||
<p><em>Le secret « source de vérité » vit avec la base ; Reflector recrée un miroir là où l’app tourne.</em></p>
|
||||
<pre><code class="language-yaml"># Namespace database — secret maître (ex. mot de passe géré par l’opérateur / la DBA)
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: db-credentials
|
||||
namespace: database
|
||||
annotations:
|
||||
reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
|
||||
reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"
|
||||
reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "app-shop"
|
||||
stringData:
|
||||
DATABASE_URL: "postgres://…"
|
||||
</code></pre>
|
||||
<ul>
|
||||
<li>Reflector maintient un <strong>Secret du même nom</strong> dans <code>app-shop</code>, à jour quand la
|
||||
source change.</li>
|
||||
<li>Alternative : miroir manuel avec <code>reflector.v1.k8s.emberstack.com/reflects: "database/db-credentials"</code>.</li>
|
||||
</ul>
|
||||
<aside class="notes">
|
||||
<p>Scénario concret : l’équipe data garde le secret dans <code>database</code> ; l’app en
|
||||
<code>app-shop</code> n’a pas besoin d’accès RBAC au namespace DB — elle consomme la <strong>copie</strong>
|
||||
synchronisée.</p>
|
||||
<p>Insister sur <code>reflection-auto-*</code> vs miroir explicite selon votre politique (Helm, GitOps…).</p>
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Exemple Reloader : rollout quand le secret miroir change</h1>
|
||||
<p><em>Le Deployment monte le secret copié ; Reloader redémarre les Pods si le Secret (ou ConfigMap) référencé
|
||||
est mis à jour.</em></p>
|
||||
<pre><code class="language-yaml">apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: shop-api
|
||||
namespace: app-shop
|
||||
annotations:
|
||||
reloader.stakater.com/auto: "true"
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: api
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: db-credentials # copie Reflector depuis database/
|
||||
</code></pre>
|
||||
<ul>
|
||||
<li>Rotation du mot de passe dans <code>database/db-credentials</code> → Reflector met à jour
|
||||
<code>app-shop/db-credentials</code> → Reloader déclenche un <strong>nouveau rollout</strong>.</li>
|
||||
<li>Sans Reloader, les Pods gardent l’ancienne valeur en mémoire jusqu’à un redémarrage manuel.</li>
|
||||
</ul>
|
||||
<aside class="notes">
|
||||
<p>Enchaînement oral : (1) changement côté source, (2) miroir à jour, (3) Reloader voit la révision du Secret,
|
||||
(4) rolling update. Mentionner les annotations plus fines (<code>secret.reloader.stakater.com/reload</code>,
|
||||
etc.) si vous voulez limiter à un seul secret.</p>
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Cert-manager, Postgres, Keycloak : la spec devient une entité</h1>
|
||||
<table>
|
||||
@@ -453,6 +532,51 @@ spec:
|
||||
</p>
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Quelle techno pour développer votre opérateur ?</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Piste</th>
|
||||
<th>Outils typiques</th>
|
||||
<th>À retenir</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Go</strong> (référence)</td>
|
||||
<td><strong>Kubebuilder</strong>, <code>controller-runtime</code>, client-go</td>
|
||||
<td>Même patterns que l’upstream (<strong>reconcile</strong>, <strong>owner refs</strong>). Sous pic
|
||||
d’événements : <strong>goroutines</strong> + files + <strong>back-off</strong> → peu de RAM en plus —
|
||||
le Pod opérateur ne part pas en <strong>limite</strong> quand le cluster s’emballe.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Operator SDK</strong></td>
|
||||
<td>CLI Red Hat · Go (souvent comme Kubebuilder) · <strong>Ansible</strong> / <strong>Helm</strong></td>
|
||||
<td>Go = solide ; Ansible / Helm = wrapper playbooks / charts — court si la logique métier épaissit.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Autres langages</strong></td>
|
||||
<td>Python <strong>Kopf</strong>, Rust <strong>kube-rs</strong>, Java (Fabric8, Quarkus)…</td>
|
||||
<td>Si <strong>équipe</strong> ou libs métier ; moins d’exemples tout faits côté core k8s.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p><em><strong>Go + controller-runtime</strong> pour un opérateur maison sérieux ; sinon : celui que l’équipe
|
||||
<strong>livre et maintient</strong> le mieux.</em></p>
|
||||
<p><em>Charge ↑ : un runtime qui gonfle la RAM par événement mange votre quota ; Go + <code>controller-runtime</code>,
|
||||
non.</em></p>
|
||||
<aside class="notes">
|
||||
<p>Carte rapide : Kubebuilder / Operator SDK (Go) = même famille <code>controller-runtime</code>.</p>
|
||||
<p><strong>Go & charge</strong> : goroutines, workqueues, back-off — pics d’événements sans RAM qui explose ;
|
||||
éviter le Pod opérateur en <strong>OOM</strong> / limits alors que le cluster est juste chargé ailleurs.</p>
|
||||
<p>Liens utiles : <a href="https://kubebuilder.io/">kubebuilder.io</a>,
|
||||
<a href="https://sdk.operatorframework.io/">Operator SDK</a>,
|
||||
<a href="https://kopf.readthedocs.io/">Kopf</a> (Python),
|
||||
<a href="https://kube.rs/">kube-rs</a> (Rust).</p>
|
||||
<p>Helm / Ansible : OK pour emballer du déploiement ; logique riche → souvent retour au <strong>Go</strong>.</p>
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Quand un opérateur est légitime</h1>
|
||||
<ul>
|
||||
@@ -477,11 +601,22 @@ spec:
|
||||
selon ton design).</li>
|
||||
<li>Intérêt conf : montrer le <strong>même schéma</strong> que les opérateurs « connus » : <em>observe →
|
||||
compare → corrige</em> sur <strong>ton</strong> domaine métier.</li>
|
||||
<li><strong>Incident ou compromission</strong> de secrets clients : un <strong>reload</strong> Keycloak
|
||||
<strong>émet des événements</strong> côté IdP ; l’opérateur les <strong>interroge régulièrement</strong>
|
||||
(ex. <strong>toutes les minutes</strong>), repère l’écart et <strong>propage</strong> la correction vers
|
||||
l’état attendu dans le cluster.</li>
|
||||
</ul>
|
||||
<p><em>Exemple concret de CRD à la slide suivante.</em></p>
|
||||
<aside class="notes">
|
||||
<p>Je vous montre ça pas pour vendre un produit, mais pour prouver que <strong>la logique</strong> est la même
|
||||
: une API Kubernetes, un état désiré, et du code qui parle à Keycloak à la place d’un humain et d’un
|
||||
runbook.</p>
|
||||
<p><strong>Scénario oral</strong> : en cas d’<strong>incident</strong> ou de <strong>compromission</strong> de
|
||||
credentials clients, tu forces un <strong>reload</strong> Keycloak — ça <strong>déclenche des événements</strong>
|
||||
côté serveur. L’opérateur ne repose pas sur un webhook magique seul : il <strong>scrape / poll</strong> (dans
|
||||
ton design : <strong>toutes les minutes</strong>) pour lire ces signaux ou resynchroniser, puis
|
||||
<strong>réconcilie</strong> : Secrets / CRD à jour, rotation propagée, pas de dérive longue entre l’IdP et le
|
||||
déclaratif cluster.</p>
|
||||
<ul>
|
||||
<li><strong>Lien repo</strong> : <code>https://…</code> (README, une spec d’exemple, capture d’un
|
||||
<code>kubectl get</code>).
|
||||
@@ -493,6 +628,57 @@ spec:
|
||||
style diagramme propre (flat design).</p>
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Exemple — <code>KeycloakClientCredential</code></h1>
|
||||
<p><em>CRD</em> <code>secrets.carbogame.com/v1alpha1</code> — <code>spec.source</code> (realm, client) →
|
||||
<code>spec.target</code> (Secret, rafraîchissement, mapping des clés).</p>
|
||||
<pre><code class="language-yaml">apiVersion: secrets.carbogame.com/v1alpha1
|
||||
kind: KeycloakClientCredential
|
||||
metadata:
|
||||
name: gitea-creds
|
||||
namespace: keycloak
|
||||
spec:
|
||||
source:
|
||||
keycloakServer: prod-keycloak
|
||||
realm: operator
|
||||
clientId: gitea
|
||||
target:
|
||||
secretName: gitea-secret-creds
|
||||
namespace:
|
||||
- gitea
|
||||
refreshPeriod: 10m
|
||||
fields:
|
||||
clientId: key
|
||||
clientSecret: secret
|
||||
</code></pre>
|
||||
<p><em>Extrait type ; en chart Helm, <code>namespace</code> et <code>keycloakServer</code> sont injectés via
|
||||
<code>include "common.namespace" …</code>, <code>{{ .Values.global.env }}-keycloak</code>, etc.</em></p>
|
||||
<aside class="notes">
|
||||
<p>Parcourir le YAML : <code>source</code> = où lire côté Keycloak ; <code>target</code> = où écrire le Secret
|
||||
et sous quelles clés.</p>
|
||||
<p><code>spec.target.refreshPeriod</code> (ex. <code>10m</code>) cadence la resync vers le Secret ; la boucle
|
||||
événements / poll (ex. minute) peut être un autre réglage — à distinguer en live si on pose la question.</p>
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Développer son opérateur : réutiliser les autres</h1>
|
||||
<ul>
|
||||
<li><strong>Ne pas tout recoder</strong> : un opérateur maison peut s’appuyer sur des opérateurs / contrôleurs
|
||||
existants pour ce qu’ils font déjà bien.</li>
|
||||
<li>Ex. <strong>Secrets / ConfigMaps</strong> vers d’autres namespaces : plutôt que dupliquer la logique et une
|
||||
<strong>deuxième config</strong> de ce qui est autorisé, brancher <strong>Reflector</strong> — une politique
|
||||
de réflexion, <strong>un seul RBAC</strong> à raisonner (source vs cibles), moins de surface d’erreur.</li>
|
||||
<li>Votre code se concentre sur <strong>le métier</strong> ; la diffusion transverse reste le problème de
|
||||
l’outil prévu pour ça.</li>
|
||||
</ul>
|
||||
<aside class="notes">
|
||||
<p>Message : <strong>composition</strong> plutôt que réinventer Reflector dans votre binaire. Même idée pour
|
||||
Reloader, External Secrets, etc. : chaque brique a déjà ses garde-fous et sa doc.</p>
|
||||
<p>RBAC : si vous copiez vous-même des secrets entre namespaces, vous maintenez souvent <strong>deux</strong>
|
||||
chemins (votre opérateur + la vérité côté Reflector). Avec Reflector seul pour la copie, une seule matrice
|
||||
« qui peut refléter où ».</p>
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Ce qu’un opérateur bien choisi vous apporte</h1>
|
||||
<ul>
|
||||
@@ -587,11 +773,14 @@ spec:
|
||||
<li>Effets <strong>cachés</strong></li>
|
||||
</ul>
|
||||
<blockquote>
|
||||
<p>Un opérateur mal conçu, c’est un microservice sous LSD.</p>
|
||||
<p>Un opérateur mal conçu, c’est un microservice dont plus personne ne tient les règles du jeu.</p>
|
||||
</blockquote>
|
||||
<aside class="notes">
|
||||
<p>Un mauvais opérateur, c’est de la complexité distribuée avec un debugger qui vous hait.<br />
|
||||
Le piège classique : « formation Kubernetes par la trappe » parce qu’on empile des comportements opaques.
|
||||
<p>Un mauvais opérateur, c’est de la complexité distribuée et le genre de session debug qui nous tient
|
||||
éveillés à
|
||||
3 h du matin — j’en ai croisé de très pénibles, et je ne suis pas toujours innocent de l’empilement.<br />
|
||||
Le piège classique : « formation Kubernetes par la trappe » parce qu’on empile des comportements opaques —
|
||||
souvent avec les meilleures intentions.
|
||||
</p>
|
||||
</aside>
|
||||
</section>
|
||||
@@ -701,7 +890,8 @@ spec:
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.6.1/reveal.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.6.1/plugin/notes/notes.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.6.1/plugin/highlight/highlight.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
||||
<!-- Image shape (flowchart) : Mermaid 11.3+ — https://mermaid.ai/open-source/syntax/flowchart.html#image-shape -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
if (typeof RevealNotes === "undefined" || typeof RevealHighlight === "undefined") {
|
||||
@@ -745,7 +935,7 @@ spec:
|
||||
},
|
||||
flowchart: {
|
||||
useMaxWidth: true,
|
||||
curve: "natural",
|
||||
curve: "basis",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -773,6 +963,31 @@ spec:
|
||||
el.textContent = src;
|
||||
}
|
||||
|
||||
/** Chemins relatifs → absolus pour <image> SVG (forme « image » v11) et foreignObject le cas échéant. */
|
||||
function fixMermaidImageUrls(slide) {
|
||||
if (!slide) return;
|
||||
var base = window.location.href;
|
||||
function toAbs(url) {
|
||||
if (!url || url.indexOf("data:") === 0) return null;
|
||||
if (/^https?:\/\//i.test(url)) return null;
|
||||
try {
|
||||
return new URL(url, base).href;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
slide.querySelectorAll(".mermaid svg image").forEach(function (el) {
|
||||
var u = el.getAttribute("href") || el.getAttribute("xlink:href");
|
||||
var abs = toAbs(u);
|
||||
if (abs) el.setAttribute("href", abs);
|
||||
});
|
||||
slide.querySelectorAll(".mermaid svg foreignObject img").forEach(function (img) {
|
||||
var u = img.getAttribute("src");
|
||||
var abs = toAbs(u);
|
||||
if (abs) img.setAttribute("src", abs);
|
||||
});
|
||||
}
|
||||
|
||||
function runMermaidInSlide(slide) {
|
||||
if (!slide || typeof mermaid.run !== "function") return;
|
||||
slide.querySelectorAll(".mermaid").forEach(resetMermaidIfBroken);
|
||||
@@ -780,7 +995,12 @@ spec:
|
||||
if (!pending.length) return;
|
||||
requestAnimationFrame(function () {
|
||||
requestAnimationFrame(function () {
|
||||
mermaid.run({ nodes: pending, suppressErrors: true }).catch(function () { });
|
||||
var p = mermaid.run({ nodes: pending, suppressErrors: true });
|
||||
var after = function () {
|
||||
fixMermaidImageUrls(slide);
|
||||
};
|
||||
if (p && typeof p.then === "function") p.then(after).catch(after);
|
||||
else after();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user