This commit is contained in:
roydfalk 2024-07-12 11:19:40 +02:00
commit 93c0241488
37 changed files with 2009 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/temp/
/build/
**/__pycache__

417
source/data.json Normal file
View file

@ -0,0 +1,417 @@
{
"meta": {
"author": "Christian Fraß",
"date": "2024-07-11",
"title": "Digitale Infrastruktur für DIE LINKE.",
"contact": {
"email_address": "christian.frass@dielinke-glauchau.de"
}
},
"services": {
"theory": [
{
"text": "Wie setzt man einen Web-Dienst mit ausgelagerter Anmeldung auf?",
"image": "services-0"
},
{
"text": "Zunächst wird eine Datenbank eingerichtet",
"image": "services-1"
},
{
"text": "Weiterhin wird ein Web-Server benötigt",
"image": "services-2"
},
{
"text": "Zudem soll ein Authentifizierungs-Dienst zum Einsatz kommen",
"image": "services-3"
},
{
"text": "… dieser benötigt in der Regel eine Datenbank-Anbindung",
"image": "services-4"
},
{
"text": "… und soll über den Web-Server von außen erreichbar sein",
"image": "services-5"
},
{
"text": "Der eigentliche Dienst …",
"image": "services-6"
},
{
"text": "… benötigt ebenfalls eine Datenbank-Anbindung",
"image": "services-7"
},
{
"text": "… soll den Authentifizierungs-Dienst nutzen",
"image": "services-8"
},
{
"text": "… und über den Web-Server angesprochen werden",
"image": "services-9"
}
],
"practice": {
"example": {
"link": "https://linke.sx",
"label": "linke.sx"
},
"technologies": {
"pool": {
"debian": {
"name": "Debian GNU/Linux",
"link": "https://www.debian.org/",
"icon": null,
"desc": "Betriebssystem"
},
"ansible": {
"name": "Ansible",
"link": "https://www.ansible.com/",
"icon": null,
"desc": "IAC-Plattform"
},
"keepassxc": {
"name": "KeePassXC",
"link": "https://keepassxc.org/",
"icon": null,
"desc": "Passwort-Verwaltung"
},
"letsencrypt": {
"name": "Let's Encrypt",
"link": "https://letsencrypt.org/",
"icon": null,
"desc": "TLS-Zertifikats-Erstellung"
},
"inwx": {
"name": "INWX",
"link": "https://www.inwx.de/",
"icon": null,
"desc": "Domänen-Registrar"
}
},
"order": [
"debian",
"ansible",
"keepassxc",
"inwx",
"letsencrypt"
]
},
"basics": {
"pool": {
"postgresql": {
"name": "PostgreSQL",
"link": "https://www.postgresql.org/",
"icon": null,
"desc": "Datenbank-Server"
},
"nginx": {
"name": "nginx",
"link": "https://nginx.org/",
"icon": null,
"desc": "Web-Server und Lastenverteiler"
},
"authelia": {
"name": "Authelia",
"link": "https://www.authelia.com/",
"icon": null,
"desc": "Auth-Server"
}
},
"order": [
"postgresql",
"nginx",
"authelia"
]
},
"concrete_present": {
"pool": {
"synapse": {
"name": "Synapse",
"link": "https://github.com/element-hq/synapse",
"icon": "https://matrix.org/images/matrix-favicon.svg",
"desc": "Chat-Server"
},
"element": {
"name": "Element",
"link": "https://element.io/",
"icon": "https://element.io/images/webclip.png",
"desc": "Chat-Client für Browser"
},
"hedgedoc": {
"name": "Hedgedoc",
"link": "https://hedgedoc.org/",
"icon": "https://informatik-box.de/images/hedgedoc.png",
"desc": "Notizen"
},
"gitlab": {
"name": "GitLab",
"link": "https://about.gitlab.com/",
"icon": "https://about.gitlab.com/nuxt-images/ico/favicon-192x192.png",
"desc": "Code-Management und Aufgabenverwaltung",
"stat": "present"
},
"forgejo": {
"name": "Forgejo",
"link": "https://forgejo.org/",
"icon": "https://forgejo.org/favicon.png",
"desc": "Code-Management und Aufgabenverwaltung"
},
"vikunja": {
"name": "Vikunja",
"link": "https://vikunja.io/",
"icon": "https://vikunja.io/favicon.svg",
"desc": "Aufgabenverwaltung und Kalender"
},
"dokuwiki": {
"name": "Dokuwiki",
"link": "https://www.dokuwiki.org/dokuwiki",
"icon": "https://www.dokuwiki.org/lib/tpl/dokuwiki/images/logo.png",
"desc": "Wissensspeicher"
},
"owncloud": {
"name": "ownCloud",
"link": "https://owncloud.com/de/infinite-scale/",
"icon": "https://owncloud.com/wp-content/themes/ownCloud/dist/assets/img/favicon/apple-touch-icon.png",
"desc": "Datenablage"
},
"murmur": {
"name": "Murmur",
"link": "https://www.mumble.info/",
"icon": "https://www.mumble.info/css/mumble.svg",
"desc": "Audiokonferenzen (bislang ohne Authentifizierung)"
}
},
"order": [
"synapse",
"element",
"murmur",
"hedgedoc",
"dokuwiki",
"owncloud",
"vikunja",
"gitlab",
"forgejo"
]
},
"concrete_planned": {
"pool": {
"bigbluebutton": {
"name": "BigBlueButton",
"link": "https://bigbluebutton.org/",
"icon": null,
"desc": "Videokonferenzen",
"stat": "planned"
},
"mastodon": {
"name": "Mastodon",
"link": "https://joinmastodon.org",
"icon": "https://creazilla-store.fra1.digitaloceanspaces.com/icons/3204993/logo-mastodon-icon-sm.png",
"desc": "Microblogging",
"stat": "planned"
},
"grav": {
"name": "grav",
"link": "https://getgrav.org/",
"icon": "https://getgrav.org/user/themes/planetoid/images/favicon.png",
"desc": "Web-Inhaltsverwaltung",
"stat": "planned"
},
"dovecot": {
"name": "Dovecot",
"link": "https://doc.dovecot.org/",
"icon": "https://w2.influxdata.com/wp-content/uploads/dovecot-logo.png",
"desc": "E-Mail-Empfang",
"stat": "planned"
},
"postfix": {
"name": "Postfix",
"link": "https://www.postfix.org/",
"icon": "https://webhostinggeeks.com/howto/wp-content/uploads/2012/06/Posfix-Mail.jpg",
"desc": "E-Mail-Versand",
"stat": "planned"
}
},
"order": [
"postfix",
"dovecot",
"mastodon",
"grav"
]
}
},
"alternatives": {
"list": [
"MS Exchange",
"Google",
"facebook",
"WhatsApp/Telegram/Signal",
"Dropbox",
"Nextcloud",
"Humhub",
"Zetkin",
"…"
],
"problems": [
"zentralisiert",
"proprietär",
"datenschutzverletzend",
"zwielichtig",
"überladen",
"unterladen",
"kompliziert",
"unflexibel",
"kaputt"
]
}
},
"user_management": {
"schema": [
{
"text": "Die Dienste stehen bereit, aber wer darf sie verwenden?",
"image": "user_management-0"
},
{
"text": "Espe als Nutzerverwaltung …",
"image": "user_management-1"
},
{
"text": "… hat einen typischen technischen Aufbau",
"image": "user_management-2"
},
{
"text": "… und kann den Authentifizierungs-Dienst füttern",
"image": "user_management-3"
},
{
"text": "MGL …",
"image": "user_management-4"
},
{
"text": "… ist über den Browser bedienbar",
"image": "user_management-5"
},
{
"text": "… hat anscheinend auch einen typischen technischen Aufbau",
"image": "user_management-6"
},
{
"text": "… und kann erweitert werden :)",
"image": "user_management-7"
},
{
"text": "… und damit als Quelle für Espe und den Authentifizierungs-Dienst dienen",
"image": "user_management-8"
}
],
"processes": {
"entry": [
{
"text": "Bei Eintritt legt ein Mitgliederbeauftragter einen Datensatz in MGL an",
"image": "user_management-9"
},
{
"text": "Datensatz landet in MGL-BE/MGL-DB",
"image": "user_management-10"
},
{
"text": "Syncer liest Mitglieder aus und gleicht in Richtung Espe ab",
"image": "user_management-11"
},
{
"text": "Espe erzeugt für jeden neuen Nutzerdatensatz ein Passwort und sendet E-Mail",
"image": "user_management-12"
},
{
"text": "… und schickt die neue Nutzerliste an den Authentifizierungs-Dienst",
"image": "user_management-13"
}
]
},
"example": {
"link": "https://zackeneule.linke.sx/",
"label": "zackeneule.linke.sx",
"remark": " (keine MGL-Anbindung)"
}
},
"realization": {
"todos_technical": [
{
"name": "Entwickler gewinnen",
"link": null
},
{
"name": "Penetrationstests durchführen",
"link": "https://de.m.wikipedia.org/wiki/Penetrationstest_(Informatik)"
},
{
"name": "Lastverteilung vorsehen",
"link": "https://de.m.wikipedia.org/wiki/Lastverteilung_(Informatik)"
},
{
"name": "Datensicherung einrichten",
"link": "https://de.m.wikipedia.org/wiki/Datensicherung"
},
{
"name": "Überwachung einrichten",
"link": "https://de.m.wikipedia.org/wiki/Monitoring"
}
],
"todos_social": [
{
"name": "Kräfte bündeln",
"link": null
},
{
"name": "Tester gewinnen",
"link": null
},
{
"name": "Admins gewinnen",
"link": null
},
{
"name": "Überzeugungsarbeit leisten",
"link": null
}
]
},
"resources": [
{
"name": "Ansible-Rollen für Dienste",
"link": "https://gitlab.die-linke.cloud/misc/ansible-base"
},
{
"name": "Infrastruktur-Definition für LAG Netzpolitik Sachsen",
"link": "https://gitlab.die-linke.cloud/misc/infrastructure"
},
{
"name": "Espe | Datenmodell",
"link": "https://gitlab.die-linke.cloud/espe/datamodel"
},
{
"name": "Espe | Backend",
"link": "https://gitlab.die-linke.cloud/espe/backend"
},
{
"name": "Espe | Frontend | Web",
"link": "https://gitlab.die-linke.cloud/espe/frontend-zackeneule"
},
{
"name": "Espe | Frontend | CLI",
"link": "https://gitlab.die-linke.cloud/espe/frontend-mondvogel"
},
{
"name": "Espe | Ansible-Rollen",
"link": "https://gitlab.die-linke.cloud/espe/infrastructure"
},
{
"name": "MGL-CLI-Client",
"link": "https://gitlab.die-linke.cloud/misc/mgl-cli"
},
{
"name": "MGL-Espe-Syncer",
"link": "https://gitlab.die-linke.cloud/misc/mgl-espe-syncer"
}
]
}

