Add_Directory_and_ProfilePicture #9
2
Makefile
|
@ -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)
|
||||||
|
|
103
directory.go
|
@ -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
|
|||||||
type SearchResult struct {
|
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"`
|
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"),
|
||||||
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"),
|
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]
|
||||||
}
|
}
|
||||||
|
|
2
main.go
|
@ -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)
|
||||||
|
|
|
@ -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}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"> </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}}
|
||||||
|
|
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>
|
<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>
|
||||||
|
|
L'identifiant, c'est le CN ? Dans tous les cas
Identifiant
c'est un mot français, il faudrait appeller ça plutôt justeId
.