forked from Deuxfleurs/guichet
Improve profile editing page & photo uploading
This commit is contained in:
parent
9e3279b9c0
commit
e94bd728ec
7 changed files with 149 additions and 140 deletions
|
@ -24,5 +24,11 @@
|
||||||
"admin_account": "uid=admin,dc=example,dc=com",
|
"admin_account": "uid=admin,dc=example,dc=com",
|
||||||
"group_can_admin": "gid=admin,ou=groups,dc=example,dc=com",
|
"group_can_admin": "gid=admin,ou=groups,dc=example,dc=com",
|
||||||
"group_can_invite": ""
|
"group_can_invite": ""
|
||||||
|
|
||||||
|
"s3_endpoint": "garage.example.com",
|
||||||
|
"s3_access_key": "",
|
||||||
|
"s3_secret_key": "",
|
||||||
|
"s3_region": "garage",
|
||||||
|
"s3_bucket": "bottin-pictures"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
13
directory.go
13
directory.go
|
@ -10,6 +10,9 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const FIELD_NAME_PROFILE_PICTURE = "profilePicture"
|
||||||
|
const FIELD_NAME_DIRECTORY_VISIBILITY = "directoryVisibility"
|
||||||
|
|
||||||
func handleDirectory(w http.ResponseWriter, r *http.Request) {
|
func handleDirectory(w http.ResponseWriter, r *http.Request) {
|
||||||
templateDirectory := template.Must(template.ParseFiles("templates/layout.html", "templates/directory.html"))
|
templateDirectory := template.Must(template.ParseFiles("templates/layout.html", "templates/directory.html"))
|
||||||
|
|
||||||
|
@ -51,8 +54,14 @@ func handleSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
searchRequest := ldap.NewSearchRequest(
|
searchRequest := ldap.NewSearchRequest(
|
||||||
config.UserBaseDN,
|
config.UserBaseDN,
|
||||||
ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
|
ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
"(&(objectclass=organizationalPerson)(visibility=on))",
|
"(&(objectclass=organizationalPerson)("+FIELD_NAME_DIRECTORY_VISIBILITY+"=on))",
|
||||||
[]string{config.UserNameAttr, "displayname", "mail", "description"},
|
[]string{
|
||||||
|
config.UserNameAttr,
|
||||||
|
"displayname",
|
||||||
|
"mail",
|
||||||
|
"description",
|
||||||
|
FIELD_NAME_PROFILE_PICTURE,
|
||||||
|
},
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
sr, err := login.conn.Search(searchRequest)
|
sr, err := login.conn.Search(searchRequest)
|
||||||
|
|
28
main.go
28
main.go
|
@ -44,11 +44,11 @@ type ConfigFile struct {
|
||||||
GroupCanInvite string `json:"group_can_invite"`
|
GroupCanInvite string `json:"group_can_invite"`
|
||||||
GroupCanAdmin string `json:"group_can_admin"`
|
GroupCanAdmin string `json:"group_can_admin"`
|
||||||
|
|
||||||
S3_Endpoint string `json:"s3_endpoint"`
|
S3Endpoint string `json:"s3_endpoint"`
|
||||||
S3_AccesKey string `json:"s3_acces_key"`
|
S3AccessKey string `json:"s3_access_key"`
|
||||||
S3_SecretKey string `json:"s3_secret_key"`
|
S3SecretKey string `json:"s3_secret_key"`
|
||||||
S3_Region string `json:"s3_region"`
|
S3Region string `json:"s3_region"`
|
||||||
S3_Bucket string `json:"s3_bucket"`
|
S3Bucket string `json:"s3_bucket"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var configFlag = flag.String("config", "./config.json", "Configuration file path")
|
var configFlag = flag.String("config", "./config.json", "Configuration file path")
|
||||||
|
@ -110,13 +110,13 @@ func main() {
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/", handleHome)
|
r.HandleFunc("/", handleHome)
|
||||||
r.HandleFunc("/logout", handleLogout)
|
r.HandleFunc("/logout", handleLogout)
|
||||||
|
|
||||||
r.HandleFunc("/profile", handleProfile)
|
r.HandleFunc("/profile", handleProfile)
|
||||||
r.HandleFunc("/passwd", handlePasswd)
|
r.HandleFunc("/passwd", handlePasswd)
|
||||||
|
r.HandleFunc("/picture/{name}", handleDownloadPicture)
|
||||||
r.HandleFunc("/image/{name}/{size}", handleDownloadImage)
|
|
||||||
|
|
||||||
r.HandleFunc("/directory", handleDirectory)
|
r.HandleFunc("/directory", handleDirectory)
|
||||||
r.HandleFunc("/search/{input}", handleSearch)
|
r.HandleFunc("/directory/search/{input}", handleSearch)
|
||||||
|
|
||||||
r.HandleFunc("/invite/new_account", handleInviteNewAccount)
|
r.HandleFunc("/invite/new_account", handleInviteNewAccount)
|
||||||
r.HandleFunc("/invite/send_code", handleInviteSendCode)
|
r.HandleFunc("/invite/send_code", handleInviteSendCode)
|
||||||
|
@ -226,7 +226,17 @@ func checkLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
||||||
login_info.DN,
|
login_info.DN,
|
||||||
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
requestKind,
|
requestKind,
|
||||||
[]string{"dn", "displayname", "givenname", "sn", "mail", "memberof", "visibility", "description", PROFILE_PICTURE_FIELD_NAME},
|
[]string{
|
||||||
|
"dn",
|
||||||
|
"displayname",
|
||||||
|
"givenname",
|
||||||
|
"sn",
|
||||||
|
"mail",
|
||||||
|
"memberof",
|
||||||
|
"description",
|
||||||
|
FIELD_NAME_DIRECTORY_VISIBILITY,
|
||||||
|
FIELD_NAME_PROFILE_PICTURE,
|
||||||
|
},
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
sr, err := l.Search(searchRequest)
|
sr, err := l.Search(searchRequest)
|
||||||
|
|
|
@ -16,7 +16,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-ldap/ldap/v3"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
|
@ -24,10 +23,28 @@ import (
|
||||||
"github.com/nfnt/resize"
|
"github.com/nfnt/resize"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PROFILE_PICTURE_FIELD_NAME = "profilePicture"
|
func newMinioClient() (*minio.Client, error) {
|
||||||
|
endpoint := config.S3Endpoint
|
||||||
|
accessKeyID := config.S3AccessKey
|
||||||
|
secretKeyID := config.S3SecretKey
|
||||||
|
useSSL := true
|
||||||
|
|
||||||
|
//Initialize Minio
|
||||||
|
minioCLient, err := minio.New(endpoint, &minio.Options{
|
||||||
|
Creds: credentials.NewStaticV4(accessKeyID, secretKeyID, ""),
|
||||||
|
Secure: useSSL,
|
||||||
|
Region: config.S3Region,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return minioCLient, nil
|
||||||
|
}
|
||||||
|
|
||||||
//Upload image through guichet server.
|
//Upload image through guichet server.
|
||||||
func uploadImage(w http.ResponseWriter, r *http.Request, login *LoginStatus) (string, error) {
|
func uploadProfilePicture(w http.ResponseWriter, r *http.Request, login *LoginStatus) (string, error) {
|
||||||
file, _, err := r.FormFile("image")
|
file, _, err := r.FormFile("image")
|
||||||
|
|
||||||
if err == http.ErrMissingFile {
|
if err == http.ErrMissingFile {
|
||||||
|
@ -37,14 +54,15 @@ func uploadImage(w http.ResponseWriter, r *http.Request, login *LoginStatus) (st
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
err = checkImage(file)
|
err = checkImage(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
buff := bytes.NewBuffer([]byte{})
|
buffFull := bytes.NewBuffer([]byte{})
|
||||||
buff_thumbnail := bytes.NewBuffer([]byte{})
|
buffThumb := bytes.NewBuffer([]byte{})
|
||||||
err = resizeThumb(file, buff, buff_thumbnail)
|
err = resizePicture(file, buffFull, buffThumb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -54,52 +72,32 @@ func uploadImage(w http.ResponseWriter, r *http.Request, login *LoginStatus) (st
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var name, nameFull string
|
// If a previous profile picture existed, delete it
|
||||||
|
// (don't care about errors)
|
||||||
if nameConsul := login.UserEntry.GetAttributeValue(PROFILE_PICTURE_FIELD_NAME); nameConsul != "" {
|
if nameConsul := login.UserEntry.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE); nameConsul != "" {
|
||||||
name = nameConsul
|
mc.RemoveObject(context.Background(), config.S3Bucket, nameConsul, minio.RemoveObjectOptions{})
|
||||||
nameFull = "full_" + name
|
mc.RemoveObject(context.Background(), config.S3Bucket, nameConsul+"-thumb", minio.RemoveObjectOptions{})
|
||||||
} else {
|
|
||||||
name = uuid.New().String() + ".jpeg"
|
|
||||||
nameFull = "full_" + name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = mc.PutObject(context.Background(), config.S3_Bucket, name, buff_thumbnail, int64(buff_thumbnail.Len()), minio.PutObjectOptions{
|
// Generate new random name for picture
|
||||||
|
nameFull := uuid.New().String()
|
||||||
|
nameThumb := nameFull + "-thumb"
|
||||||
|
|
||||||
|
_, err = mc.PutObject(context.Background(), config.S3Bucket, nameThumb, buffThumb, int64(buffThumb.Len()), minio.PutObjectOptions{
|
||||||
ContentType: "image/jpeg",
|
ContentType: "image/jpeg",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = mc.PutObject(context.Background(), config.S3_Bucket, nameFull, buff, int64(buff.Len()), minio.PutObjectOptions{
|
_, err = mc.PutObject(context.Background(), config.S3Bucket, nameFull, buffFull, int64(buffFull.Len()), minio.PutObjectOptions{
|
||||||
ContentType: "image/jpeg",
|
ContentType: "image/jpeg",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return name, nil
|
return nameFull, nil
|
||||||
}
|
|
||||||
|
|
||||||
func newMinioClient() (*minio.Client, error) {
|
|
||||||
endpoint := config.S3_Endpoint
|
|
||||||
accessKeyID := config.S3_AccesKey
|
|
||||||
secretKeyID := config.S3_SecretKey
|
|
||||||
useSSL := true
|
|
||||||
|
|
||||||
//Initialize Minio
|
|
||||||
minioCLient, err := minio.New(endpoint, &minio.Options{
|
|
||||||
Creds: credentials.NewStaticV4(accessKeyID, secretKeyID, ""),
|
|
||||||
Secure: useSSL,
|
|
||||||
Region: config.S3_Region,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return minioCLient, nil
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkImage(file multipart.File) error {
|
func checkImage(file multipart.File) error {
|
||||||
|
@ -112,102 +110,72 @@ func checkImage(file multipart.File) error {
|
||||||
|
|
||||||
fileType := http.DetectContentType(buff)
|
fileType := http.DetectContentType(buff)
|
||||||
fileType = strings.Split(fileType, "/")[0]
|
fileType = strings.Split(fileType, "/")[0]
|
||||||
switch fileType {
|
if fileType != "image" {
|
||||||
case "image":
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return errors.New("bad type")
|
return errors.New("bad type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resizeThumb(file multipart.File, buff, buff_thumbnail *bytes.Buffer) error {
|
func resizePicture(file multipart.File, buffFull, buffThumb *bytes.Buffer) error {
|
||||||
file.Seek(0, 0)
|
file.Seek(0, 0)
|
||||||
images, _, err := image.Decode(file)
|
picture, _, err := image.Decode(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
buff.Reset()
|
|
||||||
images = resize.Thumbnail(200, 200, images, resize.Lanczos3)
|
|
||||||
images_thumbnail := resize.Thumbnail(80, 80, images, resize.Lanczos3)
|
|
||||||
|
|
||||||
err = jpeg.Encode(buff, images, &jpeg.Options{
|
thumbnail := resize.Thumbnail(90, 90, picture, resize.Lanczos3)
|
||||||
|
picture = resize.Thumbnail(480, 480, picture, resize.Lanczos3)
|
||||||
|
|
||||||
|
err = jpeg.Encode(buffFull, picture, &jpeg.Options{
|
||||||
Quality: 95,
|
Quality: 95,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = jpeg.Encode(buff_thumbnail, images_thumbnail, &jpeg.Options{
|
err = jpeg.Encode(buffThumb, thumbnail, &jpeg.Options{
|
||||||
Quality: 95,
|
Quality: 100,
|
||||||
})
|
})
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDownloadImage(w http.ResponseWriter, r *http.Request) {
|
func handleDownloadPicture(w http.ResponseWriter, r *http.Request) {
|
||||||
//Get input value by user
|
name := mux.Vars(r)["name"]
|
||||||
dn := mux.Vars(r)["name"]
|
|
||||||
size := mux.Vars(r)["size"]
|
|
||||||
//Check login
|
//Check login
|
||||||
login := checkLogin(w, r)
|
login := checkLogin(w, r)
|
||||||
if login == nil {
|
if login == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var imageName string
|
|
||||||
if dn != "unknown_profile" {
|
|
||||||
//Search values with ldap and filter
|
|
||||||
|
|
||||||
searchRequest := ldap.NewSearchRequest(
|
|
||||||
dn,
|
|
||||||
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
|
||||||
"(objectclass=*)",
|
|
||||||
[]string{PROFILE_PICTURE_FIELD_NAME},
|
|
||||||
nil)
|
|
||||||
|
|
||||||
sr, err := login.conn.Search(searchRequest)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Search: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(sr.Entries) != 1 {
|
|
||||||
http.Error(w, fmt.Sprintf("Not found user: %s cn: %s and numberEntries: %d", dn, strings.Split(dn, ",")[0], len(sr.Entries)), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
imageName = sr.Entries[0].GetAttributeValue(PROFILE_PICTURE_FIELD_NAME)
|
|
||||||
if imageName == "" {
|
|
||||||
http.Error(w, "User doesn't have profile image", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
imageName = "unknown_profile.jpg"
|
|
||||||
}
|
|
||||||
|
|
||||||
if size == "full" {
|
|
||||||
imageName = "full_" + imageName
|
|
||||||
}
|
|
||||||
//Get the object after connect MC
|
//Get the object after connect MC
|
||||||
mc, err := newMinioClient()
|
mc, err := newMinioClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "MinioClient: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "MinioClient: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
obj, err := mc.GetObject(context.Background(), "bottin-pictures", imageName, minio.GetObjectOptions{})
|
|
||||||
|
obj, err := mc.GetObject(context.Background(), "bottin-pictures", name, minio.GetObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "MinioClient: GetObject: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "MinioClient: GetObject: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer obj.Close()
|
defer obj.Close()
|
||||||
|
|
||||||
objStat, err := obj.Stat()
|
objStat, err := obj.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "MiniObjet: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "MiniObjet: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Send JSON through xhttp
|
//Send JSON through xhttp
|
||||||
w.Header().Set("Content-Type", objStat.ContentType)
|
w.Header().Set("Content-Type", objStat.ContentType)
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(int(objStat.Size)))
|
w.Header().Set("Content-Length", strconv.Itoa(int(objStat.Size)))
|
||||||
//Copy obj in w
|
//Copy obj in w
|
||||||
writting, err := io.Copy(w, obj)
|
writting, err := io.Copy(w, obj)
|
||||||
|
|
||||||
if writting != objStat.Size || err != nil {
|
if writting != objStat.Size || err != nil {
|
||||||
http.Error(w, fmt.Sprintf("WriteBody: %s, bytes wrote %d on %d", err.Error(), writting, objStat.Size), http.StatusInternalServerError)
|
http.Error(w, fmt.Sprintf("WriteBody: %s, bytes wrote %d on %d", err.Error(), writting, objStat.Size), http.StatusInternalServerError)
|
||||||
return
|
return
|
17
profile.go
17
profile.go
|
@ -18,7 +18,7 @@ type ProfileTplData struct {
|
||||||
Surname string
|
Surname string
|
||||||
Visibility string
|
Visibility string
|
||||||
Description string
|
Description string
|
||||||
NameImage string
|
ProfilePicture string
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleProfile(w http.ResponseWriter, r *http.Request) {
|
func handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -39,8 +39,9 @@ func handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||||
data.DisplayName = login.UserEntry.GetAttributeValue("displayname")
|
data.DisplayName = login.UserEntry.GetAttributeValue("displayname")
|
||||||
data.GivenName = login.UserEntry.GetAttributeValue("givenname")
|
data.GivenName = login.UserEntry.GetAttributeValue("givenname")
|
||||||
data.Surname = login.UserEntry.GetAttributeValue("sn")
|
data.Surname = login.UserEntry.GetAttributeValue("sn")
|
||||||
data.Visibility = login.UserEntry.GetAttributeValue("visibility")
|
data.Visibility = login.UserEntry.GetAttributeValue(FIELD_NAME_DIRECTORY_VISIBILITY)
|
||||||
data.Description = login.UserEntry.GetAttributeValue("description")
|
data.Description = login.UserEntry.GetAttributeValue("description")
|
||||||
|
data.ProfilePicture = login.UserEntry.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE)
|
||||||
|
|
||||||
if r.Method == "POST" {
|
if r.Method == "POST" {
|
||||||
//5MB maximum size files
|
//5MB maximum size files
|
||||||
|
@ -56,13 +57,13 @@ func handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
data.Visibility = visible
|
data.Visibility = visible
|
||||||
|
|
||||||
name, err := uploadImage(w, r, login)
|
profilePicture, err := uploadProfilePicture(w, r, login)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
data.ErrorMessage = err.Error()
|
data.ErrorMessage = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
if name != "" {
|
if profilePicture != "" {
|
||||||
data.NameImage = name
|
data.ProfilePicture = profilePicture
|
||||||
}
|
}
|
||||||
|
|
||||||
modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
|
modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
|
||||||
|
@ -70,9 +71,9 @@ func handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||||
modify_request.Replace("givenname", []string{data.GivenName})
|
modify_request.Replace("givenname", []string{data.GivenName})
|
||||||
modify_request.Replace("sn", []string{data.Surname})
|
modify_request.Replace("sn", []string{data.Surname})
|
||||||
modify_request.Replace("description", []string{data.Description})
|
modify_request.Replace("description", []string{data.Description})
|
||||||
modify_request.Replace("visibility", []string{data.Visibility})
|
modify_request.Replace(FIELD_NAME_DIRECTORY_VISIBILITY, []string{data.Visibility})
|
||||||
if name != "" {
|
if data.ProfilePicture != "" {
|
||||||
modify_request.Replace(PROFILE_PICTURE_FIELD_NAME, []string{data.NameImage})
|
modify_request.Replace(FIELD_NAME_PROFILE_PICTURE, []string{data.ProfilePicture})
|
||||||
}
|
}
|
||||||
|
|
||||||
err = login.conn.Modify(modify_request)
|
err = login.conn.Modify(modify_request)
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 764 KiB |
|
@ -6,9 +6,6 @@
|
||||||
<a class="ml-auto btn btn-info" href="/">Retour</a>
|
<a class="ml-auto btn btn-info" href="/">Retour</a>
|
||||||
</div>
|
</div>
|
||||||
<h5>Photo de profil</h5>
|
<h5>Photo de profil</h5>
|
||||||
<object data="/image/{{ .Status.Info.DN}}/full" class=".img-thumbnail">
|
|
||||||
<img src="/image/unknown_profile/full" alt="Stack Overflow logo and icons and such">
|
|
||||||
</object>
|
|
||||||
{{if .ErrorMessage}}
|
{{if .ErrorMessage}}
|
||||||
<div class="alert alert-danger mt-4">Impossible d'effectuer la modification.
|
<div class="alert alert-danger mt-4">Impossible d'effectuer la modification.
|
||||||
<div style="font-size: 0.8em">{{ .ErrorMessage }}</div>
|
<div style="font-size: 0.8em">{{ .ErrorMessage }}</div>
|
||||||
|
@ -20,44 +17,62 @@
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<form method="POST" class="mt-4" enctype="multipart/form-data">
|
<form method="POST" class="mt-4" enctype="multipart/form-data">
|
||||||
<div class="form-group">
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-6">
|
||||||
<label>Nom d'utilisateur:</label>
|
<label>Nom d'utilisateur:</label>
|
||||||
<input type="text" disabled="true" class="form-control" value="{{ .Status.Info.Username }}" />
|
<input type="text" disabled="true" class="form-control" value="{{ .Status.Info.Username }}" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group col-md-6">
|
||||||
<label for="mail">Adresse e-mail:</label>
|
<label for="mail">Adresse e-mail:</label>
|
||||||
<input type="text" id="mail" disabled="true" name="mail" class="form-control" value="{{ .Mail }}" />
|
<input type="text" id="mail" disabled="true" name="mail" class="form-control" value="{{ .Mail }}" />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="display_name">Nom complet:</label>
|
<label for="display_name">Nom complet:</label>
|
||||||
<input type="text" id="display_name" name="display_name" class="form-control" value="{{ .DisplayName }}" />
|
<input type="text" id="display_name" name="display_name" class="form-control" value="{{ .DisplayName }}" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label for="given_name">Prénom:</label>
|
<h4>Informations complémentaires</h4>
|
||||||
<input type="text" id="given_name" name="given_name" class="form-control" value="{{ .GivenName }}" />
|
{{if .ProfilePicture}}
|
||||||
</div>
|
<div class="float-right">
|
||||||
<div class="form-group">
|
<a href="/picture/{{.ProfilePicture}}">
|
||||||
<label for="surname">Nom de famille:</label>
|
<img src="/picture/{{.ProfilePicture}}-thumb" />
|
||||||
<input type="text" id="surname" name="surname" class="form-control" value="{{ .Surname }}" />
|
</a>
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="description">Description (180 caractères maximum)</label>
|
|
||||||
<textarea id="description" name="description" class="form-control" maxlength="180">{{ .Description }}</textarea>
|
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<div class="form-group form-check">
|
<div class="form-group form-check">
|
||||||
{{if .Visibility}}
|
{{if .Visibility}}
|
||||||
<input class="form-check-input" name="visibility" type="checkbox" id="visibility" value="on" checked>
|
<input class="form-check-input" name="visibility" type="checkbox" id="visibility" value="on" checked>
|
||||||
{{else}}
|
{{else}}
|
||||||
<input class="form-check-input" name="visibility" type="checkbox" id="visibility">
|
<input class="form-check-input" name="visibility" type="checkbox" id="visibility">
|
||||||
{{end}}
|
{{end}}
|
||||||
<label class="form-check-label" for="visibility">Apparaît sur l'annuaire</label>
|
<label class="form-check-label" for="visibility">Apparaître sur l'annuaire</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group input-group mb-3">
|
|
||||||
<div class="form-group custom-file">
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-8 input-group mb-3 custom-file">
|
||||||
|
<label for="image">Photo de profil:</label>
|
||||||
<input type="file" name="image" class="custom-file-input" id="image">
|
<input type="file" name="image" class="custom-file-input" id="image">
|
||||||
<label class="custom-file-label" for="image">Choose picture (jpeg, jpg or png)</label>
|
<label class="custom-file-label" for="image">Photo de profil (jpeg, jpg or png)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="given_name">Prénom:</label>
|
||||||
|
<input type="text" id="given_name" name="given_name" class="form-control" value="{{ .GivenName }}" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="surname">Nom de famille:</label>
|
||||||
|
<input type="text" id="surname" name="surname" class="form-control" value="{{ .Surname }}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description">Description (180 caractères maximum)</label>
|
||||||
|
<textarea id="description" name="description" class="form-control" maxlength="180">{{ .Description }}</textarea>
|
||||||
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Enregistrer les modifications</button>
|
<button type="submit" class="btn btn-primary">Enregistrer les modifications</button>
|
||||||
</form>
|
</form>
|
||||||
<script src="/static/javascript/minio.js"></script>
|
<script src="/static/javascript/minio.js"></script>
|
||||||
|
|
Loading…
Reference in a new issue