619
source/graphs Executable file
View file

@ -0,0 +1,619 @@
#!/usr/bin/env python3
import os as _os
import sys as _sys
import json as _json
import argparse as _argparse
from lib import *
def hue_regular():
return 0.4
def hue_highlight():
return 0.0
def style(level, step):
return ("invis" if (step < level) else "filled")
def style_new(predicate):
return (
"filled"
if predicate() else
"invis"
)
def highlight(predicate):
return (
{
"fillcolor": ("%.4f+0.75+0.5" % (hue_highlight(), )),
"color": ("%.4f+0.75+0.5" % (hue_highlight(), )),
}
if predicate() else
{
}
)
def render(input_, data, format_, path_output):
path_temp = "/tmp/graph.gv"
_os.makedirs(_os.path.dirname(path_temp), exist_ok = True)
file_write(
path_temp,
string_coin(
input_,
data
)
)
_os.makedirs(_os.path.dirname(path_output), exist_ok = True)
_os.system(
string_coin(
"cat {{path_source}} | dot -T {{format}} > {{path_output}}",
{
"path_source": path_temp,
"path_output": path_output,
"format": format_,
}
)
)
def attributes_subgraph():
return {
"fontname": "monospace",
"fontcolor": ("%.4f+0+1" % (hue_regular(), )),
"color": ("%.4f+0.25+0.25" % (hue_regular(), )),
"fillcolor": ("%.4f+0.25+0.25" % (hue_regular(), )),
}
def dot_macro_services(conf):
return dot_graph(
{
"name": "services",
"settings": {
"graph": {
"layout": "dot",
"rankdir": "BT",
"bgcolor": "0.4+0+0.125",
},
"node": {
"fontname": "monospace",
"shape": "hexagon",
"style": "filled",
"fillcolor": "0.4+0.75+0.5",
"color": "0.4+0.75+0.5",
"fontcolor": "0.4+0+1",
},
"edge": {
"fontname": "monospace",
"color": "0.4+0+0.75",
}
},
"nodes": [
{
"name": "node_db",
"attributes": {
"label": "DB-Server",
"style": style_new(lambda: (conf["step"] >= 1)),
},
},
{
"name": "node_web",
"attributes": {
"label": "Web-Server",
"style": style_new(lambda: (conf["step"] >= 2)),
},
},
{
"name": "node_auth",
"attributes": {
"label": "Auth-Server",
"style": style_new(lambda: (conf["step"] >= 3)),
},
},
{
"name": "node_db_for_auth",
"attributes": {
"label": "DB-Modul\nfür\nAuth-Server",
"style": style_new(lambda: (conf["step"] >= 4)),
},
},
{
"name": "node_auth_and_web",
"attributes": {
"label": "Web-Modul\nfür\nAuth-Server",
"style": style_new(lambda: (conf["step"] >= 5)),
},
},
{
"name": "node_service",
"attributes": {
"label": "Dienst",
"style": style_new(lambda: (conf["step"] >= 6)),
},
},
{
"name": "node_db_for_service",
"attributes": {
"label": "DB-Modul\nfür\nDienst",
"style": style_new(lambda: (conf["step"] >= 7)),
},
},
{
"name": "node_auth_for_service",
"attributes": {
"label": "Auth-Modul\nfür\nDienst",
"style": style_new(lambda: (conf["step"] >= 8)),
},
},
{
"name": "node_service_and_web",
"attributes": {
"label": "Web-Modul\nfür\nDienst",
"style": style_new(lambda: (conf["step"] >= 9)),
},
},
],
"edges": [
{
"name_from": "node_db",
"name_to": "node_db_for_auth",
"attributes": {
"style": style_new(lambda: (conf["step"] >= 4)),
},
},
{
"name_from": "node_db_for_auth",
"name_to": "node_auth",
"attributes": {
"style": style_new(lambda: (conf["step"] >= 4)),
},
},
{
"name_from": "node_auth",
"name_to": "node_auth_and_web",
"attributes": {
"style": style_new(lambda: (conf["step"] >= 5)),
},
},
{
"name_from": "node_web",
"name_to": "node_auth_and_web",
"attributes": {
"style": style_new(lambda: (conf["step"] >= 5)),
},
},
{
"name_from": "node_db",
"name_to": "node_db_for_service",
"attributes": {
"style": style_new(lambda: (conf["step"] >= 7)),
},
},
{
"name_from": "node_db_for_service",
"name_to": "node_service",
"attributes": {
"style": style_new(lambda: (conf["step"] >= 7)),
},
},
{
"name_from": "node_auth",
"name_to": "node_auth_for_service",
"attributes": {
"style": style_new(lambda: (conf["step"] >= 8)),
},
},
{
"name_from": "node_auth_for_service",
"name_to": "node_service",
"attributes": {
"style": style_new(lambda: (conf["step"] >= 8)),
},
},
{
"name_from": "node_service",
"name_to": "node_service_and_web",
"attributes": {
"style": style_new(lambda: (conf["step"] >= 9)),
},
},
{
"name_from": "node_web",
"name_to": "node_service_and_web",
"attributes": {
"style": style_new(lambda: (conf["step"] >= 9)),
},
},
],
"subgraphs": [
],
}
)
def dot_macro_user_management(conf):
return dot_graph(
{
"name": "user_management",
"settings": {
"graph": {
"layout": "dot",
"rankdir": "BT",
"bgcolor": "0.4+0+0.125",
},
"node": {
"fontname": "monospace",
"shape": "hexagon",
"style": "filled",
"fillcolor": "0.4+0.75+0.5",
"color": "0.4+0.75+0.5",
"fontcolor": "0.4+0+1",
},
"edge": {
"fontname": "monospace",
"color": "0.4+0+0.75",
}
},
"nodes": [
{
"name": "node_9",
"attributes": (
{
"label": "Syncer",
"style": style_new(lambda: (conf["step"] >= 8)),
}
|
highlight(lambda: (conf["step"] == 11))
)
},
],
"edges": [
{
"name_from": "node_6",
"name_to": "node_11",
"attributes": {
"style": style_new(lambda: (conf["step"] >= 3)),
}
},
{
"name_from": "node_4",
"name_to": "node_9",
"attributes": {
"dir": "forward",
"style": style_new(lambda: (conf["step"] >= 8)),
}
},
{
"name_from": "node_8",
"name_to": "node_9",
"attributes": {
"dir": "both",
"style": style_new(lambda: (conf["step"] >= 8)),
}
},
],
"subgraphs": [
{
"name": "3",
"attributes": (
attributes_subgraph()
|
{
"label": "MGL",
"style": style_new(lambda: (conf["step"] >= 4)),
}
),
"nodes": [
{
"name": "node_1",
"attributes": (
{
"label": "MGL-DB",
"style": style_new(lambda: (conf["step"] >= 6)),
}
|
highlight(lambda: (conf["step"] == 10))
)
},
{
"name": "node_2",
"attributes": (
{
"label": "MGL-BE",
"style": style_new(lambda: (conf["step"] >= 6)),
}
|
highlight(lambda: (conf["step"] == 10))
)
},
{
"name": "node_3",
"attributes": (
{
"label": "MGL-FE-Web",
"style": style_new(lambda: (conf["step"] >= 5)),
}
|
highlight(lambda: (conf["step"] == 9))
)
},
{
"name": "node_4",
"attributes": (
{
"label": "MGL-FE-CLI",
"style": style_new(lambda: (conf["step"] >= 7)),
}
|
highlight(lambda: (conf["step"] == 11))
)
},
],
"edges": [
{
"name_from": "node_1",
"name_to": "node_2",
"attributes": {
"dir": "both",
"style": style_new(lambda: (conf["step"] >= 6)),
}
},
{
"name_from": "node_2",
"name_to": "node_3",
"attributes": {
"dir": "both",
"style": style_new(lambda: (conf["step"] >= 6)),
}
},
{
"name_from": "node_2",
"name_to": "node_4",
"attributes": {
"dir": "forward",
"style": style_new(lambda: (conf["step"] >= 7)),
}
},
],
},
{
"name": "2",
"attributes": (
attributes_subgraph()
|
{
"label": "Espe",
"style": style_new(lambda: (conf["step"] >= 1)),
}
),
"nodes": [
{
"name": "node_5",
"attributes": (
{
"label": "Espe-DB",
"style": style_new(lambda: (conf["step"] >= 2)),
}
|
highlight(lambda: (conf["step"] == 12))
)
},
{
"name": "node_6",
"attributes": (
{
"label": "Espe-BE",
"style": style_new(lambda: (conf["step"] >= 2)),
}
|
highlight(lambda: (conf["step"] == 12))
)
},
{
"name": "node_8",
"attributes": (
{
"label": "Espe-FE-CLI",
"style": style_new(lambda: (conf["step"] >= 2)),
}
|
highlight(lambda: (conf["step"] == 11))
)
},
{
"name": "node_7",
"attributes": (
{
"label": "Espe-FE-Web",
"style": style_new(lambda: (conf["step"] >= 2)),
}
)
},
],
"edges": [
{
"name_from": "node_5",
"name_to": "node_6",
"attributes": {
"dir": "both",
"style": style_new(lambda: (conf["step"] >= 2)),
}
},
{
"name_from": "node_6",
"name_to": "node_7",
"attributes": {
"dir": "both",
"style": style_new(lambda: (conf["step"] >= 2)),
}
},
{
"name_from": "node_6",
"name_to": "node_8",
"attributes": {
"dir": "both",
"style": style_new(lambda: (conf["step"] >= 2)),
}
},
],
},
{
"name": "1",
"attributes": (
attributes_subgraph()
|
{
"label": "Dienste",
"style": style_new(lambda: (conf["step"] >= 0)),
}
),
"nodes": [
{
"name": "node_10",
"attributes": {
"label": "…",
"style": style_new(lambda: (conf["step"] >= 0)),
}
},
{
"name": "node_11",
"attributes": (
{
"label": "Auth-Server",
"style": style_new(lambda: (conf["step"] >= 0)),
}
|
highlight(lambda: (conf["step"] == 13))
)
},
{
"name": "node_12",
"attributes": {
"label": "…",
"style": style_new(lambda: (conf["step"] >= 0)),
}
},
],
"edges": [
{
"name_from": "node_10",
"name_to": "node_11",
"attributes": {
"dir": "both",
"style": style_new(lambda: (conf["step"] >= 0)),
}
},
{
"name_from": "node_11",
"name_to": "node_12",
"attributes": {
"dir": "both",
"style": style_new(lambda: (conf["step"] >= 0)),
}
},
],
},
]
}
)
def main():
## consts
dir_source = "source"
## args
argument_parser = _argparse.ArgumentParser(
)
argument_parser.add_argument(
"-f",
"--format",
type = str,
choices = ["svg","png"],
dest = "format",
metavar = "<format>",
default = "svg"
)
argument_parser.add_argument(
"-n",
"--no-extension",
action = "store_true",
dest = "no_extension",
default = False,
)
argument_parser.add_argument(
"-o",
"--output-directory",
type = str,
dest = "output_directory",
metavar = "<output-directory>",
default = "temp"
)
args = argument_parser.parse_args()
## exec
### excc:services
if True:
size = 10
for step in range(size):
render(
dot_macro_services(
{
"step": step,
}
),
{},
args.format,
string_coin(
"{{directory}}/{{name}}-{{step}}{{extension}}",
{
"name": "services",
"directory": args.output_directory,
"step": ("%u" % step),
"extension": (
""
if args.no_extension else
("." + args.format)
),
}
)
)
### exec:syncing
if True:
size = 14
for step in range(size):
render(
dot_macro_user_management(
{
"step": step,
}
),
{},
args.format,
string_coin(
"{{directory}}/{{name}}-{{step}}{{extension}}",
{
"name": "user_management",
"directory": args.output_directory,
"step": ("%u" % step),
"extension": (
""
if args.no_extension else
("." + args.format)
),
}
)
)
main()

