First iteration on mailing list administration interface
This commit is contained in:
parent
cd41532572
commit
670123df38
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@ guichet
|
|||||||
guichet.static
|
guichet.static
|
||||||
config.json
|
config.json
|
||||||
result
|
result
|
||||||
|
.direnv/
|
||||||
|
200
admin.go
200
admin.go
@ -117,15 +117,186 @@ func handleAdminGroups(w http.ResponseWriter, r *http.Request) {
|
|||||||
templateAdminGroups.Execute(w, data)
|
templateAdminGroups.Execute(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AdminMailingTplData struct {
|
||||||
|
Login *LoginStatus
|
||||||
|
MailingNameAttr string
|
||||||
|
MailingBaseDN string
|
||||||
|
MailingLists EntryList
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAdminMailing(w http.ResponseWriter, r *http.Request) {
|
||||||
|
templateAdminMailing := getTemplate("admin_mailing.html")
|
||||||
|
|
||||||
|
login := checkAdminLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
searchRequest := ldap.NewSearchRequest(
|
||||||
|
config.MailingBaseDN,
|
||||||
|
ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
fmt.Sprintf("(&(objectClass=groupOfNames))"),
|
||||||
|
[]string{config.MailingNameAttr, "dn", "description"},
|
||||||
|
nil)
|
||||||
|
|
||||||
|
sr, err := login.conn.Search(searchRequest)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &AdminMailingTplData{
|
||||||
|
Login: login,
|
||||||
|
MailingNameAttr: config.MailingNameAttr,
|
||||||
|
MailingBaseDN: config.MailingBaseDN,
|
||||||
|
MailingLists: EntryList(sr.Entries),
|
||||||
|
}
|
||||||
|
sort.Sort(data.MailingLists)
|
||||||
|
|
||||||
|
templateAdminMailing.Execute(w, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminMailingListTplData struct {
|
||||||
|
Login *LoginStatus
|
||||||
|
MailingNameAttr string
|
||||||
|
MailingBaseDN string
|
||||||
|
|
||||||
|
MailingList *ldap.Entry
|
||||||
|
Members EntryList
|
||||||
|
PossibleNewMembers EntryList
|
||||||
|
|
||||||
|
Error string
|
||||||
|
Success bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAdminMailingList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
templateAdminMailingList := getTemplate("admin_mailing_list.html")
|
||||||
|
|
||||||
|
login := checkAdminLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := mux.Vars(r)["id"]
|
||||||
|
dn := fmt.Sprintf("%s=%s,%s", config.MailingNameAttr, id, config.MailingBaseDN)
|
||||||
|
|
||||||
|
// handle modifications
|
||||||
|
dError := ""
|
||||||
|
dSuccess := false
|
||||||
|
|
||||||
|
if r.Method == "POST" {
|
||||||
|
r.ParseForm()
|
||||||
|
action := strings.Join(r.Form["action"], "")
|
||||||
|
if action == "add-member" {
|
||||||
|
member := strings.Join(r.Form["member"], "")
|
||||||
|
modify_request := ldap.NewModifyRequest(dn, nil)
|
||||||
|
modify_request.Add("member", []string{member})
|
||||||
|
|
||||||
|
err := login.conn.Modify(modify_request)
|
||||||
|
if err != nil {
|
||||||
|
dError = err.Error()
|
||||||
|
} else {
|
||||||
|
dSuccess = true
|
||||||
|
}
|
||||||
|
} else if action == "delete-member" {
|
||||||
|
member := strings.Join(r.Form["member"], "")
|
||||||
|
modify_request := ldap.NewModifyRequest(dn, nil)
|
||||||
|
modify_request.Delete("member", []string{member})
|
||||||
|
|
||||||
|
err := login.conn.Modify(modify_request)
|
||||||
|
if err != nil {
|
||||||
|
dError = err.Error()
|
||||||
|
} else {
|
||||||
|
dSuccess = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve mailing list
|
||||||
|
searchRequest := ldap.NewSearchRequest(
|
||||||
|
dn,
|
||||||
|
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
fmt.Sprintf("(objectclass=groupOfNames)"),
|
||||||
|
[]string{"dn", config.MailingNameAttr, "member"},
|
||||||
|
nil)
|
||||||
|
|
||||||
|
sr, err := login.conn.Search(searchRequest)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sr.Entries) != 1 {
|
||||||
|
http.Error(w, fmt.Sprintf("Object not found: %s", dn), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ml := sr.Entries[0]
|
||||||
|
|
||||||
|
memberDns := make(map[string]bool)
|
||||||
|
for _, attr := range ml.Attributes {
|
||||||
|
if attr.Name == "member" {
|
||||||
|
for _, v := range attr.Values {
|
||||||
|
memberDns[v] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve list of current and possible new members
|
||||||
|
members := []*ldap.Entry{}
|
||||||
|
possibleNewMembers := []*ldap.Entry{}
|
||||||
|
|
||||||
|
searchRequest = ldap.NewSearchRequest(
|
||||||
|
config.UserBaseDN,
|
||||||
|
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
fmt.Sprintf("(objectClass=organizationalPerson)"),
|
||||||
|
[]string{"dn", "displayname", "mail"},
|
||||||
|
nil)
|
||||||
|
sr, err = login.conn.Search(searchRequest)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ent := range sr.Entries {
|
||||||
|
if _, ok := memberDns[ent.DN]; ok {
|
||||||
|
members = append(members, ent)
|
||||||
|
} else {
|
||||||
|
possibleNewMembers = append(possibleNewMembers, ent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &AdminMailingListTplData{
|
||||||
|
Login: login,
|
||||||
|
MailingNameAttr: config.MailingNameAttr,
|
||||||
|
MailingBaseDN: config.MailingBaseDN,
|
||||||
|
|
||||||
|
MailingList: ml,
|
||||||
|
Members: members,
|
||||||
|
PossibleNewMembers: possibleNewMembers,
|
||||||
|
|
||||||
|
Error: dError,
|
||||||
|
Success: dSuccess,
|
||||||
|
}
|
||||||
|
sort.Sort(data.Members)
|
||||||
|
sort.Sort(data.PossibleNewMembers)
|
||||||
|
|
||||||
|
templateAdminMailingList.Execute(w, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================
|
||||||
|
// LDAP EXPLORER
|
||||||
|
// ===================================================
|
||||||
|
|
||||||
type AdminLDAPTplData struct {
|
type AdminLDAPTplData struct {
|
||||||
DN string
|
DN string
|
||||||
|
|
||||||
Path []PathItem
|
Path []PathItem
|
||||||
ChildrenOU []Child
|
ChildrenOU []Child
|
||||||
ChildrenOther []Child
|
ChildrenOther []Child
|
||||||
CanAddChild bool
|
CanAddChild bool
|
||||||
Props map[string]*PropValues
|
Props map[string]*PropValues
|
||||||
CanDelete bool
|
CanDelete bool
|
||||||
|
|
||||||
HasMembers bool
|
HasMembers bool
|
||||||
Members []EntryName
|
Members []EntryName
|
||||||
@ -523,12 +694,12 @@ func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
|
|||||||
templateAdminLDAP.Execute(w, &AdminLDAPTplData{
|
templateAdminLDAP.Execute(w, &AdminLDAPTplData{
|
||||||
DN: dn,
|
DN: dn,
|
||||||
|
|
||||||
Path: path,
|
Path: path,
|
||||||
ChildrenOU: childrenOU,
|
ChildrenOU: childrenOU,
|
||||||
ChildrenOther: childrenOther,
|
ChildrenOther: childrenOther,
|
||||||
Props: props,
|
Props: props,
|
||||||
CanAddChild: dn_last_attr == "ou" || isOrganization,
|
CanAddChild: dn_last_attr == "ou" || isOrganization,
|
||||||
CanDelete: dn != config.BaseDN && len(childrenOU) == 0 && len(childrenOther) == 0,
|
CanDelete: dn != config.BaseDN && len(childrenOU) == 0 && len(childrenOther) == 0,
|
||||||
|
|
||||||
HasMembers: len(members) > 0 || hasMembers,
|
HasMembers: len(members) > 0 || hasMembers,
|
||||||
Members: members,
|
Members: members,
|
||||||
@ -671,9 +842,12 @@ func handleAdminCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
data.Error = err.Error()
|
data.Error = err.Error()
|
||||||
} else {
|
} else {
|
||||||
http.Redirect(w, r, "/admin/ldap/"+dn, http.StatusFound)
|
if super_dn == config.MailingBaseDN && data.IdType == config.MailingNameAttr {
|
||||||
|
http.Redirect(w, r, "/admin/mailing/"+data.IdValue, http.StatusFound)
|
||||||
|
} else {
|
||||||
|
http.Redirect(w, r, "/admin/ldap/"+dn, http.StatusFound)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
71
flake.nix
71
flake.nix
@ -1,43 +1,44 @@
|
|||||||
{
|
{
|
||||||
description = "A simple LDAP web interface for Bottin";
|
description = "A simple LDAP web interface for Bottin";
|
||||||
|
|
||||||
inputs.nixpkgs.url = "github:nixos/nixpkgs/0244e143dc943bcf661fdaf581f01eb0f5000fcf";
|
inputs.nixpkgs.url =
|
||||||
inputs.gomod2nix.url = "github:tweag/gomod2nix/40d32f82fc60d66402eb0972e6e368aeab3faf58";
|
"github:nixos/nixpkgs/0244e143dc943bcf661fdaf581f01eb0f5000fcf";
|
||||||
|
inputs.gomod2nix.url =
|
||||||
|
"github:tweag/gomod2nix/40d32f82fc60d66402eb0972e6e368aeab3faf58";
|
||||||
|
|
||||||
outputs = { self, nixpkgs, gomod2nix }:
|
outputs = { self, nixpkgs, gomod2nix }:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
overlays = [
|
overlays = [
|
||||||
(self: super: {
|
(self: super: {
|
||||||
gomod = super.callPackage "${gomod2nix}/builder/" { };
|
gomod = super.callPackage "${gomod2nix}/builder/" { };
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
};
|
|
||||||
src = ./.;
|
|
||||||
bottin = pkgs.gomod.buildGoApplication {
|
|
||||||
pname = "guichet";
|
|
||||||
version = "0.1.0";
|
|
||||||
src = src;
|
|
||||||
modules = ./gomod2nix.toml;
|
|
||||||
|
|
||||||
CGO_ENABLED=0;
|
|
||||||
|
|
||||||
ldflags = [
|
|
||||||
"-X main.templatePath=${src + "/templates"}"
|
|
||||||
"-X main.staticPath=${src + "/static"}"
|
|
||||||
];
|
|
||||||
|
|
||||||
meta = with pkgs.lib; {
|
|
||||||
description = "A simple LDAP web interface for Bottin";
|
|
||||||
homepage = "https://git.deuxfleurs.fr/Deuxfleurs/guichet";
|
|
||||||
license = licenses.gpl3Plus;
|
|
||||||
platforms = platforms.linux;
|
|
||||||
};
|
};
|
||||||
|
src = ./.;
|
||||||
|
bottin = pkgs.gomod.buildGoApplication {
|
||||||
|
pname = "guichet";
|
||||||
|
version = "0.1.0";
|
||||||
|
src = src;
|
||||||
|
modules = ./gomod2nix.toml;
|
||||||
|
|
||||||
|
CGO_ENABLED = 0;
|
||||||
|
|
||||||
|
ldflags = [
|
||||||
|
"-X main.templatePath=${src + "/templates"}"
|
||||||
|
"-X main.staticPath=${src + "/static"}"
|
||||||
|
];
|
||||||
|
|
||||||
|
meta = with pkgs.lib; {
|
||||||
|
description = "A simple LDAP web interface for Bottin";
|
||||||
|
homepage = "https://git.deuxfleurs.fr/Deuxfleurs/guichet";
|
||||||
|
license = licenses.gpl3Plus;
|
||||||
|
platforms = platforms.linux;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
packages.x86_64-linux.bottin = bottin;
|
||||||
|
devShell.x86_64-linux = pkgs.mkShell { nativeBuildInputs = [ pkgs.go ]; };
|
||||||
};
|
};
|
||||||
in
|
|
||||||
{
|
|
||||||
packages.x86_64-linux.bottin = bottin;
|
|
||||||
packages.x86_64-linux.default = self.packages.x86_64-linux.bottin;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
14
main.go
14
main.go
@ -23,11 +23,13 @@ type ConfigFile struct {
|
|||||||
LdapServerAddr string `json:"ldap_server_addr"`
|
LdapServerAddr string `json:"ldap_server_addr"`
|
||||||
LdapTLS bool `json:"ldap_tls"`
|
LdapTLS bool `json:"ldap_tls"`
|
||||||
|
|
||||||
BaseDN string `json:"base_dn"`
|
BaseDN string `json:"base_dn"`
|
||||||
UserBaseDN string `json:"user_base_dn"`
|
UserBaseDN string `json:"user_base_dn"`
|
||||||
UserNameAttr string `json:"user_name_attr"`
|
UserNameAttr string `json:"user_name_attr"`
|
||||||
GroupBaseDN string `json:"group_base_dn"`
|
GroupBaseDN string `json:"group_base_dn"`
|
||||||
GroupNameAttr string `json:"group_name_attr"`
|
GroupNameAttr string `json:"group_name_attr"`
|
||||||
|
MailingBaseDN string `json:"mailing_list_base_dn"`
|
||||||
|
MailingNameAttr string `json:"mailing_list_name_attr"`
|
||||||
|
|
||||||
InvitationBaseDN string `json:"invitation_base_dn"`
|
InvitationBaseDN string `json:"invitation_base_dn"`
|
||||||
InvitationNameAttr string `json:"invitation_name_attr"`
|
InvitationNameAttr string `json:"invitation_name_attr"`
|
||||||
@ -131,6 +133,8 @@ func main() {
|
|||||||
|
|
||||||
r.HandleFunc("/admin/users", handleAdminUsers)
|
r.HandleFunc("/admin/users", handleAdminUsers)
|
||||||
r.HandleFunc("/admin/groups", handleAdminGroups)
|
r.HandleFunc("/admin/groups", handleAdminGroups)
|
||||||
|
r.HandleFunc("/admin/mailing", handleAdminMailing)
|
||||||
|
r.HandleFunc("/admin/mailing/{id}", handleAdminMailingList)
|
||||||
r.HandleFunc("/admin/ldap/{dn}", handleAdminLDAP)
|
r.HandleFunc("/admin/ldap/{dn}", handleAdminLDAP)
|
||||||
r.HandleFunc("/admin/create/{template}/{super_dn}", handleAdminCreate)
|
r.HandleFunc("/admin/create/{template}/{super_dn}", handleAdminCreate)
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ func handlePasswd(w http.ResponseWriter, r *http.Request) {
|
|||||||
data.NoMatchError = true
|
data.NoMatchError = true
|
||||||
} else {
|
} else {
|
||||||
modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
|
modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
|
||||||
pw, err := SSHAEncode(password);
|
pw, err := SSHAEncode(password)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
modify_request.Replace("userpassword", []string{pw})
|
modify_request.Replace("userpassword", []string{pw})
|
||||||
err := login.conn.Modify(modify_request)
|
err := login.conn.Modify(modify_request)
|
||||||
|
@ -8,6 +8,11 @@
|
|||||||
<a class="ml-4 btn btn-info" href="/">Menu principal</a>
|
<a class="ml-4 btn btn-info" href="/">Menu principal</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-warning mt-4">
|
||||||
|
Les groupes servent uniquement à contrôler l'accès à différentes fonctionalités de Deuxfleurs.
|
||||||
|
Ce ne sont pas des <a href="/admin/mailing">mailing lists</a>.
|
||||||
|
</div>
|
||||||
|
|
||||||
<table class="table mt-4">
|
<table class="table mt-4">
|
||||||
<thead>
|
<thead>
|
||||||
<th scope="col">Identifiant</th>
|
<th scope="col">Identifiant</th>
|
||||||
|
32
templates/admin_mailing.html
Normal file
32
templates/admin_mailing.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{{define "title"}}Mailing lists |{{end}}
|
||||||
|
|
||||||
|
{{define "body"}}
|
||||||
|
|
||||||
|
<div class="d-flex">
|
||||||
|
<h4>Mailing lists</h4>
|
||||||
|
<a class="ml-auto btn btn-success" href="/admin/create/group/{{.MailingBaseDN}}">Nouvelle mailing list</a>
|
||||||
|
<a class="ml-4 btn btn-info" href="/">Menu principal</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table mt-4">
|
||||||
|
<thead>
|
||||||
|
<th scope="col">Adresse</th>
|
||||||
|
<th scope="col">Description</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{with $root := .}}
|
||||||
|
{{range $ml := $root.MailingLists}}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/admin/mailing/{{$ml.GetAttributeValue $root.MailingNameAttr}}">
|
||||||
|
{{$ml.GetAttributeValue $root.MailingNameAttr}}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{$ml.GetAttributeValue "description"}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{{end}}
|
73
templates/admin_mailing_list.html
Normal file
73
templates/admin_mailing_list.html
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
{{define "title"}}ML {{.MailingList.GetAttributeValue .MailingNameAttr}} |{{end}}
|
||||||
|
|
||||||
|
{{define "body"}}
|
||||||
|
|
||||||
|
<div class="d-flex">
|
||||||
|
<h4>ML {{.MailingList.GetAttributeValue .MailingNameAttr}}
|
||||||
|
<a class="ml-auto btn btn-sm btn-dark" href="/admin/ldap/{{.MailingList.DN}}">Vue avancée</a>
|
||||||
|
</h4>
|
||||||
|
<a class="ml-auto btn btn-dark" href="/admin/mailing">Liste des ML</a>
|
||||||
|
<a class="ml-4 btn btn-info" href="/">Menu principal</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if .Success}}
|
||||||
|
<div class="alert alert-success mt-2">Modification enregistrée.</div>
|
||||||
|
{{end}}
|
||||||
|
{{if .Error}}
|
||||||
|
<div class="alert alert-danger mt-2">
|
||||||
|
Impossible d'effectuer la modification.
|
||||||
|
<div style="font-size: 0.8em">{{.Error}}</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<table class="table mt-4">
|
||||||
|
<thead>
|
||||||
|
<th scope="col">Adresse</th>
|
||||||
|
<th scope="col">Nom</th>
|
||||||
|
<th scope="col" style="width: 6em"></th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{with $root := .}}
|
||||||
|
{{range $member := $root.Members}}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/admin/ldap/{{$member.DN}}">
|
||||||
|
{{$member.GetAttributeValue "mail"}}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{$member.GetAttributeValue "displayname"}}</td>
|
||||||
|
<td>
|
||||||
|
<form method="POST" onsubmit="return confirm('Supprimer de la ML ?');">
|
||||||
|
<input type="hidden" name="action" value="delete-member" />
|
||||||
|
<input type="hidden" name="member" value="{{.DN}}" />
|
||||||
|
<input type="submit" value="Suppr" class="form-control btn btn-danger btn-sm" />
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<hr class="mt-4" />
|
||||||
|
<h5 class="mt-4">Ajouter un destinataire</h5>
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="action" value="add-member" />
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-md-3"><strong>Utilisateur existant :</strong>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<input class="form-control" type="text" list="users" name="member" placeholder="Utilisateur..." />
|
||||||
|
<datalist id="users">
|
||||||
|
{{range .PossibleNewMembers}}
|
||||||
|
{{if .GetAttributeValue "mail"}}
|
||||||
|
<option value="{{.DN}}">{{if .GetAttributeValue "displayname"}}{{.GetAttributeValue "displayname"}} ({{.GetAttributeValue "mail" }}){{else}}{{.GetAttributeValue "mail"}}{{end}}</option>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</datalist>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<input type="submit" value="Ajouter" class="form-control btn btn-success btn-sm" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
@ -40,6 +40,7 @@
|
|||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
<a class="list-group-item list-group-item-action" href="/admin/users">Utilisateur·ices</a>
|
<a class="list-group-item list-group-item-action" href="/admin/users">Utilisateur·ices</a>
|
||||||
<a class="list-group-item list-group-item-action" href="/admin/groups">Groupes</a>
|
<a class="list-group-item list-group-item-action" href="/admin/groups">Groupes</a>
|
||||||
|
<a class="list-group-item list-group-item-action" href="/admin/mailing">Mailing lists</a>
|
||||||
<a class="list-group-item list-group-item-action" href="/admin/ldap/{{.BaseDN}}">Explorateur LDAP</a>
|
<a class="list-group-item list-group-item-action" href="/admin/ldap/{{.BaseDN}}">Explorateur LDAP</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<title>{{template "title"}} Guichet</title>
|
<title>{{template "title"}} Guichet</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container mb-4">
|
||||||
<h1>Guichet Deuxfleurs💮💮</h1>
|
<h1>Guichet Deuxfleurs💮💮</h1>
|
||||||
<hr />
|
<hr />
|
||||||
{{template "body" .}}
|
{{template "body" .}}
|
||||||
|
Loading…
Reference in New Issue
Block a user