Add_Directory_and_ProfilePicture #9
2
Makefile
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
|
||||
all: $(BIN)
|
||||
|
|
103
directory.go
|
@ -1,13 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
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
|
||||
type SearchResult struct {
|
||||
erwan marked this conversation as resolved
Outdated
lx
commented
C'est le displayname que tu prends depuis le LDAP? Dans ce cas il faudrait appeller ça C'est le displayname que tu prends depuis le LDAP? Dans ce cas il faudrait appeller ça `DisplayName`
|
||||
Id string `json:"id"`
|
||||
Displayname string `json:"displayname"`
|
||||
Email string `json:"email"`
|
||||
Description string `json:"description"`
|
||||
DN string `json:"dn"`
|
||||
DN string
|
||||
Id string
|
||||
DisplayName string
|
||||
Email string
|
||||
Description string
|
||||
ProfilePicture string
|
||||
}
|
||||
|
||||
type Results struct {
|
||||
Search []SearchResult `json:"search"`
|
||||
MessageID uint32 `json:"id"`
|
||||
type SearchResults struct {
|
||||
Results []SearchResult
|
||||
}
|
||||
|
||||
type UniqueID struct {
|
||||
Id int `json:"id"`
|
||||
}
|
||||
func handleDirectorySearch(w http.ResponseWriter, r *http.Request) {
|
||||
templateDirectoryResults := template.Must(template.ParseFiles("templates/directory_results.html"))
|
||||
|
||||
func handleSearch(w http.ResponseWriter, r *http.Request) {
|
||||
//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
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
http.Error(w, "Login required", http.StatusUnauthorized)
|
||||
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
|
||||
var result Results
|
||||
results := []SearchResult{}
|
||||
|
||||
for _, values := range sr.Entries {
|
||||
|
||||
if strings.Contains(values.GetAttributeValue(config.UserNameAttr), input) || strings.Contains(values.GetAttributeValue("displayname"), input) ||
|
||||
(values.GetAttributeValue("email") != "" && strings.Contains(values.GetAttributeValue("email"), input)) {
|
||||
result = Results{
|
||||
Search: append(result.Search, SearchResult{
|
||||
Id: values.GetAttributeValue(config.UserNameAttr),
|
||||
Displayname: values.GetAttributeValue("displayname"),
|
||||
Email: values.GetAttributeValue("email"),
|
||||
Description: values.GetAttributeValue("description"),
|
||||
DN: values.DN,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if result.Search == nil {
|
||||
result = Results{
|
||||
Search: append(result.Search, SearchResult{}),
|
||||
if ContainsI(values.GetAttributeValue(config.UserNameAttr), input) ||
|
||||
ContainsI(values.GetAttributeValue("displayname"), input) ||
|
||||
ContainsI(values.GetAttributeValue("mail"), input) {
|
||||
results = append(results, SearchResult{
|
||||
DN: values.DN,
|
||||
Id: values.GetAttributeValue(config.UserNameAttr),
|
||||
DisplayName: values.GetAttributeValue("displayname"),
|
||||
Email: values.GetAttributeValue("mail"),
|
||||
lx
commented
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
erwan
commented
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 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.
lx
commented
Mais si tu met pas le 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
erwan
commented
Justement au début j'avais fait ça. Ce qui me donnait une valeur
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"),
|
||||
ProfilePicture: values.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var id UniqueID
|
||||
//Decode JSON body
|
||||
err = json.NewDecoder(r.Body).Decode(&id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
search_results := SearchResults{
|
||||
Results: results,
|
||||
}
|
||||
result.MessageID = uint32(id.Id)
|
||||
sort.Sort(&search_results)
|
||||
|
||||
//Send JSON through xhttp
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := json.NewEncoder(w).Encode(result); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
templateDirectoryResults.Execute(w, search_results)
|
||||
}
|
||||
|
||||
func ContainsI(a string, b string) bool {
|
||||
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]
|
||||
}
|
||||
|
|
2
main.go
|
@ -115,8 +115,8 @@ func main() {
|
|||
r.HandleFunc("/passwd", handlePasswd)
|
||||
r.HandleFunc("/picture/{name}", handleDownloadPicture)
|
||||
|
||||
r.HandleFunc("/directory/search", handleDirectorySearch)
|
||||
r.HandleFunc("/directory", handleDirectory)
|
||||
r.HandleFunc("/directory/search/{input}", handleSearch)
|
||||
|
||||
r.HandleFunc("/invite/new_account", handleInviteNewAccount)
|
||||
r.HandleFunc("/invite/send_code", handleInviteSendCode)
|
||||
|
|
|
@ -1,51 +1,24 @@
|
|||
var perso_id = 0;
|
||||
var last_id = 0;
|
||||
|
||||
function searchDirectory() {
|
||||
var input = document.getElementById("search").value;
|
||||
if(input){
|
||||
last_id++;
|
||||
var request_id = last_id;
|
||||
|
||||
var data = new FormData();
|
||||
data.append("query", input);
|
||||
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 201) {
|
||||
// Typical action to be performed when the document is ready:
|
||||
//Response from Request Ajax
|
||||
var jsonResponse = JSON.parse(xhttp.responseText);
|
||||
if (request_id != last_id) return;
|
||||
|
||||
if (last_id < jsonResponse.id) {
|
||||
last_id = jsonResponse.id
|
||||
//We get the old table element, we create an new table element then we increment this new table.
|
||||
//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)
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
var result_div = document.getElementById("search-results");
|
||||
result_div.innerHTML = xhttp.responseText;
|
||||
}
|
||||
}
|
||||
};
|
||||
perso_id += 1
|
||||
xhttp.overrideMimeType("application/json");
|
||||
xhttp.open("POST", "/search/".concat(input), true);
|
||||
xhttp.send(JSON.stringify({"id": perso_id}));
|
||||
xhttp.open("POST", "/directory/search", true);
|
||||
xhttp.send(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,23 @@
|
|||
{{define "title"}}Directory |{{end}}
|
||||
{{define "title"}}Annuaire |{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<div class="d-flex">
|
||||
<h4>Directory</h4>
|
||||
<h4>Annuaire</h4>
|
||||
<a class="ml-auto btn btn-info" href="/">Menu principal</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="d-flex">
|
||||
<div class="d-flex mx-auto">
|
||||
<p class="">Name:</p>
|
||||
<form class="px-2" style="width: fit-content;">
|
||||
<input id="search" type="text" onkeyup="searchDirectory()" size="20">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<form>
|
||||
<div class="form-group form-row">
|
||||
<div class="col-sm-2"> </div>
|
||||
<label for="search" class="col-sm-2 col-form-label">Rechercher :</label>
|
||||
<input class="form-control col-sm-4" id="search" name="search" type="text" onkeyup="searchDirectory()" size="20">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table class="table mt-4">
|
||||
<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>
|
||||
<div id="search-results"></div>
|
||||
|
||||
{{end}}
|
||||
<script src="/static/javascript/search.js"></script>
|
||||
|
||||
{{end}}
|
||||
|
|
23
templates/directory_results.html
Normal 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}}
|
|
@ -5,7 +5,7 @@
|
|||
<h4>Modifier mon profil</h4>
|
||||
<a class="ml-auto btn btn-info" href="/">Retour</a>
|
||||
</div>
|
||||
<h5>Photo de profil</h5>
|
||||
|
||||
{{if .ErrorMessage}}
|
||||
<div class="alert alert-danger mt-4">Impossible d'effectuer la modification.
|
||||
<div style="font-size: 0.8em">{{ .ErrorMessage }}</div>
|
||||
|
@ -70,8 +70,8 @@
|
|||
</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>
|
||||
<label for="description">Description</label>
|
||||
<textarea id="description" name="description" class="form-control">{{ .Description }}</textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Enregistrer les modifications</button>
|
||||
</form>
|
||||
|
|
L'identifiant, c'est le CN ? Dans tous les cas
Identifiant
c'est un mot français, il faudrait appeller ça plutôt justeId
.