199
source/lib.py Normal file
View file

@ -0,0 +1,199 @@
import os as _os
def convey(x, fs):
y = x
for f in fs:
y = f(y)
return y
def string_coin(template, arguments, options = None):
options = (
{
"open": "{{",
"close": "}}",
}
|
(options or {})
)
result = template
for (key, value, ) in arguments.items():
if (value is None):
pass
else:
result = result.replace(
(
"%s%s%s"
% (
options["open"],
key,
options["close"],
)
),
value
)
return result
def file_read(path):
handle = open(path, "r")
content = handle.read()
handle.close()
return content
def file_write(path, content):
_os.makedirs(_os.path.dirname(path), exist_ok = True)
handle = open(path, "w" if _os.path.exists(path) else "a")
handle.write(content)
handle.close()
return None
def dot_entry(head, attributes, options = None):
options = (
{
"indentation": 0,
}
|
(options or {})
)
return string_coin(
"{{indentation}}{{head}} [{{attributes}}];\n",
{
"indentation": ("\t" * options["indentation"]),
"head": head,
"attributes": convey(
attributes,
[
lambda x: x.items(),
lambda x: map(
lambda pair: string_coin(
"{{key}}=\"{{value}}\"",
{
"key": pair[0],
"value": pair[1]
}
),
x
),
", ".join,
]
),
}
)
def dot_node(node, options = None):
options = (
{
"indentation": 0,
}
|
(options or {})
)
return dot_entry(
string_coin("{{name}}", {"name": node["name"]}),
node["attributes"],
options
)
def dot_edge(edge, options = None):
options = (
{
"indentation": 0,
}
|
(options or {})
)
return dot_entry(
string_coin("{{name_from}} -> {{name_to}}", {"name_from": edge["name_from"], "name_to": edge["name_to"]}),
edge["attributes"],
options
)
def dot_subgraph(subgraph, options = None):
options = (
{
"indentation": 0,
}
|
(options or {})
)
return string_coin(
(
"{{indentation}}subgraph cluster_{{name}}\n"
+
"{{indentation}}{\n"
+
"{{attributes}}\n"
+
"{{nodes}}\n"
+
"{{edges}}\n"
+
"{{indentation}}}\n"
),
{
"indentation": ("\t" * options["indentation"]),
"name": subgraph["name"],
"attributes": convey(
subgraph["attributes"],
[
lambda x: x.items(),
lambda x: map(
lambda pair: string_coin(
"{{indentation}}{{key}}=\"{{value}}\";\n",
{
"indentation": ("\t" * (options["indentation"] + 1)),
"key": pair[0],
"value": pair[1]
}
),
x
),
"".join,
]
),
"nodes": "".join(map(lambda node: dot_node(node, {"indentation": (options["indentation"] + 1)}), subgraph["nodes"])),
"edges": "".join(map(lambda edge: dot_edge(edge, {"indentation": (options["indentation"] + 1)}), subgraph["edges"])),
}
)
def dot_graph(graph):
return string_coin(
(
"digraph {{name}}\n"
+
"{\n"
+
"{{settings_graph}}\n"
+
"{{settings_node}}\n"
+
"{{settings_edge}}\n"
+
"{{subgraphs}}\n"
+
"{{nodes}}\n"
+
"{{edges}}\n"
+
"}\n"
),
{
"name": graph["name"],
"settings_graph": dot_entry("graph", graph["settings"]["graph"], {"indentation": 1}),
"settings_node": dot_entry("node", graph["settings"]["node"], {"indentation": 1}),
"settings_edge": dot_entry("edge", graph["settings"]["edge"], {"indentation": 1}),
"nodes": "".join(map(lambda node: dot_node(node, {"indentation": 1}), graph["nodes"])),
"edges": "".join(map(lambda edge: dot_edge(edge, {"indentation": 1}), graph["edges"])),
"subgraphs": "".join(map(lambda subgraph: dot_subgraph(subgraph, {"indentation": 1}), graph["subgraphs"])),
}
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
source/media/icons/grav.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
source/media/icons/inwx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

9
source/tex/app.tex.tpl Normal file
View file

@ -0,0 +1,9 @@
\begin{item}
\href{<<link>>}
{
\includegraphics[width=0.025\textwidth]{media/icons/<<id>>}
\
\textbf{<<name>>}
} — <<desc>>
\end{item}

190
source/tex/master.tex.tpl Normal file
View file

@ -0,0 +1,190 @@
\documentclass[aspectratio=169]{beamer}
\usepackage{graphicx}
\usepackage{svg}
% \usepackage[ngerman]{babel}
\date{<<meta-date>>}
\author{<<meta-author>>}
\title{<<meta-title>>}
% \setmainlanguage{german}
\usetheme{Luebeck}
% \usetheme{m}
\usecolortheme{spruce}
\begin{document}
\maketitle
\begin{frame}
\tableofcontents
\end{frame}
\begin{section}{Dienste}
\begin{frame}
\sectionpage
\end{frame}
\begin{subsection}{Theorie}
<<service-theory-frames>>
\end{subsection}
\begin{subsection}{Praxis}
\begin{frame}
\frametitle{Beispiel}
Siehe \href{<<service-practice-example-link>>}{\texttt{<<service-practice-example-label>>}}
\end{frame}
\begin{frame}
\frametitle{Grundlegende Dientse}
\begin{itemize}
<<service-practice-basics-items>>
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{Konkrete Dienste}
\begin{itemize}
<<service-practice-existing-items>>
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{Geplante Dienste}
\begin{itemize}
<<service-practice-future-items>>
\end{itemize}
\end{frame}
\begin{frame}
\frametitle{Eingesetzte Technik}
\begin{itemize}
<<service-practice-technologies-items>>
\end{itemize}
\end{frame}
\end{subsection}
\begin{subsection}{Alternativen}
\begin{frame}
\frametitle{Nimm doch einfach }
\begin{columns}
\pause
\begin{column}{0.5\textwidth}
\begin{itemize}
<<services-alternatives-list-items>>
\end{itemize}
\end{column}
\pause
\begin{column}{0.5\textwidth}
\begin{itemize}
<<services-alternatives-problems-items>>
\end{itemize}
\end{column}
\end{columns}
\end{frame}
\begin{frame}<presentation:0>
\frametitle{Aber es gibt doch noch }
\pause
Du kennst ein System, was alles Gewünschte halbwegs sauber, sicher und bezahlbar abbildet? \\
\pause
\vspace{10mm}
Na geil! Dann schick mir gerne eine Nachricht: \texttt{christian.frass@dielinke-glauchau.de} \\
\pause
\vspace{10mm}
… aber rechne besser damit, dass ich es rösten oder ganz in der Luft zerreißen werde :)
\end{frame}
\end{subsection}
\end{section}
\begin{section}{Nutzerverwaltung}
\begin{frame}
\sectionpage
\end{frame}
\begin{subsection}{Aufbau}
<<user_management-schema-frames>>
\end{subsection}
\begin{subsection}{Ablauf}
<<user_management-process-entry-frames>>
\end{subsection}
\begin{frame}
\frametitle{Vorführung}
Siehe \href{<<user_management-example-link>>}{\texttt{<<user_management-example-label>>}} <<user_management-example-remark>>
\end{frame}
\end{section}
\begin{section}{Umsetzung}
\begin{frame}
\frametitle{Zu tun}
\begin{columns}
\pause
\begin{column}{0.5\textwidth}
\textbf{technisch}
\begin{itemize}
<<todos-technical-items>>
\end{itemize}
\end{column}
\pause
\begin{column}{0.5\textwidth}
\textbf{menschlich}
\begin{itemize}
<<todos-social-items>>
\end{itemize}
\end{column}
\end{columns}
\end{frame}
<<resources-frame>>
\end{section}
\begin{section}{Schluss}
\begin{frame}
\frametitle{Lust bekommen?}
\begin{itemize}
\item{E-Mail an \href{mailto:<<meta-contact-email_address>>}{\texttt{<<meta-contact-email_address>>}}}
\pause
\item{Teilnahme in \texttt{\$\{gemeinsamer-kommunikations-kanal\}}?}
\end{itemize}
\end{frame}
\begin{frame}
\begin{center}
's'war's :)
\end{center}
\end{frame}
\end{section}
\end{document}

