Add_Directory_and_ProfilePicture #9

Merged
lx merged 13 commits from Add_Directory into main 2021-08-16 14:44:53 +00:00
7 changed files with 110 additions and 117 deletions
Showing only changes of commit 01bf4aa522 - Show all commits

View file

@ -1,5 +1,5 @@
BIN=guichet BIN=guichet
SRC=main.go ssha.go profile.go admin.go invite.go SRC=main.go ssha.go profile.go admin.go invite.go directory.go picture.go
DOCKER=lxpz/guichet_amd64 DOCKER=lxpz/guichet_amd64
all: $(BIN) all: $(BIN)

View file

@ -1,13 +1,12 @@
package main package main
import ( import (
"encoding/json"
"html/template" "html/template"
"net/http" "net/http"
"sort"
"strings" "strings"
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
"github.com/gorilla/mux"
) )
const FIELD_NAME_PROFILE_PICTURE = "profilePicture" const FIELD_NAME_PROFILE_PICTURE = "profilePicture"
@ -25,28 +24,34 @@ func handleDirectory(w http.ResponseWriter, r *http.Request) {
} }
erwan marked this conversation as resolved Outdated
Outdated
Review

L'identifiant, c'est le CN ? Dans tous les cas Identifiant c'est un mot français, il faudrait appeller ça plutôt juste Id.

