From 832ebf099a0bdb52e4091a82d1f19d3c482ad810 Mon Sep 17 00:00:00 2001 From: Steffen Probst Date: Fri, 21 Mar 2025 18:47:49 +0100 Subject: [PATCH] init --- .gitignore | 1 + Dockerfile | 19 ++ LICENSE.md | 26 ++ MANUAL.md | 152 +++++++++ README.md | 68 ++++ docker-compose.yml | 20 ++ main.go | 468 +++++++++++++++++++++++++++ static/config.yaml | 26 ++ static/css/main.css | 169 ++++++++++ static/css/print.css | 107 ++++++ static/images/ccheart_black.svg | 21 ++ static/images/ccheart_black.svg_.zip | Bin 0 -> 2291 bytes static/images/favicon-16x16.png | Bin 0 -> 659 bytes static/images/favicon-32x32.png | Bin 0 -> 1533 bytes static/images/favicon.ico | Bin 0 -> 15406 bytes static/images/favicon_io.zip | Bin 0 -> 70399 bytes static/images/logo.svg | 21 ++ static/index.html | 67 ++++ static/js/phonebook.js | 84 +++++ 19 files changed, 1249 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 LICENSE.md create mode 100644 MANUAL.md create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 main.go create mode 100644 static/config.yaml create mode 100644 static/css/main.css create mode 100644 static/css/print.css create mode 100644 static/images/ccheart_black.svg create mode 100644 static/images/ccheart_black.svg_.zip create mode 100644 static/images/favicon-16x16.png create mode 100644 static/images/favicon-32x32.png create mode 100644 static/images/favicon.ico create mode 100644 static/images/favicon_io.zip create mode 100644 static/images/logo.svg create mode 100644 static/index.html create mode 100644 static/js/phonebook.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c2ad09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d63ee51 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:1.21-alpine + +WORKDIR /app + +RUN mkdir -p static/css && mkdir -p static/images && go mod init ldap_phonebook_html +COPY *.go . +#COPY static static +RUN go mod tidy && go mod download && go build -o ldap_phonebook_html /app + +#ENV LDAP_SERVER="" +#ENV LDAP_PORT="636" +#ENV LDAP_BIND_DN="CN=,CN=Users,DC=,DC=local" +#ENV LDAP_BIND_PASSWORD="" +#ENV LDAP_BASE_DN="CN=Users,DC=,DC=local" +#ENV LDAP_FILTER=(objectClass=person) +#ENV SERVER_PORT="8080" + + +CMD ["/app/ldap_phonebook_html"] diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..48cef13 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,26 @@ +# LICENSE + +## Telefonbuch-Programm +Copyright (C) 2023 Steffen Probst + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +## Full License Text + +For the full license text, please visit: +[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html) + +## Development Assistance + +This program was developed with the assistance of Claude.ai, an AI assistant created by Anthropic, PBC. All copyrights for the resulting code remain with the author, Steffen Probst. diff --git a/MANUAL.md b/MANUAL.md new file mode 100644 index 0000000..8bcfe82 --- /dev/null +++ b/MANUAL.md @@ -0,0 +1,152 @@ +# Telefonbuch-Programm Benutzerhandbuch + +## Inhaltsverzeichnis +- [Einführung](#einführung) +- [Installation](#installation) +- [Konfiguration](#konfiguration) + - [Umgebungsvariablen](#umgebungsvariablen) + - [config.yaml](#configyaml) +- [Container-Rollout mit Podman](#container-rollout-mit-podman) +- [Active Directory Konfiguration](#active-directory-konfiguration) +- [Verwendung der Benutzeroberfläche](#verwendung-der-benutzeroberfläche) +- [Fehlerbehebung](#fehlerbehebung) + +## Einführung + +Dieses Telefonbuch-Programm ermöglicht es Ihnen, Kontaktinformationen aus Ihrem Active Directory (AD) zu extrahieren und in einer benutzerfreundlichen Weboberfläche darzustellen. Es unterstützt sowohl Benutzer als auch Kontakte aus dem AD und wird als Podman-Container bereitgestellt. + +## Installation + +1. Stellen Sie sicher, dass Podman und podman-compose auf Ihrem System installiert sind. +2. Klonen Sie das Repository oder laden Sie den Quellcode herunter. +3. Navigieren Sie zum Projektverzeichnis. + +## Konfiguration + +### Umgebungsvariablen + +Erstellen Sie eine `.env`-Datei im Projektverzeichnis mit folgenden Variablen: + +``` +LDAP_SERVER= +BIND_DN= +BIND_PASSWORD= +SEARCH_BASE= +``` + +### config.yaml + +Überprüfen und passen Sie die `config.yaml` Datei im `static`-Verzeichnis an: + +```yaml +phone_rules: + invalid_number: "+49 5331 89" + country: + prefix: "49" + area_codes: + - "5331" + internal_prefix: "89" +``` + +## Container-Rollout mit Podman + +1. Bauen und Starten des Containers: + ``` + podman-compose up -d + ``` + +2. Überprüfen Sie, ob der Container läuft: + ``` + podman ps + ``` + +3. Zugriff auf die Anwendung: + Öffnen Sie einen Webbrowser und navigieren Sie zu `http://localhost:8082`. + +4. Stoppen des Containers: + ``` + podman-compose down + ``` + +5. Logs anzeigen: + ``` + podman logs + ``` + +## Active Directory Konfiguration + +### Erforderliche Attribute + +Für Benutzer und Kontakte im AD sollten folgende Attribute korrekt gefüllt sein: + +- `givenName`: Vorname +- `sn`: Nachname +- `mail`: E-Mail-Adresse +- `telephoneNumber`: Bürotelefonnummer > Konfigurationsformat `+49 5331 89xxx` oder `+49 5331 89xxxxx` +- `mobile`: Mobiltelefonnummer > Konfigurationsformat `+49 1(5..7) xxxxxx` +- `otherTelephone`: Weitere Telefonnummern > Konfigurationsformat `+49 xxxx xxxxxx` +- `physicalDeliveryOfficeName`: Abteilung + +Für Kontakte: +- Erstellen Sie Kontaktobjekte in Ihrem AD +- Füllen Sie die oben genannten Attribute entsprechend aus + +### Anzeigeverhalten von Einträgen + +Das Telefonbuch-Programm filtert die Einträge aus dem Active Directory, um sicherzustellen, dass nur relevante und vollständige Informationen angezeigt werden. Rufnummer die in keine weiterführend Nummer haben, hinter dem Prefix, werden über InvalidNumber(unvollständige Rufnummer) in der `config.yaml` zusätzlich konfiguriert. -> Es werden nur vollständige Rufnmmer angezeigt, die im Format +49 5331 89xxx(xx) vorliegen. Aus der korrekten Rufnummer wird dann auch die interne Rufnummer generiert. Diese Funktion betrifft auch nur Attribute `telephoneNumber` aus dem AD. + +#### Allgemeine Bedingungen für alle Einträge + +Ein Eintrag (Benutzer oder Kontakt) wird nur dann angezeigt, wenn: + +1. Der Vorname (`givenName`) nicht leer ist. +2. Der Nachname (`sn`) nicht leer ist. + +#### Spezifische Bedingungen für Benutzer + +Ein Benutzer wird im Telefonbuch angezeigt, wenn: + +- Die allgemeinen Bedingungen erfüllt sind UND +- Mindestens eine der folgenden Bedingungen zutrifft: + * Die Bürotelefonnummer (`telephoneNumber`) ist vorhanden und nicht als ungültig markiert (siehe `config.yaml`). -> `InvalidNumber` + * Eine Mobiltelefonnummer (`mobile`) ist vorhanden. + * Die E-Mail-Adresse (`mail`) ist vorhanden. + +Ein Benutzer wird NICHT angezeigt, wenn: + +1. Keine Telefonnummer (weder Büro noch Mobil) vorhanden ist ODER +2. Die einzige vorhandene Telefonnummer als ungültig markiert ist (siehe `config.yaml`) UND +3. Keine E-Mail-Adresse vorhanden ist. + +#### Spezifische Bedingungen für Kontakte + +Ein Kontakt wird im Telefonbuch angezeigt, wenn die allgemeinen Bedingungen erfüllt sind. Kontakte werden großzügiger behandelt als Benutzer und werden angezeigt, auch wenn keine Telefonnummer oder E-Mail-Adresse vorhanden ist. + +#### Hinweise + +- Die Abteilung (`physicalDeliveryOfficeName`) ist optional und beeinflusst nicht die Anzeige im Telefonbuch. +- Zusätzliche Telefonnummern (`otherTelephone`) werden angezeigt, wenn vorhanden, beeinflussen aber nicht die Entscheidung, ob ein Eintrag im Telefonbuch erscheint. +- Die Unterscheidung zwischen Benutzern und Kontakten basiert auf dem `objectClass`-Attribut im Active Directory. + +## Verwendung der Benutzeroberfläche + +1. Starten Sie das Programm. +2. Öffnen Sie einen Webbrowser und navigieren Sie zu `http://localhost:`. + +### Suchfunktion +- Verwenden Sie das Suchfeld oben auf der Seite, um nach Namen, Telefonnummern oder anderen Informationen zu suchen. +- Die Suche ist Case-Sensitive (unterscheidet zwischen Groß- und Kleinschreibung). + +### Sortierung +- Klicken Sie auf die Spaltenüberschriften, um die Tabelle nach dieser Spalte zu sortieren. +- Ein erneuter Klick auf dieselbe Spalte kehrt die Sortierreihenfolge um. + +## Fehlerbehebung + +- Überprüfen Sie die Container-Logs mit `podman logs `. +- Stellen Sie sicher, dass alle Umgebungsvariablen in der `.env`-Datei korrekt gesetzt sind. +- Überprüfen Sie die Netzwerkverbindung zum LDAP-Server vom Host-System aus. +- Verifizieren Sie die LDAP-Anmeldeinformationen und Zugriffsrechte. +- Bei Problemen mit dem Volume-Mount, überprüfen Sie die Berechtigungen des `static`-Verzeichnisses. + +Wenn Probleme weiterhin bestehen, erstellen Sie bitte ein Issue im GitHub-Repository oder kontaktieren Sie den Entwickler. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9089a9e --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# README + +## Beschreibung +Telefonbuch für MKN, in Go programmiert und als Podman-Container bereitgestellt. + +## Features +- Anmeldung an die Domäne AD/LDAP +- Auslesen von Benutzern und Kontakten +- Formatierung der Rufnummern +- Ausgabe über internen Webserver in Go +- Suche und Sortierung in JavaScript +- Unterstützung für Benutzer und Kontakte aus dem Active Directory +- Bereitstellung als Podman-Container + +## Programmablauf +1. Start des Podman-Containers +2. Initialisierung der Go-Anwendung: + - Laden der Umgebungsvariablen + - Laden der YAML-Konfiguration + - Laden des HTML-Templates +3. Start des HTTP-Servers +4. Periodische Aktualisierung des Caches im Hintergrund +5. Verarbeitung eingehender HTTP-Anfragen +6. Beenden der Anwendung bei Container-Stopp + +## Installation und Konfiguration + +1. Vorbereitung: + - Stellen Sie sicher, dass Podman und podman-compose auf Ihrem System installiert sind. + - Klonen Sie das Repository in ein lokales Verzeichnis. + +2. Konfiguration: + - Erstellen Sie eine `.env`-Datei im Projektverzeichnis mit folgenden Variablen: + ``` + LDAP_SERVER= + BIND_DN= + BIND_PASSWORD= + SEARCH_BASE= + ``` + - Passen Sie die `config.yaml` im `static`-Verzeichnis nach Bedarf an. + +3. Container bauen und starten: + ``` + podman-compose up -d + ``` + +4. Zugriff auf die Anwendung: + Öffnen Sie einen Webbrowser und navigieren Sie zu `http://localhost:8082`. + +5. Container stoppen: + ``` + podman-compose down + ``` + +Für detailliertere Anweisungen, siehe MANUAL.md. + +## Lizenz + +Entwickler: Steffen Probst +E-Mail: [pts@mkn.de](mailto:pts@mkn.de) + +Dieses Projekt ist lizenziert unter der [GNU General Public License Version 3 (GPL-3.0)](https://www.gnu.org/licenses/gpl-3.0.html). + +### Entwicklungsunterstützung + +Dieses Programm wurde mit Unterstützung von Claude.ai, einem KI-Assistenten entwickelt von Anthropic, PBC, erstellt. Claude.ai wurde für Code-Generierung, Problemlösung und Optimierung eingesetzt. + +Hinweis: Während Claude.ai bei der Entwicklung half, liegt das Urheberrecht des resultierenden Codes beim Autor, Steffen Probst. Claude.ai ist ein Werkzeug und beansprucht keine Rechte an dem erstellten Code. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6042569 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +version: '3.8' + +services: + go-app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" + environment: + - LDAP_SERVER=${LDAP_SERVER} + - BIND_DN=${BIND_DN} + - BIND_PASSWORD=${BIND_PASSWORD} + - SEARCH_BASE=${SEARCH_BASE} + - SERVER_PORT=8080 + volumes: + - ./static:/app/static # Map the static directory from host to container + +volumes: + go-app-data: diff --git a/main.go b/main.go new file mode 100644 index 0000000..a8edb40 --- /dev/null +++ b/main.go @@ -0,0 +1,468 @@ +package main + +/* +Copyright (C) 2023 Steffen Probst + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +Entwickelt mit Unterstützung von Claude.ai, einem KI-Assistenten von Anthropic, PBC. +*/ +package main + +import ( + "bytes" + "crypto/md5" + "crypto/tls" + "encoding/xml" + "encoding/csv" + "fmt" + "io" + "log" + "net/http" + "os" + "strings" + "sync" + "time" + + "github.com/go-ldap/ldap/v3" + "golang.org/x/text/encoding/charmap" + "golang.org/x/text/transform" +) + +type Config struct { + LDAPServer string + BindDN string + BindPassword string + SearchBase string + ServerPort string +} + +type AddressBook struct { + XMLName xml.Name `xml:"AddressBook"` + Version string `xml:"version,attr"` + PBGroups []PBGroup `xml:"pbgroup"` + Contacts []Contact `xml:"Contact"` +} + +type PBGroup struct { + ID int `xml:"id,attr"` + Name string `xml:"name"` +} + +type Contact struct { + FirstName string `xml:"FirstName"` + LastName string `xml:"LastName"` + Frequent int `xml:"Frequent"` + Phones []Phone `xml:"Phone"` + Department string `xml:"Department,omitempty"` + Group int `xml:"Group"` +} + +type Phone struct { + Type string `xml:"type,attr"` + PhoneNumber string `xml:"phonenumber"` + AccountIndex int `xml:"accountindex"` +} + +type YealinkPhoneBook struct { + XMLName xml.Name `xml:"YealinkIPPhoneDirectory"` + Entries []YealinkEntry `xml:"DirectoryEntry"` +} + +type YealinkEntry struct { + Name string `xml:"Name"` + Telephone string `xml:"Telephone"` + Mobile string `xml:"Mobile,omitempty"` + OtherMobile string `xml:"Other,omitempty"` +} + +// Neue Struktur für das Contact-Format +type ContactXML struct { + XMLName xml.Name `xml:"contacts"` + Contacts []ContactEntry `xml:"contact"` +} + +type ContactEntry struct { + Name string `xml:"name,attr"` + Number string `xml:"number,attr"` + FirstName string `xml:"firstname,attr"` + LastName string `xml:"lastname,attr"` + Phone string `xml:"phone,attr"` + Mobile string `xml:"mobile,attr"` + Email string `xml:"email,attr"` + Address string `xml:"address,attr"` + City string `xml:"city,attr"` + State string `xml:"state,attr"` + Zip string `xml:"zip,attr"` + Comment string `xml:"comment,attr"` + ID string `xml:"id,attr"` + Info string `xml:"info,attr"` + Presence string `xml:"presence,attr"` + Starred string `xml:"starred,attr"` + Directory string `xml:"directory,attr"` +} + +var ( + err error + config Config + cache struct { + grandstreamFile *os.File + yealinkFile *os.File + contactFile *os.File + hash []byte + lastUpdate time.Time + mutex sync.RWMutex + } +) + +func main() { + config = Config{ + LDAPServer: os.Getenv("LDAP_SERVER"), + BindDN: os.Getenv("BIND_DN"), + BindPassword: os.Getenv("BIND_PASSWORD"), + SearchBase: os.Getenv("SEARCH_BASE"), + ServerPort: os.Getenv("SERVER_PORT"), + } + + if config.ServerPort == "" { + config.ServerPort = "8080" + } + + // Initialisiere den Cache vor dem Start des Servers + err = initializeCache() + if err != nil { + log.Fatalf("Fehler beim Initialisieren des Caches: %v", err) + } + + http.HandleFunc("/phonebook.xml", grandstreamPhoneBookHandler) + http.HandleFunc("/yphonebook.xml", yealinkPhoneBookHandler) + http.HandleFunc("/Contacts.xml", contactXMLHandler) + http.HandleFunc("/phonebook.csv", csvExportHandler) + + log.Printf("Server starting on port %s", config.ServerPort) + log.Fatal(http.ListenAndServe(":"+config.ServerPort, nil)) +} + +func grandstreamPhoneBookHandler(w http.ResponseWriter, r *http.Request) { + servePhoneBook(w, r, cache.grandstreamFile) +} + +func yealinkPhoneBookHandler(w http.ResponseWriter, r *http.Request) { + servePhoneBook(w, r, cache.yealinkFile) +} + +func contactXMLHandler(w http.ResponseWriter, r *http.Request) { + servePhoneBook(w, r, cache.contactFile) +} + +func csvExportHandler(w http.ResponseWriter, r *http.Request) { + err := updateCachedXMLIfNeeded() + if err != nil { + http.Error(w, "Fehler beim Aktualisieren der Kontakte", http.StatusInternalServerError) + return + } + + contacts, err := getADContacts() + if err != nil { + http.Error(w, "Fehler beim Abrufen der Kontakte", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/csv; charset=windows-1252") + w.Header().Set("Content-Disposition", "attachment; filename=phonebook.csv") + + if err := exportToCSV(contacts, w); err != nil { + http.Error(w, "Fehler beim Exportieren als CSV", http.StatusInternalServerError) + return + } +} + +func servePhoneBook(w http.ResponseWriter, r *http.Request, file *os.File) { + err := updateCachedXMLIfNeeded() + if err != nil { + http.Error(w, "Fehler beim Aktualisieren der Kontakte", http.StatusInternalServerError) + return + } + + cache.mutex.RLock() + defer cache.mutex.RUnlock() + + w.Header().Set("Content-Type", "application/xml") + w.WriteHeader(http.StatusOK) + + _, err = file.Seek(0, 0) + if err != nil { + log.Printf("Fehler beim Zurücksetzen des Datei-Offsets: %v", err) + http.Error(w, "Interner Serverfehler", http.StatusInternalServerError) + return + } + + _, err = io.Copy(w, file) + if err != nil { + log.Printf("Fehler beim Kopieren der XML-Datei: %v", err) + } +} + +func initializeCache() error { + log.Println("Initialisiere Cache...") + + // Führen Sie updateCachedXMLIfNeeded aus, um die Caching-Dateien zu erstellen + err := updateCachedXMLIfNeeded() + if err != nil { + return fmt.Errorf("Fehler beim Initialisieren des Caches: %v", err) + } + + // Überprüfen Sie, ob alle erforderlichen Dateien erstellt wurden + if cache.grandstreamFile == nil || cache.yealinkFile == nil || cache.contactFile == nil { + return fmt.Errorf("Nicht alle erforderlichen Cache-Dateien wurden erstellt") + } + + log.Println("Cache erfolgreich initialisiert") + return nil +} + +func updateCachedXMLIfNeeded() error { + cache.mutex.Lock() + defer cache.mutex.Unlock() + + contacts, err := getADContacts() + if err != nil { + return err + } + + newHash := calculateHash(contacts) + if cache.grandstreamFile != nil && cache.yealinkFile != nil && cache.contactFile != nil && bytes.Equal(newHash, cache.hash) { + return nil + } + + addressBook := AddressBook{ + Version: "1.0", + PBGroups: []PBGroup{ + {ID: 1, Name: "Blacklist"}, + {ID: 2, Name: "Work"}, + {ID: 3, Name: "Friend"}, + }, + Contacts: contacts, + } + + yealinkPhoneBook := YealinkPhoneBook{ + Entries: make([]YealinkEntry, len(contacts)), + } + + for i, c := range contacts { + yealinkPhoneBook.Entries[i] = YealinkEntry{ + Name: c.FirstName + " " + c.LastName, + Telephone: getPhoneByType(c.Phones, "Work"), + Mobile: getPhoneByType(c.Phones, "Mobile"), + OtherMobile: getPhoneByType(c.Phones, "Home"), + } + } + + // Erstellen der Contacts.xml + contactXML := ContactXML{ + Contacts: make([]ContactEntry, len(contacts)), + } + + for i, c := range contacts { + contactXML.Contacts[i] = ContactEntry{ + Name: c.FirstName + " " + c.LastName, + Number: getPhoneByType(c.Phones, "Work"), + FirstName: c.FirstName, + LastName: c.LastName, + Phone: getPhoneByType(c.Phones, "Work"), + Mobile: getPhoneByType(c.Phones, "Mobile"), + Presence: "0", + Starred: "0", + Directory: "0", + } + } + + if err := saveToTempFile(&addressBook, &cache.grandstreamFile); err != nil { + return fmt.Errorf("Fehler beim Speichern der Grandstream-XML: %v", err) + } + + if err := saveToTempFile(&yealinkPhoneBook, &cache.yealinkFile); err != nil { + return fmt.Errorf("Fehler beim Speichern der Yealink-XML: %v", err) + } + + if err := saveToTempFile(&contactXML, &cache.contactFile); err != nil { + return fmt.Errorf("Fehler beim Speichern der Contact-XML: %v", err) + } + + csvFile, err := os.OpenFile("phonebook.csv", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return fmt.Errorf("Fehler beim Erstellen der CSV-Datei: %v", err) + } + defer csvFile.Close() + + if err := exportToCSV(contacts, csvFile); err != nil { + return fmt.Errorf("Fehler beim Exportieren als CSV: %v", err) + } + + cache.hash = newHash + cache.lastUpdate = time.Now() + + return nil +} + +func saveToTempFile(data interface{}, file **os.File) error { + tempFile, err := os.CreateTemp("", "phonebook_*.xml") + if err != nil { + return fmt.Errorf("Fehler beim Erstellen der temporären Datei: %v", err) + } + + enc := xml.NewEncoder(tempFile) + enc.Indent("", " ") + if err := enc.Encode(data); err != nil { + tempFile.Close() + os.Remove(tempFile.Name()) + return fmt.Errorf("Fehler beim Kodieren der XML: %v", err) + } + + if *file != nil { + (*file).Close() + os.Remove((*file).Name()) + } + + *file = tempFile + return nil +} + +func calculateHash(contacts []Contact) []byte { + h := md5.New() + for _, contact := range contacts { + io.WriteString(h, contact.FirstName) + io.WriteString(h, contact.LastName) + for _, phone := range contact.Phones { + io.WriteString(h, phone.PhoneNumber) + } + } + return h.Sum(nil) +} + +func getADContacts() ([]Contact, error) { + l, err := ldap.DialTLS("tcp", config.LDAPServer+":636", &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return nil, fmt.Errorf("LDAP-Verbindung fehlgeschlagen: %v", err) + } + defer l.Close() + + err = l.Bind(config.BindDN, config.BindPassword) + if err != nil { + return nil, fmt.Errorf("LDAP-Bindung fehlgeschlagen: %v", err) + } + + // Erweiterter LDAP-Filter für Benutzer und Kontakte + searchFilter := "(&(|(objectClass=user)(objectClass=contact))(|(telephoneNumber=*)(otherTelephone=*)(mobile=*)(homePhone=*))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" + + searchRequest := ldap.NewSearchRequest( + config.SearchBase, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + searchFilter, + []string{"givenName", "sn", "displayName", "telephoneNumber", "otherTelephone", "mobile", "homePhone"}, + nil, + ) + + sr, err := l.Search(searchRequest) + if err != nil { + return nil, fmt.Errorf("LDAP-Suche fehlgeschlagen: %v", err) + } + + var contacts []Contact + for _, entry := range sr.Entries { + firstName := entry.GetAttributeValue("givenName") + lastName := entry.GetAttributeValue("sn") + + if firstName == "" || lastName == "" { + continue + } + + phone := formatPhoneNumber(entry.GetAttributeValue("telephoneNumber")) + otherMobile := formatPhoneNumber(entry.GetAttributeValue("otherMobile")) + mobile := formatPhoneNumber(entry.GetAttributeValue("mobile")) + + if isExcludedNumber(phone) || isExcludedNumber(otherMobile) || isExcludedNumber(mobile) { + continue + } + + if phone != "" || otherMobile != "" || mobile != "" { + contact := Contact{ + FirstName: firstName, + LastName: lastName, + Phones: []Phone{}, + Group: 2, // Standardmäßig zur "Work"-Gruppe hinzufügen + } + if phone != "" { + contact.Phones = append(contact.Phones, Phone{Type: "Work", PhoneNumber: phone, AccountIndex: 0}) + } + if mobile != "" { + contact.Phones = append(contact.Phones, Phone{Type: "Mobile", PhoneNumber: mobile, AccountIndex: 0}) + } + if otherMobile != "" { + contact.Phones = append(contact.Phones, Phone{Type: "Home", PhoneNumber: otherMobile, AccountIndex: 0}) + } + contacts = append(contacts, contact) + } + } + + return contacts, nil +} + +func removeDuplicateNumbers(phones []Phone) []Phone { + seen := make(map[string]bool) + result := []Phone{} + + for _, phone := range phones { + if !seen[phone.PhoneNumber] { + seen[phone.PhoneNumber] = true + result = append(result, phone) + } else if phone.Type == "Work" { + // Wenn die Nummer bereits gesehen wurde und dies die "Work" Nummer ist, + // ersetzen wir die vorherige (wahrscheinlich "Mobile") mit dieser. + for i, p := range result { + if p.PhoneNumber == phone.PhoneNumber { + result[i] = phone + break + } + } + } + } + + return result +} + +func isExcludedNumber(number string) bool { + excludedNumbers := []string{ + "+49 5331 89", + "+49533189", + "+ 49 5331 89", + } + + // Zuerst prüfen wir auf die ausgeschlossenen Nummern + for _, excluded := range excludedNumbers { + if strings.Contains(number, excluded) { + return true + } + } + + // Dann prüfen wir, ob die Nummer mit "089" endet + if len(number) >= 3 && number[len(number)-3:] == "089" { + return true + } + + // Wenn keine der obigen Bedingungen zutrifft, ist die Nummer nicht ausgeschlossen + return false +} + +func \ No newline at end of file diff --git a/static/config.yaml b/static/config.yaml new file mode 100644 index 0000000..68a2bcb --- /dev/null +++ b/static/config.yaml @@ -0,0 +1,26 @@ +phone_rules: + # Präfixe, die von Telefonnummern entfernt werden sollen + # trim_prefixes: + # - "0049" + # - "+49" + # - "49" + # - "00" + # - "0" + + # Regeln zur Formatierung von Telefonnummern + #format_rules: + # "+49": "0" # Ersetzt internationale Vorwahl durch nationale + # "00": "0" # Ersetzt internationale Vorwahl durch nationale + + # Standard-Präfix für Nummern ohne Ländervorwahl + #default_prefix: "+49" + + # Ungültige Telefonnummer, die ignoriert werden soll + invalid_number: "+49 5331 89" + + # Konfiguration für Deutschland + country: + prefix: "49" # Ländervorwahl ohne '+' oder '00' + area_codes: + - "5331" # Vorwahl für Wolfenbüttel + internal_prefix: "89" # Präfix für interne Nummern diff --git a/static/css/main.css b/static/css/main.css new file mode 100644 index 0000000..fc4a78c --- /dev/null +++ b/static/css/main.css @@ -0,0 +1,169 @@ +:root { + --background-body: #f5f5f5; + --background: #e0e0e0; + --text-main: #363636; + --text-bright: #000; + --links: #0076d1; + --focus: rgba(0,150,191,0.67); + --border: #dbdbdb; + --code: #000; + --animation-duration: 0.1s; + --button-hover: #9b9b9b; + --scrollbar-thumb: #aaa; + --form-placeholder: #949494; + --form-text: #1d1d1d; + --table-header: #d0d0d0; + --table-row-odd: #e8e8e8; + --table-row-even: #f0f0f0; + --sort-arrow: #666; +} + +body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + line-height: 1.4; + max-width: 80%; + margin: 0 auto; + padding: 0 10%; + color: var(--text-main); + background: var(--background-body); +} + +.container { + width: 100%; + max-width: 1200px; + margin: 0 auto; +} + +table { + width: 100%; + border-collapse: collapse; +} + +th, td { + padding: 8px; + text-align: left; + border-bottom: 1px solid var(--border); +} + +th { + background-color: var(--table-header); + font-weight: bold; + cursor: pointer; + position: relative; + padding-right: 20px; /* Space for sort indicator */ +} + +tr:nth-child(odd) { + background-color: var(--table-row-odd); +} + +tr:nth-child(even) { + background-color: var(--table-row-even); +} + +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.logo { + width: 100px; + height: 100px; +} + +h1 { + text-align: center; + flex-grow: 1; + margin: 0 20px; +} + +#searchInput { + width: 100%; + font-size: 16px; + padding: 12px 20px; + margin: 8px 0; + box-sizing: border-box; +} + +/* Sort indicators */ +th::after { + content: '\25B2'; /* Upward triangle */ + position: absolute; + right: 5px; + opacity: 0.3; + color: var(--sort-arrow); +} + +th.asc::after { + content: '\25B2'; /* Upward triangle */ + opacity: 1; +} + +th.desc::after { + content: '\25BC'; /* Downward triangle */ + opacity: 1; +} + +th:hover::after { + opacity: 0.6; +} + +footer { + margin-top: 20px; + padding: 10px 0; + background-color: var(--background); + font-size: 8px; +} + +.footer-container { + display: flex; + justify-content: space-between; + align-items: center; + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +a[href^="tel:"]:before { + content: "📞 "; +} + +a[href^="mailto:"]:before { + content: "📧 "; +} + +/* Responsive design */ +@media (max-width: 768px) { + body { + max-width: 95%; + padding: 0 2.5%; + } + + .header-container { + flex-direction: column; + } + + .logo { + margin-bottom: 10px; + } + + h1 { + font-size: 24px; + } + + th, td { + padding: 6px; + } +} + +/* Accessibility improvements */ +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} \ No newline at end of file diff --git a/static/css/print.css b/static/css/print.css new file mode 100644 index 0000000..5fd2a6a --- /dev/null +++ b/static/css/print.css @@ -0,0 +1,107 @@ +@media print { + @page { + size: A4 landscape; + margin: 0.8cm; + @bottom-right { + content: "Seite " counter(page) " von " counter(pages); + font-size: 8pt; + margin-right: -0.5cm; + margin-bottom: -0.5cm; + } + } + + html, body { + height: 100%; + width: 100%; + margin: 0; + padding: 0; + font-size: 9pt; + line-height: 1.3; + background: none; + color: #000; + display: flex; + justify-content: center; + align-items: flex-start; + } + + .dynamic-table-container { + width: 100%; + display: flex; + justify-content: center; + margin-bottom: 1.5cm; + } + + table { + width: 100%; + max-width: 29.7cm; /* A4 landscape width minus margins */ + border-collapse: collapse; + page-break-inside: auto; + } + + thead { + display: table-header-group; + } + + tbody { + display: table-row-group; + } + + tr { + page-break-inside: avoid; + page-break-after: auto; + } + + th, td { + padding: 0.1cm; + border: 1px solid #000; + text-align: center; + vertical-align: middle; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + th { + background-color: #f0f0f0 !important; + -webkit-print-color-adjust: exact; + color-adjust: exact; + } + + a { + text-decoration: none; + color: #000; + } + + /* Hide elements not needed for print */ + #searchInput, .sort-icon::after, .header-container, footer, .print-date { + display: none; + } + + /* Column widths */ + th:nth-child(1), td:nth-child(1), + th:nth-child(2), td:nth-child(2) { + width: 15%; + } + th:nth-child(3), td:nth-child(3) { + width: 20%; + } + th:nth-child(4), td:nth-child(4) { + width: 10%; + } + th:nth-child(5), td:nth-child(5) { + width: 25%; + } + th:nth-child(6), td:nth-child(6) { + width: 15%; + } + + /* Remove top margin for the first page */ + .dynamic-table-container:first-child { + margin-top: 0; + } + + /* Ensure content on subsequent pages starts at the top */ + @page :not(:first) { + margin-top: 0.8cm; + } +} diff --git a/static/images/ccheart_black.svg b/static/images/ccheart_black.svg new file mode 100644 index 0000000..637e186 --- /dev/null +++ b/static/images/ccheart_black.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/static/images/ccheart_black.svg_.zip b/static/images/ccheart_black.svg_.zip new file mode 100644 index 0000000000000000000000000000000000000000..f7c41d466215b050a6eba414bc52412e4cc0b85c GIT binary patch literal 2291 zcmai$c{CLK8pp>n8fIh?N|x+pjfffB%%CP?9}$yvW+IuyG?9{P$r9NnvS%!f?wE`a zWs;>V3FVcEY*~sdS;~?nmvhg3U++ELbD!t@&hz~Kc%Jk9<8!{}v9;jg6#;;@6KapP z2JDJ`04RV!xNzQ+OmWAPJPE!UR|35qoFITZQ+Ss$D;Jj~E)hV7qwD4VQ_v8hZ>b@j^ERzYC{e(s`6ANBd;WHg;_r`&F(%4q#kSsOO6=qm*mwVL8+$BM2E^k?U0 zx&|yxj#BbFYdWgj)mK!HS78UqHCan*W!M@ z@C07GeI(J$OShe|0D08YK=+O5@v@O(>JE<1PqbW4t4Qt9>zY2YNxOglNxaQ0pmM5X z?@~3!w@K)lTMD;^AeGDPY3yTWmpdY&hEWIe5-IX!j#`~&Nn z(9q*yDI_)E5sM(F0?ehDH$XDU=Hu03y+JbPQhndh^K-d@FX?$NK2o~fMh`Rc*#}=h z|H!NQ&h~Mf&PU9$YVwSq#D_i&d1(A2K58fgN4YWRTtRc3!I5rVmfW>IP5H){K~aoivZQ1D(*#Lj7AA3sj{0zK(m(e#MMwVe z?p8Q+xsS)w1lbA8yIA0Q)PxLbfVta4W-RZy>Nb<&c-kNoWxR6q>E0iFT^(NygL?a{B4>cu`I*yd^3ExMplr;+ zAR`nz?Le)P`qvf`lQXdi>_>LPV&*c6 zW0GgpPDiRpD{^7^F1)RloRScJ0BqvZ2U5a%p)~+sRHBPojl#e_3-^+%SB<281@Jzl zijN*e+c8wZ0P+W~`FY_9<&XSK#ZnX^G36c)-^g(AyM&7YICCtbIkDH9bVx_$^`|gZ zH1TVV8&uWAXTqZ}gI6U))+npHS0PcYTuk(4%Kb3+1!>oYTXdzOc{kWn9Fdc}3aJ;x zEZ|jjgfz>@`(B`7r<{@6YBaln{FXD?!1GZ$e!b=$4`ELaD#%7>$IH*OXCP+$l;rRk zD4`tQ&Jio8(@e7y2Vf|LdEl!UeYm*#{C+h)MENyPLCc*|#=?Y|67;$#lqW)6caJhi z2g`(y`0@hxAQ}S>HaZvZH{N)x4vzFc{A-k`8O~R!awqLnAvVIa9I9j=rw&1Xn@aVt z&eU)FRV~+YEH0-GOWazLTmQ#7;z}L#=NAGkbI$nqxNZsyF|fsJYq1A3YHYw`1q1*V z1ONcrZExN#+fk3R{F^uPZ+o-5yA{gR#@^}Jjwcr{;$3#$jwkPo{oc|gcaLef|6g}c zuzTazjTDh8HmRHvkaa>ME0E2$@pxW!>EKX$s+~x|>^O>c1%WD6^P_lwlj=4?FeM8MUizj{Hub(5n zQ*%qE3~QqpRB^Lu0emLey6_|)+*_bqG^>ks(t0jK&h4zW#~meFbJ+kxw*t|tqLus1 z3mz?&?Gf!ILWL`eOeYGy0x(~{(vh^+EA!L2bWRZ5nT`|*%nqTjxb@=97Qnr$0sXb} zu3OhPf(@k-lwQgl#FNa6N2=uObmZW?7%g2VWOj*3jiqQ z-hqpYTa@Q7PIiO04*>bkA%H9j-)Vn?3h2*6;NPqM7pZ@(+C=4Slf8>u(4XA?x0>Bb eCI6P)&lQjvX@Gr;~ zYP3*-g_5{7f(XGFP*>5gJG=AYxo_TnL?6t|d-Kk{=iI}+s<2d@hr*mj^41Vp5pXQHj$GF5XK9tP^#8>?JL3+FJfqd4W-D z#5jCJRV>LZ$RucS$yf7YqTj`(0T<1PR}9Lqw3lC+*!UWDYSTsm(gco`O#I{t58rDs z%AZ5zJcT=FvsmA5BF}xE5*~kS5*+c7AeP;&;NoEyKL0=44x#v6Egrz-L5~RMc z1ncC;)vlz-tV{pd7?#_e$* z58jG`R5nZaW=n8z@}R;%&cV4uF2?&Egfx4q{Kxc&n0jT6O1QY~}y}002ovPDHLkV1nljH1hxe literal 0 HcmV?d00001 diff --git a/static/images/favicon-32x32.png b/static/images/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..8b6346a06f4a47682386e53ee8ca9b90cff17a24 GIT binary patch literal 1533 zcmVUBZPVSpw!5A6+?mef0Nu&#TEtgsc-o zdj$c9f*Z;*(KZQ!pf#FTMN}hWoMYc?S0nC#Bvq32|Md!Fc~rw9xp<%vrk4Sj=1374 zeFR`g5{P%1UY=zkL4gznXc@DEspUOdGnhiQwSgYZCv%9fux2QRN2Bw~X4p@~Jp*{X zi?onJ076^o9H@*cLt2Z%wrK%Ga-x^f&nt6L8Q@baVP%FD#aR|S`H&f@cEuF~6B4cu zPUHN)K3wYaM{$pKSTRgyF`5gETJlm&II!A5UJKyd-#%O(@S}fRl9i-6MJ!7-qx2Cg zrpcj|9pku0fOzgV>YWvUiX1CmUlND+NO9|3zwS6C*#s znIPXFGJtYuVWSFsMxec4oc~o%)toEQ0fI$jJvW(c(68!XhGqyhw zhaE(HSVB2;yOuie&N93F&VsR)=wCbEf^>%o+rRS=ps=a6o1Ccb@}i@c#X~SLh391# z@RG{m0y{eI`_c4|Pe)vaQ$TB3BI3*f+V1-B3IWt^Xtc02+k*TwGpdNm;G>N;*#*j( zWJxxRu#x=x#t)s{FLwI?D1GBB_uJ zr?9mw0YwdNOl!*6j>R@P&=@DhUyY!9m=w-XMolz-u{;s0>PARPT`bCxE5j5Po}?I{ zcVl5WUF^c%_HiA>^(8Lyrv>*%r?G}C(mE#^4(5XDA{R;;BV8Z^$fh9Vd2wf69Qr7+ zoc&YDW_B~sRgnZUndB$*+nU|@wLf+#Vsa&mEGVQC^6rIkeGI6Gb%{vpOO{@b7CKP= z&8W;yPKpW5TM|M_KE5)6JEIcnZcS1UkYp4rHAPN*(LITl+vH15;g6XKKV0uX(_cP( zf7^%0lSO>HIT3;hWVw&n31IOY6V}zcLt&tIgMj?G%m^on=(MmR75Pvv7ONW0rE*)d z!G+i8I65pQd{Ah|m@k0eDEaKnvt#$g37jG8c!9`@R0E~r{hi~upk_uc1T6qv^QfAW zM1{p}C0}M+$#TD3W?6iUQRO>v-D_Z8Ic6zw0zRQ4)zag`@v8(d#xtvO%;cZVG-F++ z75k_pM|+q7YaTY^P=N!V{^3RSH7|duCfN7@qTB%vtg>SsRl!rgdvRr88Xj_oh1Rlw zeTqu+3y)gmQd~tKf1&yC!)whRs3i+3kgz<}f){gaSVApXl8xGJ3N99qU4wp^^3Gmk zOBR_SD2_vIx}E`AYpjaA1fwU2wPn<>hZb@qcM>`Q9P(L*xYnAD?=j|%|e}gIc3+gOuC+nLf|b*jA?2JsQ)-VROw+2hZO1rV*A-} jvtl>&ej@)EyPp3KVasF1W)Kd+00000NkvXXu0mjf%`Mes literal 0 HcmV?d00001 diff --git a/static/images/favicon.ico b/static/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..323eb071b9db3dd53ad5ac60bae6c3e8c3a3d276 GIT binary patch literal 15406 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 M`}ej4#v+0L0`T|phX4Qo literal 0 HcmV?d00001 diff --git a/static/images/favicon_io.zip b/static/images/favicon_io.zip new file mode 100644 index 0000000000000000000000000000000000000000..3b03a6be173ca85217cf58f85a0c18d2e362325e GIT binary patch literal 70399 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 + + + + + + + + + + + + + + diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..683db73 --- /dev/null +++ b/static/index.html @@ -0,0 +1,67 @@ + + + + + + + + ☎ Telefonbuch ☎ + + + + + + +
+ +