View file

@ -0,0 +1,6 @@
\begin{item}
\href{<<link>>}
{
<<name>>
}
\end{item}

View file

@ -0,0 +1,8 @@
\begin{frame}
\frametitle{Ressourcen}
\begin{itemize}
<<resource-items>>
\end{itemize}
\end{frame}

View file

@ -0,0 +1,16 @@
\begin{frame}
\frametitle{Theorie}
\begin{columns}
% \begin{column}{0.618\textwidth}
\begin{column}{0.5\textwidth}
\begin{center}
\includesvg[inkscapelatex=false,height=0.8\textheight]{graphs/<<image>>}
\end{center}
\end{column}
% \begin{column}{0.382\textwidth}
\begin{column}{0.5\textwidth}
<<text>>
\end{column}
\end{columns}
\end{frame}

4
source/tex/todo.tex.tpl Normal file
View file

@ -0,0 +1,4 @@
\begin{item}
\href{<<link>>}{<<name>>}
\end{item}

View file

@ -0,0 +1,8 @@
\begin{frame}
\frametitle{<<title>>}
\begin{figure}
\includesvg[inkscapelatex=false,width=1.0\textwidth]{graphs/<<image>>}
\end{figure}
<<text>>
\end{frame}

56
tools/build Executable file
View file