L'identifiant, c'est le CN ? Dans tous les cas `Identifiant` c'est un mot français, il faudrait appeller ça plutôt juste `Id`.
type SearchResult struct { type SearchResult struct {
erwan marked this conversation as resolved Outdated
Outdated
Review

C'est le displayname que tu prends depuis le LDAP? Dans ce cas il faudrait appeller ça DisplayName

C'est le displayname que tu prends depuis le LDAP? Dans ce cas il faudrait appeller ça `DisplayName`
Id string `json:"id"` DN string
Displayname string `json:"displayname"` Id string
Email string `json:"email"` DisplayName string
Description string `json:"description"` Email string
DN string `json:"dn"` Description string
ProfilePicture string
} }
type Results struct { type SearchResults struct {
Search []SearchResult `json:"search"` Results []SearchResult
MessageID uint32 `json:"id"`
} }
type UniqueID struct { func handleDirectorySearch(w http.ResponseWriter, r *http.Request) {
Id int `json:"id"` templateDirectoryResults := template.Must(template.ParseFiles("templates/directory_results.html"))
}
func handleSearch(w http.ResponseWriter, r *http.Request) {
//Get input value by user //Get input value by user
input := mux.Vars(r)["input"] r.ParseMultipartForm(1024)
input := strings.TrimSpace(strings.Join(r.Form["query"], ""))
if r.Method != "POST" || input == "" {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
//Log to allow the research //Log to allow the research
login := checkLogin(w, r) login := checkLogin(w, r)
if login == nil { if login == nil {
http.Error(w, "Login required", http.StatusUnauthorized)
return return
} }
@ -71,42 +76,46 @@ func handleSearch(w http.ResponseWriter, r *http.Request) {
} }
//Transform the researh's result in a correct struct to send JSON //Transform the researh's result in a correct struct to send JSON
var result Results results := []SearchResult{}
for _, values := range sr.Entries { for _, values := range sr.Entries {
if ContainsI(values.GetAttributeValue(config.UserNameAttr), input) ||
if strings.Contains(values.GetAttributeValue(config.UserNameAttr), input) || strings.Contains(values.GetAttributeValue("displayname"), input) || ContainsI(values.GetAttributeValue("displayname"), input) ||
(values.GetAttributeValue("email") != "" && strings.Contains(values.GetAttributeValue("email"), input)) { ContainsI(values.GetAttributeValue("mail"), input) {
result = Results{ results = append(results, SearchResult{
Search: append(result.Search, SearchResult{ DN: values.DN,
Id: values.GetAttributeValue(config.UserNameAttr), Id: values.GetAttributeValue(config.UserNameAttr),
Displayname: values.GetAttributeValue("displayname"), DisplayName: values.GetAttributeValue("displayname"),
Email: values.GetAttributeValue("email"), Email: values.GetAttributeValue("mail"),
Outdated
Review
  1. Pourquoi un résultat vide?

  2. Si tu veux créer un résultat vide en Go, pas la peine de mettre tous les champs à "", si tu écris juste SearchResult{} ça crée une structure avec des chaines vides dans tous les champs

1. Pourquoi un résultat vide? 2. Si tu veux créer un résultat vide en Go, pas la peine de mettre tous les champs à `""`, si tu écris juste `SearchResult{}` ça crée une structure avec des chaines vides dans tous les champs
Outdated
Review

Un résultat vide permet d'avoir un readyStateChange dans le JS ce qui permet d'initialiser le tableau à vide quand il n'y a plus de match.

Pour le go fmt, j'utilise VsCode et à chaque sauvegarde il fait le go fmt et aussi de tous les warnings.

Un résultat vide permet d'avoir un readyStateChange dans le JS ce qui permet d'initialiser le tableau à vide quand il n'y a plus de match. Pour le `go fmt`, j'utilise VsCode et à chaque sauvegarde il fait le `go fmt` et aussi de tous les warnings.
Outdated
Review

Mais si tu met pas le SearchResult vide, tu as quand même le readyStateChange non ? Et tu as un tableau JSON vide, c'est tout, mais ta partie JS est sensé gérer ça

Mais si tu met pas le `SearchResult` vide, tu as quand même le readyStateChange non ? Et tu as un tableau JSON vide, c'est tout, mais ta partie JS est sensé gérer ça
Outdated
Review

Justement au début j'avais fait ça. Ce qui me donnait une valeur null. Mais la fonction JS suivante ne répondez pas, elle ignorait juste la réponse. (même en enlevant le if)

 xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 201) {

Elle n'était tout bonnement pas appelée.

Justement au début j'avais fait ça. Ce qui me donnait une valeur `null`. Mais la fonction JS suivante ne répondez pas, elle ignorait juste la réponse. (même en enlevant le if) ```js xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 201) { ``` Elle n'était tout bonnement pas appelée.
Description: values.GetAttributeValue("description"), Description: values.GetAttributeValue("description"),
DN: values.DN, ProfilePicture: values.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE),
}), })
}
}
}
if result.Search == nil {
result = Results{
Search: append(result.Search, SearchResult{}),
} }
} }
var id UniqueID search_results := SearchResults{
//Decode JSON body Results: results,
err = json.NewDecoder(r.Body).Decode(&id)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
} }
result.MessageID = uint32(id.Id) sort.Sort(&search_results)
//Send JSON through xhttp templateDirectoryResults.Execute(w, search_results)
w.Header().Set("Content-Type", "application/json; charset=UTF-8") }
w.WriteHeader(http.StatusCreated)
if err := json.NewEncoder(w).Encode(result); err != nil { func ContainsI(a string, b string) bool {
http.Error(w, err.Error(), http.StatusInternalServerError) return strings.Contains(
} strings.ToLower(a),
strings.ToLower(b),
)
}
func (r *SearchResults) Len() int {
return len(r.Results)
}
func (r *SearchResults) Less(i, j int) bool {
return r.Results[i].Id < r.Results[j].Id
}
func (r *SearchResults) Swap(i, j int) {
r.Results[i], r.Results[j] = r.Results[j], r.Results[i]
} }

View file

@ -115,8 +115,8 @@ func main() {
r.HandleFunc("/passwd", handlePasswd) r.HandleFunc("/passwd", handlePasswd)
r.HandleFunc("/picture/{name}", handleDownloadPicture) r.HandleFunc("/picture/{name}", handleDownloadPicture)
r.HandleFunc("/directory/search", handleDirectorySearch)
r.HandleFunc("/directory", handleDirectory) r.HandleFunc("/directory", handleDirectory)
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)

View file

