Erstes Frontend Layout erstellt.
This commit is contained in:
@@ -4,4 +4,7 @@ logic/scripts/.idea
|
||||
gaga*
|
||||
*backup
|
||||
.idea/
|
||||
.jbeval
|
||||
*.log*
|
||||
*.bak
|
||||
*.backup
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Projekt ToDo-Liste (ArchiveCostWebapp)
|
||||
|
||||
## 📊 Streamlit Frontend
|
||||
- [x] Erstes Tabellen-Layout mit Pandas erstellen
|
||||
- [x] Navigation über die Sidebar implementieren
|
||||
- [ ] Uebersichtsteil erstellen
|
||||
- [ ] Bereichsteil erstellen
|
||||
- [ ] Teil ueber nicht zuweisbare Daten
|
||||
- [ ] Kosten auf allen Seiten einblenden
|
||||
- [ ] Uebersicht ueber single, double Kopien
|
||||
- [ ] Benutzerseite, wo er selber Filter erstellen kann, z.B. Anzahl Datasets groesser als ...
|
||||
- [ ] Diagramm für die Department-Verteilung einbauen (Plotly)
|
||||
- [ ] Export-Button für gefilterte Excel-Dateien hinzufügen
|
||||
|
||||
## ⚙️ Node-RED & AD-Skript
|
||||
- [x] Python-Skript auf lokale Datei-Pipes umstellen
|
||||
- [x] Zweistufige p-Gruppen-Abfrage stabilisieren
|
||||
- [ ] Cronjob/Intervall in Node-RED für tägliches Update einrichten
|
||||
|
||||
## 🐳 Docker & Deployment
|
||||
- [ ] Docker-Volumes für Produktion prüfen
|
||||
- [ ] Deployment auf unserer git-Umgebung erstellen. Automatisiert.
|
||||
- [ ] requirements.txt File erstellen, falls es dies ueberhaupt braucht.
|
||||
|
||||
## 😪 Fehlerbehandlung
|
||||
- [ ] Fehler beim periodischen Update der Daten abfangen
|
||||
|
||||
## 📜 Dokumentation
|
||||
- [ ] Ausfuehrliche Dokumentation fuer Kollegen
|
||||
|
||||
## 🙋♂️Abstimmung mit Benutzern
|
||||
- [ ] Mit Markus
|
||||
@@ -0,0 +1,13 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Erfordert keine schweren Systempakete, das beschleunigt auch den Build!
|
||||
# Wir installieren direkt die benötigten Python-Bibliotheken
|
||||
RUN pip3 install --no-cache-dir streamlit pandas plotly
|
||||
|
||||
# Port für Streamlit öffnen
|
||||
EXPOSE 8501
|
||||
|
||||
# Streamlit starten und an alle Netzwerk-Schnittstellen binden
|
||||
ENTRYPOINT ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
|
||||
@@ -0,0 +1,82 @@
|
||||
import streamlit as st
|
||||
import pandas as pd
|
||||
import plotly.express as px
|
||||
|
||||
# Umrechnungsfaktor von Terabytes nach Bytes
|
||||
TB2B = 1024**4
|
||||
|
||||
# Seitenkonfiguration (gibt uns ein schönes breites Layout)
|
||||
st.set_page_config(layout="wide")
|
||||
|
||||
# 1. Daten laden (Pfad aus deinem Docker-Setup)
|
||||
FILE_PATH = "/data/json_cache.json"
|
||||
|
||||
|
||||
@st.cache_data
|
||||
def load_data():
|
||||
# Falls die Datei existiert, laden, sonst leeres Dataframe für den Test
|
||||
try:
|
||||
return pd.read_json(FILE_PATH)
|
||||
except Exception:
|
||||
# Dummy-Daten, falls Node-RED noch nichts abgelegt hat
|
||||
return pd.DataFrame([
|
||||
{"ownerGroup": "a-123", "department": "4000", "size": 500000},
|
||||
{"ownerGroup": "p9999", "department": "6000", "size": 1200000}
|
||||
])
|
||||
|
||||
|
||||
df = load_data()
|
||||
|
||||
# 2. NAVIGATION LINKS (Sidebar)
|
||||
st.sidebar.title("Navigation")
|
||||
st.sidebar.markdown("Wählen Sie eine Ansicht:")
|
||||
|
||||
# Ein Radio-Button verhält sich wie eine klickbare Liste
|
||||
auswahl = st.sidebar.radio(
|
||||
label="Ansichten",
|
||||
options=["Übersicht & Metriken", "Bereichs-Analyse", "Nicht zuweisbare Daten", "Rohdaten"],
|
||||
label_visibility="collapsed" # Versteckt das Label für eine sauberere Optik
|
||||
)
|
||||
|
||||
# 3. ANZEIGE RECHTS (Hauptbildschirm)
|
||||
# Hier steuern wir den Inhalt basierend auf der Auswahl links
|
||||
|
||||
if auswahl == "Übersicht & Metriken":
|
||||
st.title("Übersicht")
|
||||
|
||||
# Beispiel für schnelle Metriken rechts
|
||||
col1, col2 = st.columns(2)
|
||||
col1.metric("Anzahl Archivgruppen", len(df))
|
||||
col2.metric("Gesamtvolumen (TB)", f'{df["size"].sum()/TB2B:.2f}' if "size" in df else "0.00")
|
||||
|
||||
# Hier könnte eine Grafik hin
|
||||
if "department" in df:
|
||||
fig = px.pie(df, names="department", title="Verteilung nach Departments")
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
elif auswahl == "Bereichs-Analyse":
|
||||
st.title("Bereichs-Analyse")
|
||||
st.write("Filtern Sie die Daten gezielt nach einzelnen Abteilungen.")
|
||||
|
||||
# Ein zusätzlicher Filter, der nur in dieser Ansicht erscheint
|
||||
if "department" in df:
|
||||
depts = sorted(df["department"].unique().tolist())
|
||||
gewaehltes_dept = st.selectbox("Wählen Sie ein Department:", depts)
|
||||
|
||||
filtered_df = df[df["department"] == gewaehltes_dept]
|
||||
st.metric(f"Gruppen in Department {gewaehltes_dept}", len(filtered_df))
|
||||
st.dataframe(filtered_df, use_container_width=True)
|
||||
|
||||
elif auswahl == "Nicht zuweisbare Daten":
|
||||
st.title("Nicht zuweisbare Daten")
|
||||
|
||||
# Die Tabelle, die du schon gebaut hast
|
||||
st.dataframe(df, use_container_width=True)
|
||||
|
||||
elif auswahl == "Rohdaten":
|
||||
st.title("Rohdaten")
|
||||
st.write("Durchsuchen und filtern Sie die vollständige Liste der Gruppen.")
|
||||
|
||||
# Die Tabelle, die du schon gebaut hast
|
||||
st.dataframe(df, use_container_width=True)
|
||||
|
||||
+10
-10
@@ -4,18 +4,14 @@ services:
|
||||
volumes:
|
||||
- ./logic/node-red-data:/data
|
||||
- ./shared_data:/data/results # Hier landen die JSON/SQLite Dateien
|
||||
- ./logic/scripts:/data/scripts # Hier liegen Ihre Python-Skripte
|
||||
- ./logic/scripts:/data/scripts
|
||||
environment:
|
||||
- TZ=Europe/Zurich
|
||||
# Link to the newest archivegroup information file downloaded
|
||||
# from metabase.psi.ch
|
||||
#- GROUPINFO_JSON_LNK_NAME=archivegroup_information.json
|
||||
- GROUPINFO_JSON_LNK_NAME_WITH_PATH=/data/archivegroup_information.json
|
||||
- NON_SPECIFIED_GROUPS=/data/non_specified_groups.csv
|
||||
# Beginning of name of the downloaded archivebroup information file
|
||||
- BEGIN_NAME_GROUPINFO_JSON_FILE=size_by_ownergroup_and_number_of_copies_
|
||||
- ERROR_LOGFILE=error.log
|
||||
# AD variables
|
||||
- JSON_CACHE=json_cache.json
|
||||
- AD_SERVER=d.psi.ch
|
||||
- AD_SEARCH_BASE=DC=d,DC=psi,DC=ch
|
||||
- AD_USER=${AD_USER}
|
||||
@@ -23,9 +19,13 @@ services:
|
||||
ports:
|
||||
- "1880:1880"
|
||||
|
||||
frontend-app:
|
||||
build: ./frontend
|
||||
# --- NEUER STREAMLIT CONTAINER ---
|
||||
analytics-app:
|
||||
build: ./analytics
|
||||
ports:
|
||||
- "8080:80"
|
||||
- "8501:8501" # Standard-Port für Streamlit im Browser
|
||||
volumes:
|
||||
- ./shared_data:/usr/share/nginx/html/data:ro # Nur lesend für die Webseite
|
||||
- ./analytics:/app # Mappt deinen Code, damit Änderungen live ohne Rebuild sichtbar sind
|
||||
- ./shared_data:/data:ro # Bindet die JSON-Daten NUR LESEND unter /data ein
|
||||
environment:
|
||||
- TZ=Europe/Zurich
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"node-red": {
|
||||
"name": "node-red",
|
||||
"version": "4.1.10",
|
||||
"version": "5.0.0",
|
||||
"local": false,
|
||||
"user": false,
|
||||
"nodes": {
|
||||
|
||||
@@ -173,11 +173,11 @@
|
||||
"property": "payload",
|
||||
"action": "",
|
||||
"pretty": false,
|
||||
"x": 1060,
|
||||
"y": 600,
|
||||
"x": 840,
|
||||
"y": 880,
|
||||
"wires": [
|
||||
[
|
||||
"426e46f53063d368"
|
||||
"8f50bc0cb9009461"
|
||||
]
|
||||
]
|
||||
},
|
||||
@@ -213,11 +213,11 @@
|
||||
"winHide": false,
|
||||
"oldrc": false,
|
||||
"name": "",
|
||||
"x": 620,
|
||||
"x": 540,
|
||||
"y": 620,
|
||||
"wires": [
|
||||
[
|
||||
"e8f1570441f300ee"
|
||||
"dd3574493b97a5f4"
|
||||
],
|
||||
[
|
||||
"444c29de997bd1b6"
|
||||
@@ -237,8 +237,8 @@
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 1320,
|
||||
"y": 600,
|
||||
"x": 1240,
|
||||
"y": 620,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
@@ -253,8 +253,92 @@
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 1060,
|
||||
"y": 720,
|
||||
"x": 920,
|
||||
"y": 700,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "dd3574493b97a5f4",
|
||||
"type": "file",
|
||||
"z": "f6f2187d.f17ca8",
|
||||
"name": "Write the new JSON object to cache",
|
||||
"filename": "\"/data/results/\" & $env(\"JSON_CACHE\")",
|
||||
"filenameType": "jsonata",
|
||||
"appendNewline": true,
|
||||
"createDir": false,
|
||||
"overwriteFile": "true",
|
||||
"encoding": "utf8",
|
||||
"x": 970,
|
||||
"y": 620,
|
||||
"wires": [
|
||||
[
|
||||
"426e46f53063d368"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "5c695a90a30b61d5",
|
||||
"type": "file in",
|
||||
"z": "f6f2187d.f17ca8",
|
||||
"name": "Read cached json file",
|
||||
"filename": "\"/data/results/\" & $env(\"JSON_CACHE\")",
|
||||
"filenameType": "jsonata",
|
||||
"format": "utf8",
|
||||
"chunk": false,
|
||||
"sendError": false,
|
||||
"encoding": "utf8",
|
||||
"allProps": false,
|
||||
"x": 480,
|
||||
"y": 880,
|
||||
"wires": [
|
||||
[
|
||||
"e8f1570441f300ee"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "6c8e4b6960b242ec",
|
||||
"type": "inject",
|
||||
"z": "f6f2187d.f17ca8",
|
||||
"name": "",
|
||||
"props": [
|
||||
{
|
||||
"p": "payload"
|
||||
},
|
||||
{
|
||||
"p": "topic",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"repeat": "",
|
||||
"crontab": "",
|
||||
"once": false,
|
||||
"onceDelay": 0.1,
|
||||
"topic": "",
|
||||
"payload": "",
|
||||
"payloadType": "date",
|
||||
"x": 200,
|
||||
"y": 880,
|
||||
"wires": [
|
||||
[
|
||||
"5c695a90a30b61d5"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "8f50bc0cb9009461",
|
||||
"type": "debug",
|
||||
"z": "f6f2187d.f17ca8",
|
||||
"name": "debug 1",
|
||||
"active": true,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
"complete": "false",
|
||||
"statusVal": "",
|
||||
"statusType": "auto",
|
||||
"x": 1120,
|
||||
"y": 880,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user