@ -0,0 +1,56 @@
#!/usr/bin/env python3
import os as _os
import argparse as _argparse
def main():
## args
argument_parser = _argparse.ArgumentParser(
)
argument_parser.add_argument(
"-c",
"--clear",
action = "store_true",
dest = "clear",
default = False
)
argument_parser.add_argument(
"-r",
"--include-resources",
action = "store_true",
dest = "include_resources",
default = False,
)
argument_parser.add_argument(
"-v",
"--verbose",
action = "store_true",
dest = "verbose",
default = False,
)
argument_parser.add_argument(
"-o",
"--output-directory",
type = str,
dest = "output_directory",
metavar = "<output-directory>",
default = "build"
)
args = argument_parser.parse_args()
## exec
make_args = []
make_args.append("dir_build=%s" % args.output_directory)
if args.include_resources:
make_args.append("coin_args='-r'")
if not args.verbose:
make_args.append("latex_args='-interaction batchmode'")
if args.clear:
make_args.append("clear")
make_args.append("all")
_os.system("make -f tools/makefile %s" % " ".join(make_args))
main()

206
tools/coin Executable file
View file

@ -0,0 +1,206 @@
#!/usr/bin/env python3
import sys as _sys
import json as _json
import argparse as _argparse
from lib import *
_template_cache = {}
def coin(template, arguments):
return string_coin(
template,
arguments,
{"open": "<<", "close": ">>"}
)
def render(template_name, arguments):
global _template_cache
if (not (template_name in _template_cache)):
template_content = file_read("source/tex/%s.tex.tpl" % template_name)
_template_cache[template_name] = template_content
else:
template_content = _template_cache[template_name]
return coin(
template_content,
arguments
)
def macro_applist(node):
return convey(
node["order"],
[
lambda x: map(
lambda y: render(
"app",
{
"id": y,
"name": node["pool"][y]["name"],
"link": node["pool"][y]["link"],
"icon": node["pool"][y]["icon"],
"desc": node["pool"][y]["desc"],
}
),
x
),
"".join,
]
)
def macro_todolist(node):
return convey(
node,
[
lambda x: map(
lambda entry: render(
"todo",
{
"name": entry["name"],
"link": entry["link"],
}
),
x
),
"".join,
]
)
def main():
## args
argument_parser = _argparse.ArgumentParser()
argument_parser.add_argument(
"-d",
"--data-path",
type = str,
dest = "data_path",
metavar = "<data-path>",
default = "source/data.json",
)
argument_parser.add_argument(
"-r",
"--include-resources",
action = "store_true",
dest = "include_resources",
default = False,
)
args = argument_parser.parse_args()
## exec
data = _json.loads(file_read(args.data_path))
_sys.stdout.write(
render(
"master",
{
"meta-date": data["meta"]["date"],
"meta-author": data["meta"]["author"],
"meta-title": data["meta"]["title"],
"meta-contact-email_address": data["meta"]["contact"]["email_address"],
"service-theory-frames": convey(
data["services"]["theory"],
[
lambda x: map(
lambda y: render(
"service-theory-frame",
y
),
x
),
"".join,
]
),
"service-practice-example-link": data["services"]["practice"]["example"]["link"],
"service-practice-example-label": data["services"]["practice"]["example"]["label"],
"service-practice-technologies-items": macro_applist(data["services"]["practice"]["technologies"]),
"service-practice-basics-items": macro_applist(data["services"]["practice"]["basics"]),
"service-practice-existing-items": macro_applist(data["services"]["practice"]["concrete_present"]),
"service-practice-future-items": macro_applist(data["services"]["practice"]["concrete_planned"]),
"services-alternatives-list-items": convey(
data["services"]["alternatives"]["list"],
[
lambda x: map(
lambda entry: coin("\\item{<<entry>>}\n", {"entry": entry}),
x
),
"".join,
]
),
"services-alternatives-problems-items": convey(
data["services"]["alternatives"]["problems"],
[
lambda x: map(
lambda entry: coin("\\item{<<entry>>}\n", {"entry": entry}),
x
),
"".join,
]
),
"user_management-schema-frames": convey(
data["user_management"]["schema"],
[
lambda x: map(
lambda y: render(
"user_management-frame",
({"title": "Aufbau"} | y)
),
x
),
"".join,
]
),
"user_management-process-entry-frames": convey(
data["user_management"]["processes"]["entry"],
[
lambda x: map(
lambda y: render(
"user_management-frame",
({"title": "Ablauf"} | y)
),
x
),
"".join,
]
),
"user_management-example-link": data["user_management"]["example"]["link"],
"user_management-example-label": data["user_management"]["example"]["label"],
"user_management-example-remark": data["user_management"]["example"]["remark"],
"resources-frame": (
render(
"resources-frame",
{
"resource-items": convey(
data["resources"],
[
lambda x: map(
lambda resource: render(
"resource-item",
{
"name": resource["name"],
"link": resource["link"],
}
),
x
),
"".join,
]
),
}
)
if args.include_resources else
""
),
"todos-technical-items": macro_todolist(data["realization"]["todos_technical"]),
"todos-social-items": macro_todolist(data["realization"]["todos_social"]),
}
)
)
main()