☎ Telefonbuch ☎

+ +
+ +
+ + + + + + + + + + + + + {{range .}} + + + + + + + + + {{end}} + +
NachnameVornameTelefonnummerInterne RufnummerE-MailAbteilung
{{.LastName}}{{.FirstName}} + {{range .Phones}} + ({{.Type}}) {{.PhoneNumber}}
+ {{else}}-{{end}} +
{{if .InternalPhone}}{{.InternalPhone}}{{else}}-{{end}}{{.Email}}{{if .Department}}{{.Department}}{{else}}-{{end}}
+
+ + + + + + diff --git a/static/js/phonebook.js b/static/js/phonebook.js new file mode 100644 index 0000000..9945fcc --- /dev/null +++ b/static/js/phonebook.js @@ -0,0 +1,84 @@ +function searchTable() { + const input = document.getElementById("searchInput"); + const filter = input.value; // Entfernung von .toUpperCase() + const table = document.getElementById("phonebookTable"); + const tr = table.getElementsByTagName("tr"); + + for (let i = 1; i < tr.length; i++) { + let visible = false; + const td = tr[i].getElementsByTagName("td"); + for (let j = 0; j < td.length; j++) { + if (td[j]) { + const txtValue = td[j].textContent || td[j].innerText; + if (txtValue.indexOf(filter) > -1) { // Entfernung von .toUpperCase() + visible = true; + break; + } + } + } + tr[i].style.display = visible ? "" : "none"; + } +} + +let lastSortedColumn = 0; +let sortDirections = Array(document.querySelectorAll('#phonebookTable th').length).fill('asc'); + +// Array to keep track of the sort state for each column +let sortStates = []; + +function sortTable(n) { + const table = document.getElementById("phonebookTable"); + const tbody = table.querySelector("tbody"); + const rows = Array.from(tbody.querySelectorAll("tr")); + const th = table.querySelectorAll("th")[n]; + + // Initialize sort state if not set + if (sortStates[n] === undefined) { + sortStates[n] = 'asc'; + } else { + // Toggle sort direction + sortStates[n] = sortStates[n] === 'asc' ? 'desc' : 'asc'; + } + + const isAsc = sortStates[n] === 'asc'; + + // Clear all sorting classes + table.querySelectorAll("th").forEach(header => header.classList.remove("asc", "desc")); + + // Set the appropriate class for the clicked header + th.classList.add(isAsc ? "asc" : "desc"); + + rows.sort((a, b) => { + let aValue = a.cells[n].textContent.trim(); + let bValue = b.cells[n].textContent.trim(); + + // Check if the values are numbers + if (!isNaN(aValue) && !isNaN(bValue)) { + return isAsc ? aValue - bValue : bValue - aValue; + } + + // For non-numeric values, use localeCompare for proper string comparison + return isAsc ? + aValue.localeCompare(bValue, undefined, {sensitivity: 'base'}) : + bValue.localeCompare(aValue, undefined, {sensitivity: 'base'}); + }); + + // Remove all existing rows + while (tbody.firstChild) { + tbody.removeChild(tbody.firstChild); + } + + // Add sorted rows + tbody.append(...rows); +} + +// Add event listeners to table headers and initial sort +document.addEventListener('DOMContentLoaded', function() { + const headers = document.querySelectorAll('#phonebookTable th'); + headers.forEach((header, index) => { + header.addEventListener('click', () => sortTable(index)); + }); + + // Initial sort on the first column (index 0) in ascending order + sortTable(0); +}); \ No newline at end of file