#!/bin/bash /usr/scripts/python.sh # -*- coding: utf-8 -*- u""" Interface utilisateur du système de gestion des machines et adhérents du crans Copyright (C) Valentin Samir Licence : GPLv3 """ import os import sys import signal import collections from pythondialog import Dialog from gestion.affich_tools import get_screen_size, coul import lc_ldap.shortcuts import lc_ldap.printing as printing # Implémentation "à la main" de la tail récursion en python # voir http://kylem.net/programming/tailcall.html # je trouve ça assez sioux class TailCaller(object) : def __init__(self, f) : self.f = f def __call__(self, *args, **kwargs) : ret = self.f(*args, **kwargs) while isinstance(ret, TailCall) : ret = ret.handle() return ret def tailCaller(f): f.tailCaller = True return f class TailCall(object) : def __init__(self, call, *args, **kwargs) : if isinstance(call, TailCall): call.update(**kwargs) kwargs = call.kwargs args = call.args + args call = call.call self.call = call self.args = args self.kwargs = kwargs def __call__(self, *args, **kwargs): self.kwargs.update(kwargs) self.args = self.args + args return self def update(self, **d): self.kwargs.update(d) return self def handle(self) : if isinstance(self.call, TailCaller) : return self.call.f(*self.args, **self.kwargs) else : return self.call(*self.args, **self.kwargs) def handle_exit_code(d, code): if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): if code == d.DIALOG_CANCEL: msg = "Vous avez choisi Annuler dans la dernière fenêtre de dialogue.\n\n" \ "Voulez vous quitter le programme ?" os.system('clear') sys.exit(0) else: msg = "Vous avez appuyer sur ESC dans la dernière fenêtre de dialogue.\n\n" \ "Voulez vous quitter le programme ?" if d.yesno(msg, width=60) == d.DIALOG_OK: os.system('clear') sys.exit(0) return False else: return True # code est d.DIALOG_OK class GestCrans(object): def __getattribute__(self, attr): ret = super(GestCrans, self).__getattribute__(attr) if getattr(ret, 'tailCaller', False) and not isinstance(ret, TailCaller): ret = TailCaller(ret) setattr(self, attr, ret) return ret def __init__(self): signal.signal(signal.SIGINT, signal.SIG_IGN) # On initialise le moteur de rendu en spécifiant qu'on va faire du dialog printing.template(dialog=True) # On ouvre une connexion lc_ldap self.conn = lc_ldap.shortcuts.lc_ldap_admin() # On vérifie que l'utilisateur système existe dans ldap (pour la gestion des droits) luser=self.conn.search(u'(&(uid=%s)(objectClass=cransAccount))' % self.conn.current_login) if not luser: sys.stderr.write("L'utilisateur %s n'existe pas dans la base de donnée" % self.conn.current_login) sys.exit(1) self.conn.droits = [str(d) for d in luser[0]['droits']] self.conn.dn = luser[0].dn self.dialog = Dialog() self.menu_principal() @tailCaller def search(self, objectClassS, title, values={}, cont=None): """ Rechercher des adhérents ou des machines dans la base ldap retourne le tuple (code de retour dialog, valeurs entrée par l'utilisateur, liste d'objets trouvés) La fonction est découpé en trois partie : * affichage dialog et récupération du résultat * construction de filtres de recherche ldap et recherches ldap * filtre sur les résultats des recherches ldap """ select_dict = { #label attribut ldap search for substring param dialog: line col valeur input-line icol len max-chars 'Nom' : {'ldap':'nom', 'sub':True, 'params' : [ 1, 1, values.get('nom', ""), 1, 13, 20, 20]}, 'Prenom' : {'ldap':'prenom', 'sub':True, 'params' : [ 2, 1, values.get('prenom', ""), 2, 13, 20, 20]}, 'Téléphone' : {'ldap':'tel', 'sub':True, 'params' : [ 3, 1, values.get('tel', ""), 3, 13, 10, 00]}, 'Chambre' : {'ldap':'chbre','sub':True, 'params' : [ 4, 1, values.get('chbre',""), 4, 13, 05, 00]}, 'aid' : {'ldap' : 'aid', 'sub':False, 'params' : [ 5, 1, values.get('aid',""), 5, 13, 05, 05]}, 'mail' : {'ldap' : 'mail', 'sub':True, 'params' : [ 6, 1, values.get('mail',""), 6, 13, 20, 00]}, # seconde colone 'Machine' : {'ldap' : '*', 'sub':True, 'params' : [1, 35, "", 1, 43, 0, 0]}, 'Host' : {'ldap' : 'host', 'sub':True, 'params' : [2, 37, values.get('host',""), 2, 43, 17, 17]}, 'Mac' : {'ldap' : 'macAddress', 'sub':False, 'params' : [3, 37, values.get('macAddress',""), 3, 43, 17, 17]}, 'IP' : {'ldap' : 'ipHostNumber', 'sub':False,'params' : [4, 37, values.get('ipHostNumber',""), 4, 43, 15, 15]}, 'mid' : {'ldap' : 'mid', 'sub':False, 'params' : [5, 37, values.get('mid',""), 5, 43, 5, 5]}, } # On a besoin de l'ordre pour récupérer les valeurs ensuite select_adherent = ['Nom', 'Prenom', 'Téléphone', 'Chambre', 'aid', 'mail'] select_machine = ['Host', 'Mac', 'IP', 'mid'] def box(): # On met les argument à dialog à la main ici, sinon, c'est difficile de choisir comment mettre une seconde colone cmd = ["--form", "Entrez vos paramètres de recherche", '0', '0', '0'] for key in select_adherent: cmd.extend(['%s :' % key] + [str(e) for e in select_dict[key]['params']]) cmd.extend(['Machine :'] + [str(e) for e in select_dict['Machine']['params']]) for key in select_machine: cmd.extend(['%s :' % key] + [str(e) for e in select_dict[key]['params']]) cmd.extend(["Les champs vides sont ignorés.", '7', '1', "", '0', '0', '0', '0']) # On utilise quand même la fonction de la bibliothèques pour passer les arguments (code, output) = self.dialog._perform(*(cmd,), title=title, backtitle="Entrez vos paramètres de recherche") if output: return (code, output.split('\n')[:-1]) else: # empty selection return (code, []) (code, dialog_values) = box() # Si il a appuyé sur annuler ou sur escape, on saute sur la continuation if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): return cont else: # Transformation de la liste des valeures entrée en dictionnnaire dialog_values = dict(zip(select_adherent + select_machine, dialog_values)) ldap_values = dict([(select_dict[key]['ldap'], value) for key, value in dialog_values.items()]) # Construction des filtres ldap pour les adhérents et les machines filter_adherent = [] filter_machine = [] for (key, value) in dialog_values.items(): if value: if key in select_adherent: filter_adherent.append((u"(%s=*%s*)" if select_dict[key]['sub'] else u"(%s=%s)") % (select_dict[key]['ldap'], unicode(value, 'utf-8'))) elif key in select_machine: filter_machine.append((u"(%s=*%s*)" if select_dict[key]['sub'] else u"(%s=%s)") % (select_dict[key]['ldap'], unicode(value, 'utf-8'))) if filter_adherent: filter_adherent=u"(&%s)" % "".join(filter_adherent) if filter_machine: filter_machine=u"(&%s)" % "".join(filter_machine) # Récupération des adhérents et des machines adherents=self.conn.search(filter_adherent) if filter_adherent else [] machines=self.conn.search(filter_machine) if filter_machine else [] # Filtrage des machines en fonction des adhérents if filter_adherent: if filter_machine: # Si on filtre sur des adhérent et des machines, on calcule l'intersection adherents_dn = set([a.dn for a in adherents]) machines_f = [m for m in machines if m.parent_dn in adherents_dn] else: # Sinon on filtre seulement sur les adhérents, récupère les machines des adhérents trouvés machines_f = [m for a in adherents for m in a.machines()] else: # Sinon si on filtre seulement sur des machines machines_f = machines # Filtrage des adhérents en fonction des machines if filter_machine: if filter_adherent: # Si on filtre sur des adhérents et des machines, on calcule l'intersection machines_dn = set([m.parent_dn for m in machines]) adherents_f = [a for a in adherents if a.dn in machines_dn] else: # Sinon on récupères les proprios des machines trouvées adherents_f = [m.proprio() for m in machines] else: # Sinon si on filtre seulement sur des adhérents adherents_f = adherents # On filtre sur les objectClassS return ldap_values, [ o for objectClass in objectClassS for o in machines_f+adherents_f if objectClass in o['objectClass'] ] # select_one n'est pas un tailcaller, les continuations sont traitée par les fonctions appelantes def select_one(self, items, title, default_item=None, cont=None): """Fait selectionner un item parmis une liste d'items à l'utisateur""" choices=[] olist={} count = 0 # On sépare les item d'items en fonction de leur type for o in items: olist[o.__class__] = olist.get(o.__class__, []) + [o] default_tag = items.index(default_item) if default_item in items else default_item # On se débrouille pour faire corresponde l'ordre d'affichache des objets # et leur ordre dans la liste items. On donne la largeur de l'affichage à la main # pour prendre en compte la largeur du widget dialog items=[] items_id = {} (line, col) = get_screen_size() for c in olist.keys(): items.extend(olist[c]) items_s = printing.sprint_list(olist[c], col-20).encode('utf-8').split('\n') choices.append(("", str(items_s[0]))) choices.append(("", str(items_s[1]))) for i in items_s[2:]: choices.append((str(count), str(i))) count+=1 # On laisse une ligne vide pour séparer les listes d'objets de type différent choices.append(("", "")) # On supprime la dernière ligne qui est vide del(choices[-1]) (code, tag) = self.dialog.menu( "Que souhaitez vous faire ?", width=0, height=0, menu_height=0, item_help=0, default_item=str(default_tag), title=title, scrollbar=True, choices=choices, colors=True) # Si l'utilisateur à annulé, on coninue avec la continuation if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): return cont # Si l'utilisateur n'a pas choisis une ligne correspondant à quelque chose elif not tag: self.dialog.msgbox("Merci de choisir l'un des item de la liste ou d'annuler", title="Sélection", width=0, height=0) return TailCall(self.select_one, items, title, default_item, cont) # Sinon on retourne l'item choisis elif self.select_confirm(items[int(tag)], title): return items[int(tag)] else: return cont def select_confirm(self, item, title): """Affiche un item et demande si c'est bien celui là que l'on veux (supprimer, éditer, créer,...)""" return self.dialog.yesno( printing.sprint(item), no_collapse=True, colors=True, title=title, width=0, height=0, backtitle="Appuyez sur MAJ pour selectionner du texte" ) == self.dialog.DIALOG_OK @tailCaller def select(self, objectClassS, title, values={}, cont=None): """Permet de choisir un objet adhérent ou machine dans la base ldap""" try: # On fait effectuer une recherche à l'utilisateur values, items = self.search(objectClassS, title, values, cont=cont) # S'il n'y a pas de résultas, on recommence if not items: self.dialog.msgbox("Aucun Résultat", title="Recherche", width=0, height=0) return TailCall(self.select, objectClassS, title, values, cont=cont) # S'il y a plusieurs résultats elif len(items)>1: # On en fait choisir un, si c'est une continuation qui est renvoyé, elle est gérée par select return self.select_one(items, title, cont=TailCall(self.select, objectClassS, title, values, cont)) # S'il y a exactement 1 résultat à la recherche, on fait confirmer son choix à l'utilisateur elif len(items) == 1: item=items[0] # On fait confirmer son choix à l'utilisateur if self.select_confirm(item, title): return item else: return TailCall(self.select, objectClassS, title, values, cont=cont) except Exception as e: self.dialog.msgbox("%r" % e, title="Erreur rencontrée", width=0, height=0) return TailCall(self.select, objectClassS, title, values, cont=cont) @tailCaller def modif_adherent(self, cont, adherent=None): if adherent is None: adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont) self.dialog.msgbox("todo", width=0, height=0) return cont(proprio=adherent) @tailCaller def create_adherent(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont @tailCaller def delete_adherent(self, cont): adherent = self.select(["adherent"], "Recherche d'un adhérent pour supression", cont=cont) self.dialog.msgbox("todo", width=0, height=0) return cont def modif_machine_information(self, machine, cont): self.dialog.msgbox("todo", width=0, height=0) (code, tag) = self.dialog.form( text="", height=0, width=0, form_height=0, fields=[("Nom de machine :", "", 10, 30), ("Rugby Team", "", 20), ("Car", "", 20), ("Celebrity", "", 20)], title="A demo of the form dialog.", backtitle="And now, for something " "completely different...") return cont def modif_machine_blacklist(self, machine, cont): self.dialog.msgbox("todo", width=0, height=0) return cont def modif_machine_alias(self, machine, cont): self.dialog.msgbox("todo", width=0, height=0) return cont def modif_machine_exemption(self, machine, cont): self.dialog.msgbox("todo", width=0, height=0) return cont def modif_machine_remarque(self, machine, cont): self.dialog.msgbox("todo", width=0, height=0) return cont @tailCaller def modif_machine(self, cont, machine=None, tag=None): if machine is None: machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour modification", cont=cont) menu = { 'Information' : {'text' : "Modifier le nom de machine, l'IP, adresse MAC", "callback":self.modif_machine_information}, 'Blackliste' : {'text': 'Modifier les blacklist de la machine', 'callback':self.modif_machine_blacklist}, 'Alias' : {'text': 'Créer ou supprimer un alias de la machine', 'callback':self.modif_machine_alias}, 'Exemption' : {'text':"Modifier la liste d'exemption d'upload de la machine", 'callback':self.modif_machine_exemption}, 'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'callback':self.modif_machine_remarque}, } menu_order = ['Information', 'Blackliste', 'Alias', 'Exemption', 'Remarques'] def box(default_item=None): return self.dialog.menu( "Que souhaitez vous modifier ?", width=0, height=0, menu_height=0, item_help=0, default_item=str(default_item), title="Modification de %s" % machine['host'][0], scrollbar=True, cancel_label="Retour", backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, choices=[(key, menu[key]['text']) for key in menu_order]) (code, tag) = box(tag) if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC): return cont(machine=machine) elif not tag in menu_order: return TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag) else: return TailCall(menu[tag]['callback'], machine=machine, cont=TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag)) @tailCaller def create_machine_adherent(self, cont, adherent=None): if adherent is None: adherent = self.select(["adherent"], "Recherche d'un adhérent pour lui ajouter une machine", cont=cont) self.dialog.msgbox("todo", width=0, height=0) return cont(proprio=adherent) @tailCaller def delete_machine(self, cont): machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour supression", cont=cont) self.dialog.msgbox("todo", width=0, height=0) return cont @tailCaller def create_club(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont @tailCaller def delete_club(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont @tailCaller def modif_club(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont @tailCaller def create_machine_club(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont @tailCaller def create_machine_crans(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont @tailCaller def create_borne(self, cont): self.dialog.msgbox("todo", width=0, height=0) return cont @tailCaller def menu_principal(self, tag=None, machine=None, proprio=None): menu = { 'aA' : {'text':"Inscrire un nouvel adhérent", 'callback': self.create_adherent}, 'mA' : {'text':"Modifier l'inscription d'un adhérent", 'callback': self.modif_adherent, 'help':"Changer la chambre, la remarque, la section, la carte d'étudiant ou précâbler."}, 'aMA': {'text':"Ajouter une machine à un adhérent", 'callback': self.create_machine_adherent}, 'dA' : {'text':"Détruire un adhérent", 'callback': self.delete_adherent, 'help':"Suppression de l'adhérent ainsi que de ses machines"}, 'mM' : {'text':"Modifier une machine existante", 'callback': self.modif_machine, 'help':"Changer le nom ou la MAC d'une machine."}, 'dM' : {'text':"Détruire une machine", 'callback': self.delete_machine}, 'aC' : {'text':"Inscrire un nouveau club", 'callback': self.create_club}, 'mC' : {'text':"Modifier un club", 'callback': self.modif_club}, 'aMC': {'text':"Ajouter une machine à un club", 'callback': self.create_machine_club}, 'dC' : {'text':"Détruire un club", 'callback': self.delete_club}, 'aKM': {'text':"Ajouter une machine à l'association", 'callback': self.create_machine_crans}, 'aKB': {'text':"Ajouter une borne wifi", 'callback': self.create_borne}, '' : {'text':"---------------------------------------",'callback': None}, } ### Les clef qui n'existe pas sont toute renvoyé sur la clef '' menu_order = ["aA", "mA", "aMA", "dA", "", "mM", "dM", " ", "aC", "mC", "aMC", "dC", " ", "aKM", "aKB"] if machine and not proprio: proprio = machine.proprio() if machine or proprio: menu_order = [' '] + menu_order if machine: menu_machine = { 'mMc' : { 'text':"Modifier la machine %s" % machine['host'][0], 'callback': TailCall(self.modif_machine, machine=machine), 'help':"Changer le nom ou la MAC d'une machine." }, } menu_machine_order = ['mMc'] menu.update(menu_machine) menu_order = menu_machine_order + menu_order if proprio: menu_proprio = { 'mAc' : { 'text':"Modifier l'inscription de %s" % proprio.get("cn", proprio["nom"])[0], 'callback': TailCall(self.modif_adherent, adherent=proprio) }, 'aMc' : { 'text':"Ajouter une machine à %s" % proprio.get("cn", proprio["nom"])[0], 'callback': TailCall(self.create_machine_adherent, adherent=proprio) }, } menu_proprio_order = ['mAc', 'aMc'] menu.update(menu_proprio) menu_order = menu_proprio_order + menu_order def box(default_item=None): return self.dialog.menu( "Que souhaitez vous faire ?", width=0, height=0, menu_height=0, item_help=1, default_item=str(default_item), title="Menu principal", scrollbar=True, cancel_label="Quitter", backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, choices=[(key, menu.get(key, menu[''])['text'], menu.get(key, menu['']).get('help', "")) for key in menu_order]) (code, tag) = box(tag) callback = menu.get(tag, menu[''])['callback'] if handle_exit_code(self.dialog, code) and callback: return TailCall(callback, cont=TailCall(self.menu_principal, tag, machine=machine, proprio=proprio)) else: return TailCall(self.menu_principal, tag, proprio=proprio, machine=machine) if __name__ == '__main__': GestCrans() os.system('clear')