8
tools/fetch Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env python3
import json as _json
data = _json.loads(open("source/data.json").read())
for pair in data["service"]["practice"]["pool"].items():
print("# %s\ncurl -L %s -o source/media/icons/%s\n" % (pair[0], pair[1]["icon"], pair[0]))

199
tools/lib.py Normal file
View file

@ -0,0 +1,199 @@
import os as _os
def convey(x, fs):
y = x
for f in fs:
y = f(y)
return y
def string_coin(template, arguments, options = None):
options = (
{
"open": "{{",
"close": "}}",
}
|
(options or {})
)
result = template
for (key, value, ) in arguments.items():
if (value is None):
pass
else:
result = result.replace(
(
"%s%s%s"
% (
options["open"],
key,
options["close"],
)
),
value
)
return result
def file_read(path):
handle = open(path, "r")
content = handle.read()
handle.close()
return content
def file_write(path, content):
_os.makedirs(_os.path.dirname(path), exist_ok = True)
handle = open(path, "w" if _os.path.exists(path) else "a")
handle.write(content)
handle.close()
return None
def dot_entry(head, attributes, options = None):
options = (
{
"indentation": 0,
}
|
(options or {})
)
return string_coin(
"{{indentation}}{{head}} [{{attributes}}];\n",
{
"indentation": ("\t" * options["indentation"]),
"head": head,
"attributes": convey(
attributes,
[
lambda x: x.items(),
lambda x: map(
lambda pair: string_coin(
"{{key}}=\"{{value}}\"",
{
"key": pair[0],
"value": pair[1]
}
),
x
),
", ".join,
]
),
}
)
def dot_node(node, options = None):
options = (
{
"indentation": 0,
}
|
(options or {})
)
return dot_entry(
string_coin("{{name}}", {"name": node["name"]}),
node["attributes"],
options
)
def dot_edge(edge, options = None):
options = (
{
"indentation": 0,
}
|
(options or {})
)
return dot_entry(
string_coin("{{name_from}} -> {{name_to}}", {"name_from": edge["name_from"], "name_to": edge["name_to"]}),
edge["attributes"],
options
)
def dot_subgraph(subgraph, options = None):
options = (
{
"indentation": 0,
}
|
(options or {})
)
return string_coin(
(
"{{indentation}}subgraph cluster_{{name}}\n"
+
"{{indentation}}{\n"
+
"{{attributes}}\n"
+
"{{nodes}}\n"
+
"{{edges}}\n"
+
"{{indentation}}}\n"
),
{
"indentation": ("\t" * options["indentation"]),
"name": subgraph["name"],
"attributes": convey(
subgraph["attributes"],
[
lambda x: x.items(),
lambda x: map(
lambda pair: string_coin(
"{{indentation}}{{key}}=\"{{value}}\";\n",
{
"indentation": ("\t" * (options["indentation"] + 1)),
"key": pair[0],
"value": pair[1]
}
),
x
),
"".join,
]
),
"nodes": "".join(map(lambda node: dot_node(node, {"indentation": (options["indentation"] + 1)}), subgraph["nodes"])),
"edges": "".join(map(lambda edge: dot_edge(edge, {"indentation": (options["indentation"] + 1)}), subgraph["edges"])),
}
)
def dot_graph(graph):
return string_coin(
(
"digraph {{name}}\n"
+
"{\n"
+
"{{settings_graph}}\n"
+
"{{settings_node}}\n"
+
"{{settings_edge}}\n"
+
"{{subgraphs}}\n"
+
"{{nodes}}\n"
+
"{{edges}}\n"
+
"}\n"
),
{
"name": graph["name"],
"settings_graph": dot_entry("graph", graph["settings"]["graph"], {"indentation": 1}),
"settings_node": dot_entry("node", graph["settings"]["node"], {"indentation": 1}),
"settings_edge": dot_entry("edge", graph["settings"]["edge"], {"indentation": 1}),
"nodes": "".join(map(lambda node: dot_node(node, {"indentation": 1}), graph["nodes"])),
"edges": "".join(map(lambda edge: dot_edge(edge, {"indentation": 1}), graph["edges"])),
"subgraphs": "".join(map(lambda subgraph: dot_subgraph(subgraph, {"indentation": 1}), graph["subgraphs"])),
}
)

