Flash poruke: toast notifikacije umesto ?greska= i ?sacuvano= u URL-u
This commit is contained in:
+110
-79
@@ -15,8 +15,6 @@ import (
|
|||||||
type podaciAdminKorisnici struct {
|
type podaciAdminKorisnici struct {
|
||||||
model.PodaciStranice
|
model.PodaciStranice
|
||||||
Korisnici []model.Korisnik
|
Korisnici []model.Korisnik
|
||||||
Greska string
|
|
||||||
Sacuvano bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type podaciLoginIstorija struct {
|
type podaciLoginIstorija struct {
|
||||||
@@ -27,8 +25,8 @@ type podaciLoginIstorija struct {
|
|||||||
|
|
||||||
type podaciAdminProfil struct {
|
type podaciAdminProfil struct {
|
||||||
model.PodaciStranice
|
model.PodaciStranice
|
||||||
|
// Greska se koristi samo za inline prikaz greške pri TOTP aktivaciji (bez redirecta)
|
||||||
Greska string
|
Greska string
|
||||||
Sacuvano string
|
|
||||||
TotpURI string
|
TotpURI string
|
||||||
TotpTajna string
|
TotpTajna string
|
||||||
TotpQR template.URL
|
TotpQR template.URL
|
||||||
@@ -65,14 +63,10 @@ func (h *Handler) AdminKorisnici(w http.ResponseWriter, r *http.Request) {
|
|||||||
ps.Stranica = "admin"
|
ps.Stranica = "admin"
|
||||||
ps.NaslovStranice = "Korisnici"
|
ps.NaslovStranice = "Korisnici"
|
||||||
|
|
||||||
podaci := podaciAdminKorisnici{
|
h.renderujTemplate(w, "admin_korisnici", podaciAdminKorisnici{
|
||||||
PodaciStranice: ps,
|
PodaciStranice: ps,
|
||||||
Korisnici: lista,
|
Korisnici: lista,
|
||||||
Greska: r.URL.Query().Get("greska"),
|
})
|
||||||
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
|
|
||||||
}
|
|
||||||
|
|
||||||
h.renderujTemplate(w, "admin_korisnici", podaci)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminSacuvajKorisnika kreira novog korisnika
|
// AdminSacuvajKorisnika kreira novog korisnika
|
||||||
@@ -84,7 +78,8 @@ func (h *Handler) AdminSacuvajKorisnika(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,22 +90,26 @@ func (h *Handler) AdminSacuvajKorisnika(w http.ResponseWriter, r *http.Request)
|
|||||||
// superadmin uloga se ne može kreirati kroz interfejs — jedini superadmin postoji od setup-a
|
// superadmin uloga se ne može kreirati kroz interfejs — jedini superadmin postoji od setup-a
|
||||||
validneUloge := map[string]bool{"admin": true, "radnik": true}
|
validneUloge := map[string]bool{"admin": true, "radnik": true}
|
||||||
if len(ime) < 3 || len(lozinka) < 8 || !validneUloge[uloga] {
|
if len(ime) < 3 || len(lozinka) < 8 || !validneUloge[uloga] {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, err := auth.HashujLozinku(lozinka)
|
hash, err := auth.HashujLozinku(lozinka)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := h.KorisniciRepo.Kreiraj(r.Context(), ime, hash, uloga); err != nil {
|
if _, err := h.KorisniciRepo.Kreiraj(r.Context(), ime, hash, uloga); err != nil {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/admin/korisnici?sacuvano=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "uspeh", "Korisnik je uspešno kreiran.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminToggleAktivan menja aktivan status korisnika
|
// AdminToggleAktivan menja aktivan status korisnika
|
||||||
@@ -123,34 +122,40 @@ func (h *Handler) AdminToggleAktivan(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
id, err := parseID(chi.URLParam(r, "id"))
|
id, err := parseID(chi.URLParam(r, "id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ne sme deaktivirati sam sebe
|
// ne sme deaktivirati sam sebe
|
||||||
if id == k.ID {
|
if id == k.ID {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Ne možete promeniti status sopstvenog naloga.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
korisnik, err := h.KorisniciRepo.DohvatiPoID(r.Context(), id)
|
korisnik, err := h.KorisniciRepo.DohvatiPoID(r.Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// admin ne sme da menja status drugog admina ni superadmina
|
// admin ne sme da menja status drugog admina ni superadmina
|
||||||
if korisnik.Uloga == "superadmin" || (korisnik.Uloga == "admin" && k.Uloga != "superadmin") {
|
if korisnik.Uloga == "superadmin" || (korisnik.Uloga == "admin" && k.Uloga != "superadmin") {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=3", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Ova radnja nije dozvoljena.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.KorisniciRepo.AzurirajAktivan(r.Context(), id, !korisnik.Aktivan); err != nil {
|
if err := h.KorisniciRepo.AzurirajAktivan(r.Context(), id, !korisnik.Aktivan); err != nil {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/admin/korisnici?sacuvano=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "uspeh", "Promene su uspešno sačuvane.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminPromeniUlogu menja ulogu korisnika
|
// AdminPromeniUlogu menja ulogu korisnika
|
||||||
@@ -163,18 +168,21 @@ func (h *Handler) AdminPromeniUlogu(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
id, err := parseID(chi.URLParam(r, "id"))
|
id, err := parseID(chi.URLParam(r, "id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// superadmin ne može menjati svoju vlastitu ulogu
|
// superadmin ne može menjati svoju vlastitu ulogu
|
||||||
if id == k.ID {
|
if id == k.ID {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=3", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Ova radnja nije dozvoljena.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,29 +190,34 @@ func (h *Handler) AdminPromeniUlogu(w http.ResponseWriter, r *http.Request) {
|
|||||||
// dozvoljena je samo promena između admin i radnik; superadmin uloga se ne može dodeliti ni ukloniti
|
// dozvoljena je samo promena između admin i radnik; superadmin uloga se ne može dodeliti ni ukloniti
|
||||||
validneUloge := map[string]bool{"admin": true, "radnik": true}
|
validneUloge := map[string]bool{"admin": true, "radnik": true}
|
||||||
if !validneUloge[uloga] {
|
if !validneUloge[uloga] {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// dohvati korisnika da proverimo njegovu trenutnu ulogu
|
// dohvati korisnika da proverimo njegovu trenutnu ulogu
|
||||||
ciljniKorisnik, err := h.KorisniciRepo.DohvatiPoID(r.Context(), id)
|
ciljniKorisnik, err := h.KorisniciRepo.DohvatiPoID(r.Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// superadmin uloga se ne može menjati
|
// superadmin uloga se ne može menjati
|
||||||
if ciljniKorisnik.Uloga == "superadmin" {
|
if ciljniKorisnik.Uloga == "superadmin" {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=3", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Ova radnja nije dozvoljena.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.KorisniciRepo.AzurirajUlogu(r.Context(), id, uloga); err != nil {
|
if err := h.KorisniciRepo.AzurirajUlogu(r.Context(), id, uloga); err != nil {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/admin/korisnici?sacuvano=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "uspeh", "Promene su uspešno sačuvane.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminObrisiKorisnika briše korisnika sa ulogom radnik
|
// AdminObrisiKorisnika briše korisnika sa ulogom radnik
|
||||||
@@ -217,33 +230,39 @@ func (h *Handler) AdminObrisiKorisnika(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
id, err := parseID(chi.URLParam(r, "id"))
|
id, err := parseID(chi.URLParam(r, "id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if id == k.ID {
|
if id == k.ID {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=3", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Ova radnja nije dozvoljena.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ciljni, err := h.KorisniciRepo.DohvatiPoID(r.Context(), id)
|
ciljni, err := h.KorisniciRepo.DohvatiPoID(r.Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// dozvoljeno je brisanje samo radnika
|
// dozvoljeno je brisanje samo radnika
|
||||||
if ciljni.Uloga != "radnik" {
|
if ciljni.Uloga != "radnik" {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=3", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Ova radnja nije dozvoljena.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.KorisniciRepo.Obrisi(r.Context(), id); err != nil {
|
if err := h.KorisniciRepo.Obrisi(r.Context(), id); err != nil {
|
||||||
http.Redirect(w, r, "/admin/korisnici?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/admin/korisnici?sacuvano=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "uspeh", "Korisnik je obrisan.")
|
||||||
|
http.Redirect(w, r, "/admin/korisnici", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminProfil prikazuje stranicu profila
|
// AdminProfil prikazuje stranicu profila
|
||||||
@@ -265,14 +284,10 @@ func (h *Handler) AdminProfil(w http.ResponseWriter, r *http.Request) {
|
|||||||
ps.Stranica = "profil"
|
ps.Stranica = "profil"
|
||||||
ps.NaslovStranice = "Moj profil"
|
ps.NaslovStranice = "Moj profil"
|
||||||
|
|
||||||
podaci := podaciAdminProfil{
|
h.renderujTemplate(w, "admin_profil", podaciAdminProfil{
|
||||||
PodaciStranice: ps,
|
PodaciStranice: ps,
|
||||||
Greska: r.URL.Query().Get("greska"),
|
|
||||||
Sacuvano: r.URL.Query().Get("sacuvano"),
|
|
||||||
TotpAktivan: svezi.TotpTajna != "",
|
TotpAktivan: svezi.TotpTajna != "",
|
||||||
}
|
})
|
||||||
|
|
||||||
h.renderujTemplate(w, "admin_profil", podaci)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminPromeniLozinku menja lozinku prijavljenog korisnika
|
// AdminPromeniLozinku menja lozinku prijavljenog korisnika
|
||||||
@@ -284,7 +299,8 @@ func (h *Handler) AdminPromeniLozinku(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
http.Redirect(w, r, "/admin/profil?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,26 +309,31 @@ func (h *Handler) AdminPromeniLozinku(w http.ResponseWriter, r *http.Request) {
|
|||||||
potvrda := r.FormValue("nova_lozinka_potvrda")
|
potvrda := r.FormValue("nova_lozinka_potvrda")
|
||||||
|
|
||||||
if !auth.ProveriLozinku(k.LozinkaHash, stara) {
|
if !auth.ProveriLozinku(k.LozinkaHash, stara) {
|
||||||
http.Redirect(w, r, "/admin/profil?greska=lozinka", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Stara lozinka nije ispravna.")
|
||||||
|
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(nova) < 8 || nova != potvrda {
|
if len(nova) < 8 || nova != potvrda {
|
||||||
http.Redirect(w, r, "/admin/profil?greska=lozinka2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Nova lozinka mora imati najmanje 8 karaktera i lozinke moraju biti iste.")
|
||||||
|
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, err := auth.HashujLozinku(nova)
|
hash, err := auth.HashujLozinku(nova)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/admin/profil?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.KorisniciRepo.PromeniLozinku(r.Context(), k.ID, hash); err != nil {
|
if err := h.KorisniciRepo.PromeniLozinku(r.Context(), k.ID, hash); err != nil {
|
||||||
http.Redirect(w, r, "/admin/profil?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/admin/profil?sacuvano=lozinka", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "uspeh", "Lozinka je uspešno promenjena.")
|
||||||
|
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminTotpPokreni generiše TOTP tajnu i prikazuje QR kod
|
// AdminTotpPokreni generiše TOTP tajnu i prikazuje QR kod
|
||||||
@@ -326,7 +347,8 @@ func (h *Handler) AdminTotpPokreni(w http.ResponseWriter, r *http.Request) {
|
|||||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
totp, err := auth.GenerisuTotpTajnu(k.KorisnickoIme, podesavanja["naziv_firme"])
|
totp, err := auth.GenerisuTotpTajnu(k.KorisnickoIme, podesavanja["naziv_firme"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/admin/profil?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri generisanju 2FA. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,14 +356,12 @@ func (h *Handler) AdminTotpPokreni(w http.ResponseWriter, r *http.Request) {
|
|||||||
ps.Stranica = "profil"
|
ps.Stranica = "profil"
|
||||||
ps.NaslovStranice = "Podesi 2FA"
|
ps.NaslovStranice = "Podesi 2FA"
|
||||||
|
|
||||||
podaci := podaciAdminProfil{
|
h.renderujTemplate(w, "admin_profil", podaciAdminProfil{
|
||||||
PodaciStranice: ps,
|
PodaciStranice: ps,
|
||||||
TotpURI: totp.URI,
|
TotpURI: totp.URI,
|
||||||
TotpTajna: totp.Tajna,
|
TotpTajna: totp.Tajna,
|
||||||
TotpQR: template.URL("data:image/png;base64," + totp.QRBase64),
|
TotpQR: template.URL("data:image/png;base64," + totp.QRBase64),
|
||||||
}
|
})
|
||||||
|
|
||||||
h.renderujTemplate(w, "admin_profil", podaci)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminTotpAktivacija verifikuje TOTP kod i čuva tajnu
|
// AdminTotpAktivacija verifikuje TOTP kod i čuva tajnu
|
||||||
@@ -353,7 +373,8 @@ func (h *Handler) AdminTotpAktivacija(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
http.Redirect(w, r, "/admin/profil?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,36 +382,36 @@ func (h *Handler) AdminTotpAktivacija(w http.ResponseWriter, r *http.Request) {
|
|||||||
kod := r.FormValue("kod")
|
kod := r.FormValue("kod")
|
||||||
|
|
||||||
if !auth.VerifikujTotpKod(kod, tajna) {
|
if !auth.VerifikujTotpKod(kod, tajna) {
|
||||||
// ponovni prikaz sa greškom — regenerišemo isti tajnu
|
// ponovni prikaz sa greškom — regenerišemo isti QR, ne radimo redirect
|
||||||
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
podesavanja, _ := sqlite.DohvatiSvaPodesavanja(r.Context(), h.DB)
|
||||||
ps := h.popuniPodaciStranice(r, podesavanja)
|
ps := h.popuniPodaciStranice(r, podesavanja)
|
||||||
ps.Stranica = "profil"
|
ps.Stranica = "profil"
|
||||||
ps.NaslovStranice = "Podesi 2FA"
|
ps.NaslovStranice = "Podesi 2FA"
|
||||||
|
|
||||||
// regenerišemo QR za već generisanu tajnu (korisnik je video ovaj QR)
|
|
||||||
nazivFirme := podesavanja["naziv_firme"]
|
nazivFirme := podesavanja["naziv_firme"]
|
||||||
if nazivFirme == "" {
|
if nazivFirme == "" {
|
||||||
nazivFirme = "NTech"
|
nazivFirme = "NTech"
|
||||||
}
|
}
|
||||||
uri, qr := auth.RegenerisiTotpQR(tajna, k.KorisnickoIme, nazivFirme)
|
uri, qr := auth.RegenerisiTotpQR(tajna, k.KorisnickoIme, nazivFirme)
|
||||||
|
|
||||||
podaci := podaciAdminProfil{
|
h.renderujTemplate(w, "admin_profil", podaciAdminProfil{
|
||||||
PodaciStranice: ps,
|
PodaciStranice: ps,
|
||||||
TotpURI: uri,
|
TotpURI: uri,
|
||||||
TotpTajna: tajna,
|
TotpTajna: tajna,
|
||||||
TotpQR: template.URL("data:image/png;base64," + qr),
|
TotpQR: template.URL("data:image/png;base64," + qr),
|
||||||
Greska: "totp",
|
Greska: "totp",
|
||||||
}
|
})
|
||||||
h.renderujTemplate(w, "admin_profil", podaci)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.KorisniciRepo.SacuvajTotpTajnu(r.Context(), k.ID, tajna); err != nil {
|
if err := h.KorisniciRepo.SacuvajTotpTajnu(r.Context(), k.ID, tajna); err != nil {
|
||||||
http.Redirect(w, r, "/admin/profil?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/admin/profil?sacuvano=totp", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "uspeh", "Dvostepena verifikacija je uspešno uključena.")
|
||||||
|
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminTotpDeaktivacija uklanja TOTP tajnu
|
// AdminTotpDeaktivacija uklanja TOTP tajnu
|
||||||
@@ -402,11 +423,13 @@ func (h *Handler) AdminTotpDeaktivacija(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := h.KorisniciRepo.SacuvajTotpTajnu(r.Context(), k.ID, ""); err != nil {
|
if err := h.KorisniciRepo.SacuvajTotpTajnu(r.Context(), k.ID, ""); err != nil {
|
||||||
http.Redirect(w, r, "/admin/profil?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/admin/profil?sacuvano=totp_off", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "uspeh", "Dvostepena verifikacija je isključena.")
|
||||||
|
http.Redirect(w, r, "/admin/profil", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminLoginIstorija prikazuje evidenciju prijava za datog korisnika
|
// AdminLoginIstorija prikazuje evidenciju prijava za datog korisnika
|
||||||
@@ -460,8 +483,6 @@ type podaciAdminDozvole struct {
|
|||||||
DozvoleRadnik map[string]bool
|
DozvoleRadnik map[string]bool
|
||||||
DozvoleAdmin map[string]bool
|
DozvoleAdmin map[string]bool
|
||||||
DozvoleSuperadmin map[string]bool
|
DozvoleSuperadmin map[string]bool
|
||||||
Greska string
|
|
||||||
Sacuvano bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminDozvole prikazuje stranicu za upravljanje ulogama i pregled dozvola
|
// AdminDozvole prikazuje stranicu za upravljanje ulogama i pregled dozvola
|
||||||
@@ -490,8 +511,6 @@ func (h *Handler) AdminDozvole(w http.ResponseWriter, r *http.Request) {
|
|||||||
DozvoleRadnik: h.DozvoleRepo.SveDozvole(r.Context(), "radnik"),
|
DozvoleRadnik: h.DozvoleRepo.SveDozvole(r.Context(), "radnik"),
|
||||||
DozvoleAdmin: h.DozvoleRepo.SveDozvole(r.Context(), "admin"),
|
DozvoleAdmin: h.DozvoleRepo.SveDozvole(r.Context(), "admin"),
|
||||||
DozvoleSuperadmin: h.DozvoleRepo.SveDozvole(r.Context(), "superadmin"),
|
DozvoleSuperadmin: h.DozvoleRepo.SveDozvole(r.Context(), "superadmin"),
|
||||||
Greska: r.URL.Query().Get("greska"),
|
|
||||||
Sacuvano: r.URL.Query().Get("sacuvano") == "1",
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,18 +524,21 @@ func (h *Handler) AdminDozvolePromeniUlogu(w http.ResponseWriter, r *http.Reques
|
|||||||
|
|
||||||
id, err := parseID(chi.URLParam(r, "id"))
|
id, err := parseID(chi.URLParam(r, "id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/admin/dozvole?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
|
||||||
|
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
http.Redirect(w, r, "/admin/dozvole?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
|
||||||
|
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// superadmin ne može menjati svoju vlastitu ulogu
|
// superadmin ne može menjati svoju vlastitu ulogu
|
||||||
if id == k.ID {
|
if id == k.ID {
|
||||||
http.Redirect(w, r, "/admin/dozvole?greska=3", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Ova radnja nije dozvoljena.")
|
||||||
|
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,28 +546,33 @@ func (h *Handler) AdminDozvolePromeniUlogu(w http.ResponseWriter, r *http.Reques
|
|||||||
// superadmin uloga se ne može dodeliti kroz interfejs
|
// superadmin uloga se ne može dodeliti kroz interfejs
|
||||||
validneUloge := map[string]bool{"admin": true, "radnik": true}
|
validneUloge := map[string]bool{"admin": true, "radnik": true}
|
||||||
if !validneUloge[uloga] {
|
if !validneUloge[uloga] {
|
||||||
http.Redirect(w, r, "/admin/dozvole?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
|
||||||
|
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ciljni, err := h.KorisniciRepo.DohvatiPoID(r.Context(), id)
|
ciljni, err := h.KorisniciRepo.DohvatiPoID(r.Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Redirect(w, r, "/admin/dozvole?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// superadmin uloga se ne može menjati
|
// superadmin uloga se ne može menjati
|
||||||
if ciljni.Uloga == "superadmin" {
|
if ciljni.Uloga == "superadmin" {
|
||||||
http.Redirect(w, r, "/admin/dozvole?greska=3", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Ova radnja nije dozvoljena.")
|
||||||
|
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.KorisniciRepo.AzurirajUlogu(r.Context(), id, uloga); err != nil {
|
if err := h.KorisniciRepo.AzurirajUlogu(r.Context(), id, uloga); err != nil {
|
||||||
http.Redirect(w, r, "/admin/dozvole?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/admin/dozvole?sacuvano=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "uspeh", "Promene su uspešno sačuvane.")
|
||||||
|
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminDozvoleSacuvaj prima POST i čuva promene dozvola za radnik i admin uloge
|
// AdminDozvoleSacuvaj prima POST i čuva promene dozvola za radnik i admin uloge
|
||||||
@@ -556,7 +583,8 @@ func (h *Handler) AdminDozvoleSacuvaj(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
http.Redirect(w, r, "/admin/dozvole?greska=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Proverite unete podatke.")
|
||||||
|
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// čuvamo dozvole samo za radnik i admin — superadmin uvek ima sve
|
// čuvamo dozvole samo za radnik i admin — superadmin uvek ima sve
|
||||||
@@ -565,12 +593,14 @@ func (h *Handler) AdminDozvoleSacuvaj(w http.ResponseWriter, r *http.Request) {
|
|||||||
kljuc := uloga + "__" + akcija
|
kljuc := uloga + "__" + akcija
|
||||||
dozvoljeno := r.FormValue(kljuc) == "on"
|
dozvoljeno := r.FormValue(kljuc) == "on"
|
||||||
if err := h.DozvoleRepo.Sacuvaj(r.Context(), uloga, akcija, dozvoljeno); err != nil {
|
if err := h.DozvoleRepo.Sacuvaj(r.Context(), uloga, akcija, dozvoljeno); err != nil {
|
||||||
http.Redirect(w, r, "/admin/dozvole?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
http.Redirect(w, r, "/admin/dozvole?sacuvano=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "uspeh", "Promene su uspešno sačuvane.")
|
||||||
|
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminDozvoleReset vraća sve dozvole na podrazumevane vrednosti — samo superadmin
|
// AdminDozvoleReset vraća sve dozvole na podrazumevane vrednosti — samo superadmin
|
||||||
@@ -581,9 +611,10 @@ func (h *Handler) AdminDozvoleReset(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := h.DozvoleRepo.Reset(r.Context()); err != nil {
|
if err := h.DozvoleRepo.Reset(r.Context()); err != nil {
|
||||||
http.Redirect(w, r, "/admin/dozvole?greska=2", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "greska", "Greška pri čuvanju. Pokušajte ponovo.")
|
||||||
|
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Redirect(w, r, "/admin/dozvole?sacuvano=1", http.StatusSeeOther)
|
middleware.SetFlash(w, r, h.DB, "uspeh", "Dozvole su vraćene na podrazumevane vrednosti.")
|
||||||
|
http.Redirect(w, r, "/admin/dozvole", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,5 +89,6 @@ func (h *Handler) popuniPodaciStranice(r *http.Request, podesavanja map[string]s
|
|||||||
ps.Dozvole = h.DozvoleRepo.SveDozvole(r.Context(), k.Uloga)
|
ps.Dozvole = h.DozvoleRepo.SveDozvole(r.Context(), k.Uloga)
|
||||||
}
|
}
|
||||||
ps.CsrfToken = middleware.CsrfToken(r.Context())
|
ps.CsrfToken = middleware.CsrfToken(r.Context())
|
||||||
|
ps.Flash = middleware.GetFlash(r, h.DB)
|
||||||
return ps
|
return ps
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"ntech/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetFlash čuva flash poruku u koloni flash aktivne sesije
|
||||||
|
func SetFlash(w http.ResponseWriter, r *http.Request, db *sql.DB, tip, poruka string) {
|
||||||
|
kolacic, err := r.Cookie("ntech_sesija")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(model.FlashPoruka{Tip: tip, Poruka: poruka})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db.ExecContext(r.Context(),
|
||||||
|
`UPDATE sesije SET flash = ? WHERE token = ?`,
|
||||||
|
string(data), kolacic.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFlash čita i atomično briše flash poruku iz aktivne sesije
|
||||||
|
func GetFlash(r *http.Request, db *sql.DB) *model.FlashPoruka {
|
||||||
|
kolacic, err := r.Cookie("ntech_sesija")
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var flashJSON sql.NullString
|
||||||
|
if err := db.QueryRowContext(r.Context(),
|
||||||
|
`SELECT flash FROM sesije WHERE token = ?`, kolacic.Value).Scan(&flashJSON); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !flashJSON.Valid || flashJSON.String == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// briše pre parsiranja — ako parsiranje ne uspe, poruka se svakako ne prikazuje
|
||||||
|
db.ExecContext(r.Context(),
|
||||||
|
`UPDATE sesije SET flash = NULL WHERE token = ?`, kolacic.Value)
|
||||||
|
var f model.FlashPoruka
|
||||||
|
if err := json.Unmarshal([]byte(flashJSON.String), &f); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &f
|
||||||
|
}
|
||||||
@@ -23,6 +23,12 @@ type StavkaProdajePregled struct {
|
|||||||
Datum string // kratki format, npr. "01.06."
|
Datum string // kratki format, npr. "01.06."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FlashPoruka je jednokratna poruka koja se prikazuje korisniku nakon redirecta
|
||||||
|
type FlashPoruka struct {
|
||||||
|
Tip string // "uspeh" ili "greska"
|
||||||
|
Poruka string
|
||||||
|
}
|
||||||
|
|
||||||
// PodaciStranice su zajednički podaci koje svaka stranica prima
|
// PodaciStranice su zajednički podaci koje svaka stranica prima
|
||||||
type PodaciStranice struct {
|
type PodaciStranice struct {
|
||||||
Stranica string
|
Stranica string
|
||||||
@@ -37,6 +43,7 @@ type PodaciStranice struct {
|
|||||||
KorisnikUloga string // uloga: "superadmin", "admin", "radnik"
|
KorisnikUloga string // uloga: "superadmin", "admin", "radnik"
|
||||||
CsrfToken string // CSRF zaštitni token za forme
|
CsrfToken string // CSRF zaštitni token za forme
|
||||||
Dozvole map[string]bool // mapa akcija → dozvoljeno/nije
|
Dozvole map[string]bool // mapa akcija → dozvoljeno/nije
|
||||||
|
Flash *FlashPoruka // jednokratna poruka nakon redirecta
|
||||||
}
|
}
|
||||||
|
|
||||||
// PodaciDashboarda su podaci specifični za dashboard stranicu
|
// PodaciDashboarda su podaci specifični za dashboard stranicu
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE sesije ADD COLUMN flash TEXT;
|
||||||
@@ -583,3 +583,80 @@ select {
|
|||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* flash toast — jednokratna poruka u gornjem desnom uglu */
|
||||||
|
@keyframes flashUlaz {
|
||||||
|
from { opacity: 0; transform: translateX(20px); }
|
||||||
|
to { opacity: 1; transform: translateX(0); }
|
||||||
|
}
|
||||||
|
@keyframes flashIzlaz {
|
||||||
|
from { opacity: 1; transform: translateX(0); }
|
||||||
|
to { opacity: 0; transform: translateX(20px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
max-width: 380px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
|
||||||
|
animation: flashUlaz 0.3s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-toast.flash-izlaz {
|
||||||
|
animation: flashIzlaz 0.35s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-toast.flash-uspeh {
|
||||||
|
background: #f0fdf4;
|
||||||
|
color: #15803d;
|
||||||
|
border: 0.5px solid #bbf7d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-toast.flash-greska {
|
||||||
|
background: #fef2f2;
|
||||||
|
color: #dc2626;
|
||||||
|
border: 0.5px solid #fecaca;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-ikona {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-tekst {
|
||||||
|
flex: 1;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-zatvori {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1;
|
||||||
|
color: inherit;
|
||||||
|
opacity: 0.6;
|
||||||
|
padding: 0 0 0 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flash-zatvori:hover { opacity: 1; }
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.flash-toast {
|
||||||
|
top: auto;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 12px;
|
||||||
|
left: 12px;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,13 +30,6 @@
|
|||||||
{{define "sadrzaj"}}
|
{{define "sadrzaj"}}
|
||||||
<div style="display:flex;flex-direction:column;gap:20px;">
|
<div style="display:flex;flex-direction:column;gap:20px;">
|
||||||
|
|
||||||
{{if .Sacuvano}}
|
|
||||||
<div class="poruka-uspeh">Promene su uspešno sačuvane.</div>
|
|
||||||
{{end}}
|
|
||||||
{{if .Greska}}
|
|
||||||
<div class="poruka-greska">Greška pri čuvanju promena.</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<!-- matrica dozvola — editabilna forma -->
|
<!-- matrica dozvola — editabilna forma -->
|
||||||
<div class="kartica animiraj" style="padding:0;overflow:hidden;">
|
<div class="kartica animiraj" style="padding:0;overflow:hidden;">
|
||||||
<div style="padding:14px 16px;border-bottom:0.5px solid var(--ivica);">
|
<div style="padding:14px 16px;border-bottom:0.5px solid var(--ivica);">
|
||||||
|
|||||||
@@ -26,18 +26,6 @@
|
|||||||
{{define "sadrzaj"}}
|
{{define "sadrzaj"}}
|
||||||
<div style="display:flex;flex-direction:column;gap:16px;">
|
<div style="display:flex;flex-direction:column;gap:16px;">
|
||||||
|
|
||||||
{{if .Sacuvano}}
|
|
||||||
<div class="poruka-uspeh poruka-animacija">Promene su uspešno sačuvane.</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if eq .Greska "1"}}
|
|
||||||
<div class="poruka-greska">Proverite unete podatke.</div>
|
|
||||||
{{else if eq .Greska "2"}}
|
|
||||||
<div class="poruka-greska">Greška pri čuvanju. Pokušajte ponovo.</div>
|
|
||||||
{{else if eq .Greska "3"}}
|
|
||||||
<div class="poruka-greska">Ova radnja nije dozvoljena.</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<!-- lista korisnika -->
|
<!-- lista korisnika -->
|
||||||
<div class="kartica animiraj" style="padding:0;overflow:hidden;">
|
<div class="kartica animiraj" style="padding:0;overflow:hidden;">
|
||||||
<div style="padding:16px 20px;border-bottom:0.5px solid var(--ivica);">
|
<div style="padding:16px 20px;border-bottom:0.5px solid var(--ivica);">
|
||||||
|
|||||||
@@ -14,26 +14,12 @@
|
|||||||
{{define "sadrzaj"}}
|
{{define "sadrzaj"}}
|
||||||
<div style="display:flex;flex-direction:column;gap:16px;max-width:560px;">
|
<div style="display:flex;flex-direction:column;gap:16px;max-width:560px;">
|
||||||
|
|
||||||
{{if eq .Sacuvano "lozinka"}}
|
|
||||||
<div class="poruka-uspeh">Lozinka je uspešno promenjena.</div>
|
|
||||||
{{else if eq .Sacuvano "totp"}}
|
|
||||||
<div class="poruka-uspeh">Dvostepena verifikacija je uspešno uključena.</div>
|
|
||||||
{{else if eq .Sacuvano "totp_off"}}
|
|
||||||
<div class="poruka-uspeh">Dvostepena verifikacija je isključena.</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<!-- promena lozinke -->
|
<!-- promena lozinke -->
|
||||||
<div class="kartica animiraj">
|
<div class="kartica animiraj">
|
||||||
<div style="font-size:15px;font-weight:500;color:var(--tekst-glavni);margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
|
<div style="font-size:15px;font-weight:500;color:var(--tekst-glavni);margin-bottom:16px;padding-bottom:12px;border-bottom:0.5px solid var(--ivica);">
|
||||||
Promena lozinke
|
Promena lozinke
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if eq .Greska "lozinka"}}
|
|
||||||
<div class="poruka-greska" style="margin-bottom:14px;">Stara lozinka nije ispravna.</div>
|
|
||||||
{{else if eq .Greska "lozinka2"}}
|
|
||||||
<div class="poruka-greska" style="margin-bottom:14px;">Nova lozinka mora imati najmanje 8 karaktera i lozinke moraju biti iste.</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<form method="POST" action="/admin/profil/lozinka">
|
<form method="POST" action="/admin/profil/lozinka">
|
||||||
<div style="display:flex;flex-direction:column;gap:12px;">
|
<div style="display:flex;flex-direction:column;gap:12px;">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -36,6 +36,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- flash poruka — prikazuje se kao toast u gornjem desnom uglu -->
|
||||||
|
{{if .Flash}}
|
||||||
|
<div id="flash-toast" class="flash-toast flash-{{.Flash.Tip}}" role="alert">
|
||||||
|
<span class="flash-ikona">{{if eq .Flash.Tip "uspeh"}}✓{{else}}!{{end}}</span>
|
||||||
|
<span class="flash-tekst">{{.Flash.Poruka}}</span>
|
||||||
|
<button class="flash-zatvori" onclick="this.parentElement.remove()" aria-label="Zatvori">×</button>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var t = document.getElementById('flash-toast');
|
||||||
|
if (!t) return;
|
||||||
|
setTimeout(function() {
|
||||||
|
t.classList.add('flash-izlaz');
|
||||||
|
setTimeout(function() { if (t.parentElement) t.remove(); }, 350);
|
||||||
|
}, 4000);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<!-- alpine.js za interaktivnost -->
|
<!-- alpine.js za interaktivnost -->
|
||||||
<script
|
<script
|
||||||
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
|
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
|
||||||
|
|||||||
Reference in New Issue
Block a user