Apps
Apps entwickeln
Code, Eingabeparameter, Umgebungsvariablen und App Storage einer App
Eine App ist Code, der bei jedem Aufruf ausgeführt wird und HTML ausgibt. Wo Sie den Code anlegen und speichern, beschreibt Apps verwalten.
Code
Der Code (technisch: Executor) verwendet das Standard-Executor-Format von enneo (gleicher Aufbau wie bei Einstellungen und KI-Agenten). Er unterstützt aktuell den Typ Quellcode mit den Sprachen Python 3.11, Node.js 20 und PHP 8.2. Eine neue App beginnt mit einem Python-Gerüst; die Sprache wechseln Sie im Schritt Code über das Feld Sprache.
Die Ausgabe auf stdout wird als HTML an den Browser zurückgegeben.
# Einfache App: Begrüßungsseite
print("<h1>Willkommen</h1>")
print("<p>Dies ist meine erste enneo-App.</p>")// Einfache App: Begrüßungsseite
console.log("<h1>Willkommen</h1>");
console.log("<p>Dies ist meine erste enneo-App.</p>");<?php
echo "<h1>Willkommen</h1>";
echo "<p>Dies ist meine erste enneo-App.</p>";Tip
Apps haben Zugriff auf das gleiche enneo SDK, das auch für regelbasierte KI-Agenten verwendet wird. Damit können Sie Verträge abfragen, Tickets lesen, Einstellungen abrufen und externe APIs aufrufen. Weitere Informationen finden Sie in der SDK-Dokumentation.
App aufrufen
Jede App wird in einem iFrame innerhalb von enneo ausgeführt. Die API-URL akzeptiert sowohl UUID als auch Slug:
/api/mind/app/{slug}/run
/api/mind/app/{appId}/runGET-Parameter können direkt angehängt werden:
/api/mind/app/mein-dashboard/run?param1=wert1¶m2=wert2Eingabeparameter
Der Code erhält über STDIN genau die Parameter, die beim Aufruf mitgeschickt werden: die Parameter
aus der Adresse und den Inhalt einer POST-Anfrage. Ein Aufruf von /apps/mein-dashboard?kunde=123
liefert zum Beispiel:
{
"kunde": "123"
}Der Inhalt einer POST-Anfrage muss gültiges JSON sein, sonst bricht der Aufruf mit einer Fehlermeldung ab. Werte aus Adresse und POST-Anfrage werden zusammengeführt.
Angaben zum Benutzer und zur App stehen nicht in den Parametern, sondern in den Umgebungsvariablen.
Umgebungsvariablen
Folgende Umgebungsvariablen stehen im App-Code zur Verfügung:
| Variable | Beschreibung |
|---|---|
ENNEO_API_URL | Basis-URL der enneo-API |
ENNEO_SESSION_TOKEN | Session-Token für API-Aufrufe |
ENNEO_USER_AUTH_HEADER | Auth-Header des aufrufenden Benutzers |
ENNEO_APP_ID | UUID der App |
ENNEO_APP_SLUG | Slug der App |
ENNEO_APP_VERSION | Version der App (z.B. 1.0.0) |
ENNEO_APP_REVISION | Nummer der aktiven Version aus der Versionshistorie |
ENNEO_USER_ID | ID des angemeldeten Benutzers, der die App geöffnet hat |
ENNEO_CLIENT_ID | ID des Mandanten |
ENNEO_USER_AUTH_TOKEN | Anmeldung (Authorization-Header), mit der die App geöffnet wurde |
SDK | Pfad zur SDK-Datei |
App-Info
Über die Klasse AppInfo können App-Metadaten abgerufen werden:
app_id = sdk.AppInfo.get_id() # UUID der App
app_slug = sdk.AppInfo.get_slug() # Slug der App
app_version = sdk.AppInfo.get_version() # Version (z.B. "1.0.0")<?php
$appId = AppInfo::getId();
$appSlug = AppInfo::getSlug();
$appVersion = AppInfo::getVersion();const { AppInfo } = require(process.env.SDK || 'sdk.js');
const appId = AppInfo.getId(); // UUID der App
const appSlug = AppInfo.getSlug(); // Slug der App
const appVersion = AppInfo.getVersion(); // Version (z.B. "1.0.0")Die Funktionen von AppInfo liefern ihren Wert sofort; hier ist auch in Node.js kein await nötig.
App Storage
Apps können persistente Daten speichern und lesen. Der App Storage ist ein Key-Value-Store, der pro App isoliert ist. Die Werte werden als JSON gespeichert.
Warning
Der App Storage ist gemeinsamer Speicher der App, kein persönlicher Speicher ihrer Benutzer.
Es gibt keine Trennung nach Benutzer und keinen Eigentümer je Schlüssel: Jeder Code der App liest
und überschreibt alle Schlüssel dieser App. Legen Sie dort keine Passwörter, Tokens oder
personenbezogenen Daten ab — Zugangsdaten gehören in die
Secrets und werden von dort über
{{secret.KEY}} referenziert.
Zugriffswege
| Weg | Wer | Wofür gedacht |
|---|---|---|
SDK (AppStorage) aus dem App-Code | Die App selbst, unter dem Konto des Executors | Der vorgesehene Weg |
REST-Endpunkte /api/mind/app/{appId}/data… | Benutzer mit der Berechtigung manageAppData — standardmäßig nur Admin | Einsehen und Korrigieren von Hand |
| Schritt Daten im Bearbeitungsfenster der App | Benutzer mit der Berechtigung manageAppData — standardmäßig nur Admin | Einsehen, ohne Änderungsmöglichkeit |
Das SDK benötigt manageAppData nicht: Es greift unter dem Konto des Executors auf den Storage zu,
nicht unter dem des angemeldeten Benutzers. Ein Agent kann den Storage also über die App nutzen, ihn
aber nicht direkt über die API auslesen oder verändern.
Der Schritt Daten im Bearbeitungsfenster listet alle Einträge der App mit Erstellt, Geändert und Letzter Zugriff auf; ein Klick auf einen Eintrag zeigt seinen Wert. Die Ansicht dient dem Nachsehen und der Fehlersuche — ändern lassen sich die Einträge dort nicht.
SDK-Zugriff
# Wert speichern
sdk.AppStorage.save("counter", 42)
sdk.AppStorage.save("config", {"theme": "dark", "lang": "de"})
# Wert lesen
counter = sdk.AppStorage.get("counter") # → 42
config = sdk.AppStorage.get("config") # → {"theme": "dark", "lang": "de"}
# Wert mit Metadaten lesen
meta = sdk.AppStorage.get_meta("counter")
# → {"key": "counter", "value": 42, "createdAt": "...", "modifiedAt": "...", "lastAccessAt": "..."}
# Alle Schlüssel auflisten
keys = sdk.AppStorage.list_keys()
# → [{"key": "counter", "createdAt": "...", ...}, ...]
# Wert löschen
sdk.AppStorage.delete("counter")<?php
// Wert speichern
AppStorage::save("counter", 42);
AppStorage::save("config", ["theme" => "dark", "lang" => "de"]);
// Wert lesen
$counter = AppStorage::get("counter"); // → 42
$config = AppStorage::get("config"); // → {"theme": "dark", "lang": "de"}
// Wert mit Metadaten lesen
$meta = AppStorage::getMeta("counter");
// Alle Schlüssel auflisten
$keys = AppStorage::listKeys();
// Wert löschen
AppStorage::delete("counter");const { AppStorage } = require(process.env.SDK || 'sdk.js');
(async () => {
// Wert speichern
await AppStorage.save("counter", 42);
await AppStorage.save("config", { theme: "dark", lang: "de" });
// Wert lesen
const counter = await AppStorage.get("counter"); // → 42
const config = await AppStorage.get("config"); // → { theme: "dark", lang: "de" }
// Wert mit Metadaten lesen
const meta = await AppStorage.getMeta("counter");
// Alle Schlüssel auflisten
const keys = await AppStorage.listKeys();
// Wert löschen
await AppStorage.delete("counter");
})();Note
In Node.js arbeiten die AppStorage-Funktionen asynchron. Setzen Sie await vor jeden Aufruf,
sonst erhalten Sie statt des Werts nur ein Promise (einen Platzhalter für das spätere Ergebnis).
Unterstützte Werttypen
| Typ | Beispiel |
|---|---|
| String | "hello world" |
| Zahl | 42, 3.14 |
| Boolean | true, false |
| Null | null |
| Objekt | {"key": "value"} |
| Array | [1, "two", 3.0] |
Schlüsselnamen dürfen nur Buchstaben, Ziffern, Bindestrich (-), Unterstrich (_) und Punkt (.)
enthalten — zum Beispiel meine_notizen, aber nicht meine notizen.
Benutzerspezifische Daten und geschützte Werte
Soll eine App Daten je Benutzer trennen oder Werte nur bestimmten Benutzern zeigen, lässt sich das über den App Storage nicht lösen — er kennt keine Benutzer. Legen Sie die Regel stattdessen in eine benutzerdefinierte Funktion (UDF), die die App aufruft:
- Die App ruft die UDF auf. Der Aufruf läuft unter dem angemeldeten Benutzer.
- Die UDF ermittelt den Aufrufer, indem sie die enneo-API mit dessen Autorisierung abfragt.
- Erst danach greift die UDF — unter dem Konto des Executors — auf den Storage zu und gibt nur zurück, was dieser Benutzer sehen darf.
# In der UDF: Aufrufer über die API ermitteln, nicht aus den Parametern
profil = sdk.ApiEnneo.get('/api/mind/profile') # läuft unter dem aufrufenden Benutzer
benutzer_id = profil['id']
berechtigungen = profil['permissions']
# Erst danach auf den gemeinsamen Storage zugreifen
notizen = sdk.ApiEnneo.get(
f'/api/mind/app/{app_id}/data/notizen',
authorizeAs='serviceWorker',
)['value']
return notizen.get(str(benutzer_id), {})Warning
Zwei Punkte entscheiden darüber, ob diese Prüfung trägt:
- Ermitteln Sie den Aufrufer ausschließlich über den API-Aufruf, nie aus den Parametern. Werte
wie
_metadata.userIdstammen aus dem aufrufenden Code und lassen sich beliebig setzen.ENNEO_USER_IDsteht nur Apps zur Verfügung, nicht UDFs. - Die UDF muss für die betroffenen Benutzer ausführbar sein, sonst erreichen sie sie nicht. Die gesamte Rechteprüfung liegt damit im Code der UDF — behandeln Sie ihn entsprechend.
Beispiele
Dashboard mit Vertragsdaten
import importlib.util, os, json, sys
# SDK laden
file_path = os.getenv('SDK', 'sdk.py')
spec = importlib.util.spec_from_file_location('sdk', file_path)
sdk = importlib.util.module_from_spec(spec)
spec.loader.exec_module(sdk)
# Eingabedaten laden
input_data = sdk.load_input_data()
# Vertragsdaten laden (Beispiel)
contract_id = input_data.get('contractId', '715559')
try:
contract = sdk.ApiEnneo.get_contract(contract_id)
name = f"{contract.get('firstname', '')} {contract.get('lastname', '')}"
status = contract.get('status', 'Unbekannt')
except Exception:
name = "Nicht gefunden"
status = "-"
# HTML ausgeben
print(f"""
<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: Arial, sans-serif; padding: 20px; }}
.card {{ background: #f5f5f5; padding: 16px; border-radius: 8px; margin: 8px 0; }}
h1 {{ color: #333; }}
</style>
</head>
<body>
<h1>Vertragsübersicht</h1>
<div class="card">
<strong>Name:</strong> {name}<br>
<strong>Status:</strong> {status}<br>
<strong>Vertragsnummer:</strong> {contract_id}
</div>
</body>
</html>
""")App mit persistentem Zähler
import importlib.util, os, json, sys, html
file_path = os.getenv('SDK', 'sdk.py')
spec = importlib.util.spec_from_file_location('sdk', file_path)
sdk = importlib.util.module_from_spec(spec)
spec.loader.exec_module(sdk)
input_data = sdk.load_input_data()
# Zähler aus App Storage laden
try:
counter = sdk.AppStorage.get("page_views")
except Exception:
counter = 0
# Zähler erhöhen und speichern
counter += 1
sdk.AppStorage.save("page_views", counter)
print(f"""
<!DOCTYPE html>
<html>
<body style="font-family: Arial, sans-serif; padding: 20px;">
<h1>Besucherzähler</h1>
<p>Diese Seite wurde <strong>{counter}</strong> mal aufgerufen.</p>
<p><em>Der Wert wird im App Storage gespeichert und bleibt über Seitenaufrufe hinweg erhalten.</em></p>
</body>
</html>
""")Statische Info-Seite
print("""
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 20px; max-width: 800px; margin: 0 auto; }
.info { background: #e8f5e9; padding: 16px; border-radius: 8px;
border-left: 4px solid #4caf50; }
.warning { background: #fff3e0; padding: 16px; border-radius: 8px;
border-left: 4px solid #ff9800; }
</style>
</head>
<body>
<h1>Wichtige Informationen</h1>
<div class="info">
<strong>Servicezeiten:</strong> Mo-Fr, 8:00 - 18:00 Uhr
</div>
<br>
<div class="warning">
<strong>Hinweis:</strong> Wartungsarbeiten am Wochenende geplant.
</div>
</body>
</html>
""")API
Alle Endpunkte für Apps, App Storage und App Store finden Sie mit ihren Parametern und Berechtigungen in der API-Referenz.
Das Code-Objekt einer App steht in data.executorUser und folgt dem Standard-Executor-Format:
{
"id": 1,
"code": "print('<h1>Hello</h1>')",
"type": "sourceCode",
"language": "python311",
"packages": null,
"parameters": [],
"parameterDefinitions": []
}