61
tools/makefile Normal file
View file

@ -0,0 +1,61 @@
## consts/vars
dir_source := source
dir_temp := temp
dir_build := build
coin_args :=
latex_args :=
## commands
cmd_log := echo "--"
cmd_rm := rm --recursive --force
cmd_mkdir := mkdir --parents
cmd_cp := cp --recursive --update --verbose
cmd_latex := xelatex -shell-escape ${latex_args}
## rules
.PHONY: _default
_default: all
.PHONY: all
all: icons graphs ${dir_build}/infra.pdf
.PHONY: clear
clear:
@ ${cmd_log} "clearing …"
@ ${cmd_rm} ${dir_temp} ${dir_build}
.PHONY: graphs
graphs: ${dir_source}/graphs
@ ${cmd_log} "graphs …"
@ ${cmd_mkdir} ${dir_temp}/graphs
@ ${dir_source}/graphs --output-directory=${dir_temp}/graphs --format=svg # --no-extension
.PHONY: icons
icons:
@ ${cmd_log} "icons …"
@ ${cmd_mkdir} ${dir_temp}/media/icons
@ ${cmd_cp} ${dir_source}/media/icons/* ${dir_temp}/media/icons/
${dir_temp}/infra.tex: \
$(wildcard ${dir_source}/tex/*) \
${dir_source}/data.json \
$(wildcard ${dir_temp}/graphs/*) \
tools/coin
@ ${cmd_log} "coining …"
@ tools/coin --data-path=${dir_source}/data.json ${coin_args} > ${dir_temp}/infra.tex
${dir_temp}/infra.pdf: ${dir_temp}/infra.tex
@ ${cmd_log} "compiling …"
@ cd ${dir_temp} && ${cmd_latex} infra.tex && cd - > /dev/null
@ cd ${dir_temp} && ${cmd_latex} infra.tex && cd - > /dev/null
${dir_build}/infra.pdf: ${dir_temp}/infra.pdf
@ ${cmd_log} "placing …"
@ ${cmd_mkdir} ${dir_build}
@ ${cmd_cp} ${dir_temp}/infra.pdf ${dir_build}/infra.pdf