ok
This commit is contained in:
181
main.go
181
main.go
@@ -51,40 +51,40 @@ func cacheControl(next http.Handler) http.Handler {
|
|||||||
type RuleSet struct {
|
type RuleSet struct {
|
||||||
Id int
|
Id int
|
||||||
Name string
|
Name string
|
||||||
Default_contact_read int
|
Default_contact_read sql.NullInt64
|
||||||
Default_contact_write int
|
Default_contact_write sql.NullInt64
|
||||||
Default_contact_delete int
|
Default_contact_delete sql.NullInt64
|
||||||
Default_keyword_read int
|
Default_keyword_read sql.NullInt64
|
||||||
Default_keyword_write int
|
Default_keyword_write sql.NullInt64
|
||||||
Default_keyword_delete int
|
Default_keyword_delete sql.NullInt64
|
||||||
Default_keyword_attach int
|
Default_keyword_attach sql.NullInt64
|
||||||
Default_keyword_detach int
|
Default_keyword_detach sql.NullInt64
|
||||||
Default_aduser_read int
|
Default_aduser_read sql.NullInt64
|
||||||
Default_aduser_write int
|
Default_aduser_write sql.NullInt64
|
||||||
Default_aduser_delete int
|
Default_aduser_delete sql.NullInt64
|
||||||
Default_location_read int
|
Default_location_read sql.NullInt64
|
||||||
Default_location_write int
|
Default_location_write sql.NullInt64
|
||||||
Default_location_delete int
|
Default_location_delete sql.NullInt64
|
||||||
Default_department_read int
|
Default_department_read sql.NullInt64
|
||||||
Default_department_write int
|
Default_department_write sql.NullInt64
|
||||||
Default_department_delete int
|
Default_department_delete sql.NullInt64
|
||||||
Self_contact_read int
|
Self_contact_read sql.NullInt64
|
||||||
Self_contact_write int
|
Self_contact_write sql.NullInt64
|
||||||
Self_keyword_attach int
|
Self_keyword_attach sql.NullInt64
|
||||||
Self_keyword_detach int
|
Self_keyword_detach sql.NullInt64
|
||||||
Private_contact_read int
|
Private_contact_read sql.NullInt64
|
||||||
Private_contact_write int
|
Private_contact_write sql.NullInt64
|
||||||
Private_keyword_add int
|
Private_keyword_add sql.NullInt64
|
||||||
Private_keyword_delete int
|
Private_keyword_delete sql.NullInt64
|
||||||
Private_keyword_attach int
|
Private_keyword_attach sql.NullInt64
|
||||||
Private_keyword_detach int
|
Private_keyword_detach sql.NullInt64
|
||||||
}
|
}
|
||||||
|
|
||||||
type ADUser struct {
|
type ADUser struct {
|
||||||
Id int
|
Id int
|
||||||
SamAccountName string
|
SamAccountName string
|
||||||
Sid string
|
Sid string
|
||||||
RuleSetId RuleSet
|
RuleSetId sql.NullInt64
|
||||||
}
|
}
|
||||||
|
|
||||||
type Department struct {
|
type Department struct {
|
||||||
@@ -102,21 +102,32 @@ type Location struct {
|
|||||||
|
|
||||||
type Contact struct {
|
type Contact struct {
|
||||||
Id int
|
Id int
|
||||||
OwnerId int
|
OwnerId sql.NullInt64
|
||||||
AdUserId ADUser
|
AdUserId sql.NullInt64
|
||||||
DisplayName string
|
DisplayName string
|
||||||
Phone string
|
Phone string
|
||||||
Mobile string
|
Mobile string
|
||||||
Homeoffice string
|
Homeoffice string
|
||||||
Email string
|
Email string
|
||||||
Room string
|
Room string
|
||||||
DepartmentId Department
|
DepartmentId sql.NullInt64
|
||||||
LocationId Location
|
LocationId sql.NullInt64
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContactKeywordLink struct {
|
type ContactKeywordLink struct {
|
||||||
Contact int
|
Contact sql.NullInt64
|
||||||
Keyword int
|
Keyword sql.NullInt64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Keyword struct {
|
||||||
|
Id int
|
||||||
|
Owner sql.NullInt64
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataPort struct {
|
||||||
|
Contacts []Contact
|
||||||
|
Keywords []Keyword
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ################################################################## */
|
/* ################################################################## */
|
||||||
@@ -125,13 +136,71 @@ type ContactKeywordLink struct {
|
|||||||
|
|
||||||
// ----- Example handlers -----
|
// ----- Example handlers -----
|
||||||
func (s *Server) publicHello(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) publicHello(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprintln(w, "Hallo an alle – öffentliche Daten")
|
fmt.Fprintln(w, "Hallo an alle - öffentliche Daten")
|
||||||
}
|
}
|
||||||
func (s *Server) privateHello(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) privateHello(w http.ResponseWriter, r *http.Request) {
|
||||||
user := r.Context().Value(userKey).(string)
|
user := r.Context().Value(userKey).(string)
|
||||||
fmt.Fprintf(w, "Hallo %s – hier deine persönlichen Daten", user)
|
fmt.Fprintf(w, "Hallo %s - hier deine persönlichen Daten", user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDataReturnDataPort(QueryContact, QueryKeyword string) DataPort {
|
||||||
|
rows, err := DB.Query(QueryContact)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("a", err)
|
||||||
|
}
|
||||||
|
var contList []Contact
|
||||||
|
for rows.Next() {
|
||||||
|
var c Contact
|
||||||
|
err = rows.Scan(&c.Id, &c.OwnerId, &c.AdUserId, &c.DisplayName, &c.Phone, &c.Mobile, &c.Homeoffice, &c.Email, &c.Room, &c.DepartmentId, &c.LocationId)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("b", err)
|
||||||
|
}
|
||||||
|
contList = append(contList, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows1, err := DB.Query(QueryKeyword)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("c", err)
|
||||||
|
}
|
||||||
|
var keywordList []Keyword
|
||||||
|
for rows1.Next() {
|
||||||
|
var c0 Keyword
|
||||||
|
err = rows1.Scan(&c0.Id, &c0.Owner, &c0.Name)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("d", err)
|
||||||
|
}
|
||||||
|
keywordList = append(keywordList, c0)
|
||||||
|
}
|
||||||
|
return DataPort{Contacts: contList, Keywords: keywordList}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListPublic(w http.ResponseWriter, r *http.Request) {
|
||||||
|
D := GetDataReturnDataPort("SELECT * FROM contact c WHERE c.contact_owner_id = -1;", "SELECT * FROM keyword c WHERE c.keyword_owner = -1;")
|
||||||
|
funcs := template.FuncMap{"now": time.Now}
|
||||||
|
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||||||
|
layout := template.Must(template.New("base").Funcs(funcs).ParseFiles(templatesDir + "/base.html"))
|
||||||
|
tplFull := template.Must(layout.Clone())
|
||||||
|
template.Must(tplFull.Funcs(funcs).ParseFiles(templatesDir+"/kontaktliste.html", templatesDir+"/schlagwortliste.html"))
|
||||||
|
tplFull.ExecuteTemplate(w, "layout", D)
|
||||||
|
/*user := r.Context().Value(userKey).(string)
|
||||||
|
fmt.Fprintf(w, "Hallo %s - hier deine persönlichen Daten", user)*/
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListPrivate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
D := GetDataReturnDataPort("SELECT * FROM contact c WHERE c.contact_owner_id = -1 OR c.contact_owner_id = 1;", "SELECT * FROM keyword c WHERE c.keyword_owner = -1 OR c.keyword_owner = 1;")
|
||||||
|
funcs := template.FuncMap{"now": time.Now}
|
||||||
|
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||||||
|
layout := template.Must(template.New("base").Funcs(funcs).ParseFiles(templatesDir + "/base.html"))
|
||||||
|
tplFull := template.Must(layout.Clone())
|
||||||
|
template.Must(tplFull.Funcs(funcs).ParseFiles(templatesDir+"/kontaktliste.html", templatesDir+"/schlagwortliste.html"))
|
||||||
|
tplFull.ExecuteTemplate(w, "layout", D)
|
||||||
|
/*user := r.Context().Value(userKey).(string)
|
||||||
|
fmt.Fprintf(w, "Hallo %s - hier deine persönlichen Daten", user)*/
|
||||||
|
}
|
||||||
|
|
||||||
|
var CFG Config
|
||||||
|
var DB *sql.DB
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
// Signal-Kanal einrichten
|
// Signal-Kanal einrichten
|
||||||
@@ -150,32 +219,33 @@ func main() {
|
|||||||
staticDir := getenv("BLOG_STATIC_DIR", "./static")
|
staticDir := getenv("BLOG_STATIC_DIR", "./static")
|
||||||
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
templatesDir := getenv("BLOG_TEMPLATES_DIR", "./static/templates")
|
||||||
|
|
||||||
cfg := Config{
|
CFG = Config{
|
||||||
DSN: "hikos:hikos@tcp(10.10.5.31:3306)/hikos?parseTime=true",
|
DSN: "hikos:hikos@tcp(10.10.5.31:3306)/hikos?parseTime=true",
|
||||||
LDAPURL: "ldaps://ldaps.example.com:636",
|
LDAPURL: "ldaps://ldaps.example.com:636",
|
||||||
LDAPBindPattern: "uid=%s,ou=users,dc=example,dc=com",
|
LDAPBindPattern: "uid=%s,ou=users,dc=example,dc=com",
|
||||||
SessionTTL: 24 * time.Hour,
|
SessionTTL: 24 * time.Hour,
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := sql.Open("mysql", cfg.DSN)
|
db, err := sql.Open("mysql", CFG.DSN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := db.Ping(); err != nil {
|
if err := db.Ping(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
DB = db
|
||||||
|
|
||||||
store := &SessionStore{DB: db}
|
store := &SessionStore{DB: db}
|
||||||
auth := &LDAPAuthenticator{
|
auth := &LDAPAuthenticator{
|
||||||
URL: cfg.LDAPURL,
|
URL: CFG.LDAPURL,
|
||||||
BindPattern: cfg.LDAPBindPattern,
|
BindPattern: CFG.LDAPBindPattern,
|
||||||
TLSConfig: &tls.Config{
|
TLSConfig: &tls.Config{
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := &Server{
|
srv := &Server{
|
||||||
cfg: cfg,
|
cfg: CFG,
|
||||||
sessions: store,
|
sessions: store,
|
||||||
auth: auth,
|
auth: auth,
|
||||||
}
|
}
|
||||||
@@ -192,17 +262,26 @@ func main() {
|
|||||||
tplKontakt := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/kontaktliste.html"))
|
tplKontakt := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/kontaktliste.html"))
|
||||||
tplSchlagwort := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/schlagwortliste.html"))
|
tplSchlagwort := template.Must(template.New("kontakt").Funcs(funcs).ParseFiles(templatesDir + "/schlagwortliste.html"))
|
||||||
|
|
||||||
|
layoutSSO := template.Must(template.New("sso").Funcs(funcs).ParseFiles(templatesDir + "/login.html"))
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
mux.HandleFunc("/login", srv.loginHandler)
|
mux.HandleFunc("/login", srv.loginHandler)
|
||||||
//mux.Handle("/protected", srv.withAuth(http.HandlerFunc(srv.protectedHandler)))
|
//mux.Handle("/protected", srv.withAuth(http.HandlerFunc(srv.protectedHandler)))
|
||||||
|
|
||||||
|
mux.HandleFunc("/sso", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
layoutSSO.ExecuteTemplate(w, "sso", nil)
|
||||||
|
})
|
||||||
|
|
||||||
mux.Handle("/hello", srv.authAware(true, http.HandlerFunc(srv.publicHello), http.HandlerFunc(srv.privateHello)))
|
mux.Handle("/hello", srv.authAware(true, http.HandlerFunc(srv.publicHello), http.HandlerFunc(srv.privateHello)))
|
||||||
|
|
||||||
// Handler für /
|
// Handler für /
|
||||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
mux.Handle("/", srv.authAware(false, http.HandlerFunc(srv.ListPublic), http.HandlerFunc(srv.ListPrivate)))
|
||||||
|
|
||||||
|
/*mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
tplFull.ExecuteTemplate(w, "layout", nil)
|
tplFull.ExecuteTemplate(w, "layout", nil)
|
||||||
})
|
})*/
|
||||||
|
|
||||||
mux.HandleFunc("/htmx/kontakt", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/htmx/kontakt", func(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
@@ -223,9 +302,10 @@ func main() {
|
|||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var c Contact
|
var c Contact
|
||||||
err = rows.Scan(&c.Id)
|
err = rows.Scan(&c.Id)
|
||||||
|
contList = append(contList, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
tplKontakt.ExecuteTemplate(w, "kontakt", nil)
|
tplKontakt.ExecuteTemplate(w, "kontakt", contList)
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.HandleFunc("/htmx/kontaktbyschlagwort", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/htmx/kontaktbyschlagwort", func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -393,10 +473,21 @@ func (s *Server) loginHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
user := strings.TrimSpace(r.Form.Get("username"))
|
user := strings.TrimSpace(r.Form.Get("username"))
|
||||||
pass := r.Form.Get("password")
|
pass := r.Form.Get("password")
|
||||||
|
|
||||||
|
if 1 == 2 {
|
||||||
if err := s.auth.Authenticate(user, pass); err != nil {
|
if err := s.auth.Authenticate(user, pass); err != nil {
|
||||||
http.Error(w, "invalid credentials", http.StatusUnauthorized)
|
http.Error(w, "invalid credentials", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if user == "admin" && pass == "admin" {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
http.Error(w, "invalid credentials", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
token, err := s.sessions.Create(user, s.cfg.SessionTTL)
|
token, err := s.sessions.Create(user, s.cfg.SessionTTL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("cannot create session:", err)
|
log.Println("cannot create session:", err)
|
||||||
@@ -404,7 +495,7 @@ func (s *Server) loginHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.SetCookie(w, &http.Cookie{Name: "session_token", Value: token, Expires: time.Now().Add(s.cfg.SessionTTL), Path: "/", Secure: true, HttpOnly: true, SameSite: http.SameSiteStrictMode})
|
http.SetCookie(w, &http.Cookie{Name: "session_token", Value: token, Expires: time.Now().Add(s.cfg.SessionTTL), Path: "/", Secure: true, HttpOnly: true, SameSite: http.SameSiteStrictMode})
|
||||||
fmt.Fprintln(w, "ok")
|
http.Redirect(w, r, "/", http.StatusMovedPermanently)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) withAuth(next http.Handler) http.Handler {
|
func (s *Server) withAuth(next http.Handler) http.Handler {
|
||||||
|
@@ -12,7 +12,7 @@
|
|||||||
</colgroup>
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name <a href="/sso">(Anmelden)</a></th>
|
||||||
<th>Telefon</th>
|
<th>Telefon</th>
|
||||||
<th>Mobil</th>
|
<th>Mobil</th>
|
||||||
<th>Homeoffice</th>
|
<th>Homeoffice</th>
|
||||||
@@ -22,15 +22,15 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ range .Kontakte }}
|
{{ range .Contacts }}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ .Name }}</td>
|
<td>{{ if .DisplayName }}{{ .DisplayName }}{{ end }}</td>
|
||||||
<td>{{ .Telefon }}</td>
|
<td>{{ if .Phone }}{{ .Phone }}{{ end }}</td>
|
||||||
<td>{{ .Mobil }}</td>
|
<td>{{ if .Mobile }}{{ .Mobile }}{{ end }}</td>
|
||||||
<td>{{ .Homeoffice }}</td>
|
<td>{{ if .Homeoffice }}{{ .Homeoffice }}{{ end }}</td>
|
||||||
<td>{{ .Amt }}</td>
|
<td>{{ if .DepartmentId.Valid }}{{ .DepartmentId.Int64 }}{{ end }}</td>
|
||||||
<td>{{ .Raum }}</td>
|
<td>{{ if .Room }}{{ .Room }}{{ end }}</td>
|
||||||
<td>{{ .Gebaeude }}</td>
|
<td>{{ if .LocationId.Valid }}{{ .LocationId.Int64 }}{{ end }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
149
static/templates/login.html
Normal file
149
static/templates/login.html
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
{{ define "sso" }}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Login</title>
|
||||||
|
|
||||||
|
<!-- Dein zentrales styles.css mit den Variablen einbinden -->
|
||||||
|
<link rel="stylesheet" href="/static/css/main.css" />
|
||||||
|
|
||||||
|
<!-- ▸ Zusätzliche Regeln NUR für das Log-In-Formular -->
|
||||||
|
<style>
|
||||||
|
/* Vollflächig zentrieren */
|
||||||
|
.login-wrapper{
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
height:100vh; /* immer Bildschirmhöhe */
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Die Karte selbst */
|
||||||
|
.login-card{
|
||||||
|
background:var(--card-bg);
|
||||||
|
padding:2.5rem 2rem;
|
||||||
|
border-radius:calc(var(--radius)*2);
|
||||||
|
box-shadow:var(--shadow);
|
||||||
|
width:100%;
|
||||||
|
max-width:400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card h1{
|
||||||
|
font-size:1.5rem;
|
||||||
|
font-weight:600;
|
||||||
|
margin-bottom:1.5rem;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.InputBox {
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
/* Formular-Layout */
|
||||||
|
.login-card form{
|
||||||
|
display:grid;
|
||||||
|
gap:1rem;
|
||||||
|
justify-items:center; /* horizontale Zentrierung für alle Kinder */
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card label{
|
||||||
|
font-size:0.875rem;
|
||||||
|
text-align:center;
|
||||||
|
color:var(--text-muted);
|
||||||
|
margin-bottom:0.25rem;
|
||||||
|
text-align:center; /* Label-Text mittig */
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card input{
|
||||||
|
padding:0.75rem 1rem;
|
||||||
|
border:1px solid var(--code-border);
|
||||||
|
border-radius:var(--radius);
|
||||||
|
background:var(--code-bg);
|
||||||
|
color: var(--text);
|
||||||
|
font-family:"Inter",system-ui,sans-serif;
|
||||||
|
transition:border-color .2s ease;
|
||||||
|
width:100%; /* auf kleineren Screens vollbreit … */
|
||||||
|
/*max-width:280px;*/ /* … aber nie breiter als 280 px */
|
||||||
|
margin:0 auto; /* Input-Box selbst zentrieren */
|
||||||
|
}
|
||||||
|
.login-card input:focus{
|
||||||
|
border-color:var(--accent);
|
||||||
|
outline:none;
|
||||||
|
color: var(--text);
|
||||||
|
box-shadow:0 0 0 2px color-mix(in srgb,var(--accent) 40%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Primär-Button */
|
||||||
|
.login-card button{
|
||||||
|
padding:0.75rem 1rem;
|
||||||
|
border:none;
|
||||||
|
border-radius:var(--radius);
|
||||||
|
background:var(--accent);
|
||||||
|
color:#fff;
|
||||||
|
font-weight:600;
|
||||||
|
cursor:pointer;
|
||||||
|
transition:background .2s ease, transform .1s ease;
|
||||||
|
max-width:280px; /* Button bündelt sich an der Input-Breite */
|
||||||
|
width:100%;
|
||||||
|
margin:0.5rem auto 0;
|
||||||
|
}
|
||||||
|
.login-card button:hover{
|
||||||
|
background:var(--accent-light);
|
||||||
|
}
|
||||||
|
.login-card button:active{
|
||||||
|
transform:translateY(1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* kleine Links unten */
|
||||||
|
.login-card .links{
|
||||||
|
margin-top:1rem;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
.login-card .links a{
|
||||||
|
color:var(--accent);
|
||||||
|
font-size:0.875rem;
|
||||||
|
text-decoration:none;
|
||||||
|
}
|
||||||
|
.login-card .links a:hover{
|
||||||
|
text-decoration:underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-wrapper">
|
||||||
|
<div class="login-card">
|
||||||
|
<h1>Anmelden</h1>
|
||||||
|
|
||||||
|
<form action="/login" method="post">
|
||||||
|
<div class="InputBox">
|
||||||
|
<label for="username">Benutzername</label>
|
||||||
|
<input type="text"
|
||||||
|
id="email"
|
||||||
|
name="username"
|
||||||
|
placeholder="Benutzername"
|
||||||
|
autocomplete="Benutzername"
|
||||||
|
required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="InputBox">
|
||||||
|
<label for="password">Passwort</label>
|
||||||
|
<input type="password"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="••••••••"
|
||||||
|
autocomplete="current-password"
|
||||||
|
required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit">Einloggen</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="links">
|
||||||
|
<a href="/forgot-password">Passwort vergessen?</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{{ end }}
|
@@ -10,7 +10,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ range .Kontakte }}
|
{{ range .Keywords }}
|
||||||
<tr><td>{{ .Name }}</td></tr>
|
<tr><td>{{ .Name }}</td></tr>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
Reference in New Issue
Block a user