@ -1,51 +1,24 @@
var perso_id = 0;
var last_id = 0; var last_id = 0;
function searchDirectory() { function searchDirectory() {
var input = document.getElementById("search").value; var input = document.getElementById("search").value;
if(input){ if(input){
last_id++;
var request_id = last_id;
var data = new FormData();
data.append("query", input);
var xhttp = new XMLHttpRequest(); var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() { xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 201) { if (request_id != last_id) return;
// Typical action to be performed when the document is ready:
//Response from Request Ajax
var jsonResponse = JSON.parse(xhttp.responseText);
if (last_id < jsonResponse.id) { if (this.readyState == 4 && this.status == 200) {
last_id = jsonResponse.id var result_div = document.getElementById("search-results");
//We get the old table element, we create an new table element then we increment this new table. result_div.innerHTML = xhttp.responseText;
//After the new add, we replace the old table by the new one.
var old_table = document.getElementById("users");
var table = document.createElement('tbody');
table.setAttribute("id","users");
for (let i =0; i < Object.keys(jsonResponse.search).length; i++) {
var row = table.insertRow(0);
var urlName = row.insertCell(0);
var identifiant = row.insertCell(1);
var displayname = row.insertCell(2);
var email = row.insertCell(3);
var description = row.insertCell(4);
description.setAttribute("style", "word-break: break-all;");
if (jsonResponse.search[i].dn.localeCompare("")!=0) {
urlName.innerText = `<object data="/image/${jsonResponse.search[i].dn}/little" class=".img-thumbnail"><image src="/image/unknown_profile/little" class=".img-thumbnail"></object>`
}else {
urlName.innerText=""
}
identifiant.innerText = `<a href="/admin/ldap/${jsonResponse.search[i].dn}">${jsonResponse.search[i].id}</a>`
displayname.innerText = jsonResponse.search[i].displayname
email.innerText = jsonResponse.search[i].email
description.innerText = jsonResponse.search[i].description
}
old_table.parentNode.replaceChild(table, old_table)
} }
}
}; };
perso_id += 1 xhttp.open("POST", "/directory/search", true);
xhttp.overrideMimeType("application/json"); xhttp.send(data);
xhttp.open("POST", "/search/".concat(input), true);
xhttp.send(JSON.stringify({"id": perso_id}));
} }
} }

View file

@ -1,35 +1,23 @@
{{define "title"}}Directory |{{end}} {{define "title"}}Annuaire |{{end}}
{{define "body"}} {{define "body"}}
<div class="d-flex"> <div class="d-flex">
<h4>Directory</h4> <h4>Annuaire</h4>
<a class="ml-auto btn btn-info" href="/">Menu principal</a> <a class="ml-auto btn btn-info" href="/">Menu principal</a>
</div> </div>
<div class="d-flex"> <form>
<div class="d-flex mx-auto"> <div class="form-group form-row">
<p class="">Name:</p> <div class="col-sm-2">&nbsp;</div>
<form class="px-2" style="width: fit-content;"> <label for="search" class="col-sm-2 col-form-label">Rechercher :</label>
<input id="search" type="text" onkeyup="searchDirectory()" size="20"> <input class="form-control col-sm-4" id="search" name="search" type="text" onkeyup="searchDirectory()" size="20">
</form> </div>
</div> </form>
</div>
<table class="table mt-4"> <div id="search-results"></div>
<thead>
<th scope="col">Profil image</th>
<th scope="col">Identifiant</th>
<th scope="col">Nom complet</th>
<th scope="col">Email</th>
<th scope="col">Description</th>
</thead>
<tbody id="users">
</tbody>
</table>
<script src="/static/javascript/search.js"></script>
{{end}} <script src="/static/javascript/search.js"></script>
{{end}}

View file

@ -0,0 +1,23 @@
{{if .Results}}
{{range .Results}}
<div class="card mt-4">
<div class="card-body">
<div class="float-right">
{{if .ProfilePicture}}
<a href="/picture/{{.ProfilePicture}}">
<img src="/picture/{{.ProfilePicture}}-thumb"/>
</a>
{{else}}
{{end}}
</div>
<h5 class="card-title">
{{.DisplayName}}
<code>{{.Id}}@</code>
</h5>
<p class="card-text">{{.Description}}</p>
</div>
</div>
{{end}}
{{else}}
Aucun résultat.
{{end}}

View file

@ -5,7 +5,7 @@
<h4>Modifier mon profil</h4> <h4>Modifier mon profil</h4>
<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>
{{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>
@ -70,8 +70,8 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="description">Description (180 caractères maximum)</label> <label for="description">Description</label>
<textarea id="description" name="description" class="form-control" maxlength="180">{{ .Description }}</textarea> <textarea id="description" name="description" class="form-control">{{ .Description }}</textarea>
</div> </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>